Spring @Transactional preventing @Autowired in @Service

Viewed 121

I have a service with a bunch of @Autowired services. When I changed one of the methods to be @Transactional, it prevented all of the @Autowired as they are all null.

Example:

@Service
open class UserService {
    @Autowired
    private val userRepository : UserRepository? = null

    @Autowired
    private val emailService : EmailService? = null

    @Transactional
    @Throws(EmailConflictException::class)
    open fun insert(newUser: User, context : RequestContext) {
        if(!isEmailAvailable(newUser.email)) throw EmailConflictException()
        val user = userRepository!!.insert(newUser, context.org.id)
        emailService!!.send(userEmailFactory!!.getInviteEmail(user))
    }
}

Both emailService and userRepository are null during runtime upon calling insert. If I removed the @Transactional from the insert method then both emailService and userRepository are defined during runtime.

Why is this happening? How do I fix it

2 Answers

This issue is normally caused by not having the plugin.spring plugin in the project. This is because Kotlin classes & members are final by default which conflicts with Spring AOP (i.e such as proxing DB transactions).

The plugin.spring is a wrapper of kotlin-allopen and includes the Spring annotations like @Transactional.

Try adding plugin.spring in your project. For example; a working config looks like below.

plugins {
    id("org.springframework.boot") version "2.4.2"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.4.21"
    kotlin("plugin.spring") version "1.4.21"
}

Here's the documentation - https://kotlinlang.org/docs/all-open-plugin.html#gradle

Note: the question shows that the class & the function are open. In theory, it should be sufficient. But as the OP doesn't show the complete project setup and how this code gets invoked. It's not sure why the open function doesn't get the correctly initialised bean.

I think Deinum has the right idea. To add some flavor, read up on CgLib vs. Dynamic Proxying as a concept.

https://cliffmeyers.com/blog/2006/12/29/spring-aop-cglib-or-jdk-dynamic-proxies.html

As one cheap thing you might also try, as counter-intuitive as it might feel, try adding "setters" and "getters" to your private variables.

Then, instead of referencing userRepository.insert... directly, change it to use getUserRepository().insert...

In this manner, by having getters/setters exposed, Spring's calls to set with the setter and return with the getter centralize better on the same ultimatel proxy target so you're both hitting the same private variables instead of the direct-accessing at the dummy proxy-wrapper's not-initialized variables.

Let me know if this helps!

Related