8

I'm trying to use @Transactional annotation in a method on my service to lazily load a field. However using @Transactional on my Implementation class makes all autowired fields null.

Here is my implementation :

@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;
 }

Thank you in advance.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
Mohamed
  • 113
  • 1
  • 2
  • 7
  • 3
    Remove `final` from your method or set `spring.aop.proxy-target-class=false` in your `application.properties`. **NOTE:** This requires Spring Boot 1.5.3 or up to fully work! – M. Deinum Jul 03 '17 at 09:24
  • Is the transactional context enabled in your spring application? – drgPP Jul 03 '17 at 09:27
  • Make an transaction is most of the time useless during an reading action, are you sure you need an reading-transaction ? – Zorglube Jul 03 '17 at 10:01
  • Using spring.aop.proxy-target-class=false or removing final like @M.Deinum suggested fixes the issue. Can you please explain what really does spring.aop.proxy-target-class=false ? thank you – Mohamed Jul 03 '17 at 10:11
  • @Zorglube I need a transaction to fetch data declared in Lazy fetching in the entity. – Mohamed Jul 03 '17 at 10:14

3 Answers3

11

Transaction, amongst others, are applied using AOP, the default AOP mechanism in Spring is to use proxies. When using Spring Boot the proxy mode is set the class based proxies.

You can fix this in 1 of 2 ways.

  1. Remove final from your method
  2. Disable class based proxies by adding spring.aop.proxy-target-class=false to your application.properties

Now when you aded @Transactional this will lead to a proxy of your UserServiceImpl to be created, a class-based proxy to be exact. What happens is that a subclass is created for your UserServiceImpl and all methods are overriden to apply the TransactionInterceptor. However as your method is marked final the dynamically created class cannot override this method. As a result the method looks at field instances in the dynamically created proxy class which always will be null.

When removing final the method can be overridden, the behavior applied and it will look at the proper field instances (of the actual UserServiceImpl instead of the proxy).

When disabling class based proxies, you will get a JDK Dynamic Proxy which is basically a thin wrapper which implements all the interfaces your service implements. It applies the added behavior (transactions) and calls the actual service. There is no extension of the actual class needed and as such you can proxy final methods (as long as it is part of your interface).

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
3

I faced the same problem working with Kotlin. When I added the @Transactional annotation to a method inside a service, I got a message saying Methods annotated with '@Transactional' must be overridable so I went ahead and marked both, the class and the method as open. Easy, right?! Well, not quite.

Although this compiles, I got the required repository as null at execution time. I was able to solve the problem in two ways:

  1. Mark the class and ALL of its methods as open:
open class FooService(private val barRepository: BarRepository) {
    open fun aMethod(): Bar {
        ...
    }

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

This works but having all the methods in a class marked with open might look a bit odd, so I tried something else.

  1. Declare an interface:
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 {
        ...
    }
}

This way you can still use the annotation since all the methods will be overridable and you won't need to use open everywhere.

Hope this helps =)

  • 1
    Right, I just tried to help people who might come from Kotlin looking for an answer for this issue, as it was my case. Usually, Kotlin and Java code are easily interchangeable. – Jhoan Manuel Muñoz Serrano May 08 '20 at 16:56
0

Notice - final on the method you execute creates the problem, not necessary the method marked with @Transnational. @Transnational annotation lead to dynamic creation of proxy objects. When having final method, it will not run on the proxy objects.

maor chetrit
  • 337
  • 3
  • 2