Spring Boot 2中的@Transactional注解会使得@Autowired字段变为空。

8

我正在尝试在我的服务方法中使用@Transactional注解来懒加载一个字段。然而,将@Transactional应用于我的实现类会使所有自动装配的字段变为null

以下是我的实现代码:

@Service
public class UserServiceImpl implements UserService {

 /**
  * DefaultMapper.
  */
 @Autowired
 private DefaultMapper defaultMapper;

 /**
  * Resource service injection.
  */
 @Autowired
 private ResourceService resourceService;

 /**
  * UserRepository.
  */
 @Autowired
 private UserRepository userRepository;

 /**
  * Jwt Factory.
  */
 @Autowired
 private JwtService jwtService;

 @Override
 @Transactional
 public final UserDto findByLogin(final String login) throws ResourceNotFoundException {
 // user repository is null here when using @Transactional
  User user = this.userRepository.findByLogin(login)
   .orElseThrow(() -> new ResourceNotFoundException(
    resourceService.getMessage(MessageBundle.EXCEPTION, "resource.notfound.user.login")
   ));
  UserDto userDto = defaultMapper.asUserDtoWithRoles(user);
  return userDto;
 }

预先感谢你。


3
从您的方法中删除 final,或者在您的 application.properties 中设置 spring.aop.proxy-target-class=false注意: 这需要 Spring Boot 1.5.3 或更高版本才能完全生效! - M. Deinum
你的Spring应用程序中启用了事务上下文吗? - drgPP
在读取操作期间进行交易大多数时候是没有用的,你确定需要一个读取交易吗? - Zorglube
使用 spring.aop.proxy-target-class=false 或者像 @M.Deinum 建议的那样移除 final 可以解决这个问题。能否请您解释一下 spring.aop.proxy-target-class=false 到底是什么意思?谢谢。 - Mohamed
@Zorglube 我需要一个事务来获取实体中声明的延迟加载数据。 - Mohamed
3个回答

11

在Spring中,事务等操作使用AOP来实现,而Spring的默认AOP机制是使用代理。在使用Spring Boot时,默认代理模式是基于类的代理。

有两种方法可以解决这个问题:

  1. 从你的方法中移除 final
  2. 通过将 spring.aop.proxy-target-class=false 添加到你的application.properties文件中来禁用基于类的代理。

现在当你添加了@Transactional注释时,会创建一个代理UserServiceImpl,确切地说是基于类的代理。它会为UserServiceImpl创建一个子类,并覆盖所有方法以应用TransactionInterceptor。但是,由于你的方法标记为final,动态创建的类无法覆盖这个方法。因此,该方法会查看动态创建代理类中的字段实例,这些实例始终为null

当移除final后,该方法就可以被覆盖,并且可以应用所需的行为,并查看正确的字段实例(即实际的UserServiceImpl而不是代理)。

当禁用基于类的代理时,你将获得JDK动态代理,它基本上是一个薄包装器,实现你的服务实现的所有接口。它应用了所添加的行为(事务)并调用实际的服务。不需要扩展实际的类,因此你可以代理最终方法(只要它是接口的一部分)。


3
我使用 Kotlin 时遇到了同样的问题。当我在服务内部的某个方法上添加了 @Transactional 注解后,我收到了一条消息,表明 被 '@Transactional' 注释的方法必须是可重写的,于是我将类和方法都标记为open。 容易理解,对吧? 嗯,并不完全是这样。
尽管代码编译成功,但在执行时,所需的仓库为空。 我有两种解决方法:
  1. 将类及其所有方法都标记为 open
open class FooService(private val barRepository: BarRepository) {
    open fun aMethod(): Bar {
        ...
    }

    @Transactional
    open fun aTransactionalMethod(): Bar {
        ...
    }
}

这种方法可以运行,但是将类中的所有方法都标记为 open 可能看起来有点奇怪,所以我尝试了其他方法。

  1. 声明一个接口:
interface IFooService {
    fun aMethod(): Bar

    fun aTransactionalMethod(): Bar
}

open class FooService(private val barRepository: BarRepository) : IFooService {
    override fun aMethod(): Bar {
        ...
    }

    @Transactional
    override fun aTransactionalMethod(): Bar {
        ...
    }
}

这样你仍然可以使用注释,因为所有方法都可以被覆盖,你不需要到处使用 "open"。希望这可以帮助=)

1
没错,我只是试图帮助那些可能来自 Kotlin 寻找此问题答案的人,因为这也是我的情况。通常,Kotlin 和 Java 代码很容易互换。 - Jhoan Manuel Muñoz Serrano

0

注意 - 问题的根源在于您执行的方法,而不一定是标有@Transnational的方法。 @Transnational注释会导致动态创建代理对象。当存在final方法时,它将无法在代理对象上运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接