Search code examples
javaspring-bootrollbacktransactional

How to get a method inside a @Component class to be @Transactional?


I am writing an application, based on spring-boot. In my code I have a manager class UManager in which there is a method save. Inside this method, two actions are taking part. The first one saves some data using another Manager and the second one saves the data given into the save method. Now when one of both actions may fail, both should be undone. I assume, that using the @Transactional annotation would provide what I need, as if one of both actions failed, the whole transaction would be rolled back and no changes would be committed to the database.

@Component
public class UManager {

    @Autowired
    AManager aManager;

    @Autowired
    URepository uRepository;

    ... some stuff happening here

    @Transactional
    public U save(UBuilder uBuilder) {
            aManager.save(something);
            uRepository.save(uBuilder.build());
    }
}

As soon as I add the @Transactional annotation to the save method, the application fails to startup. I get the following error:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bController': Injection of autowired dependencies failed;

What am I doing wrong or am I missing out on something?

EDIT: This is the stacktrace:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: UManager BController.uManager; nested exception is java.lang.IllegalArgumentException: Can not set UManager field BController.uManager to com.sun.proxy.$Proxy102
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839) ~[spring-context-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538) ~[spring-context-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118) ~[spring-boot-1.3.1.RELEASE.jar:1.3.1.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:764) [spring-boot-1.3.1.RELEASE.jar:1.3.1.RELEASE]
at org.springframework.boot.SpringApplication.doRun(SpringApplication.java:357) [spring-boot-1.3.1.RELEASE.jar:1.3.1.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:305) [spring-boot-1.3.1.RELEASE.jar:1.3.1.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1124) [spring-boot-1.3.1.RELEASE.jar:1.3.1.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1113) [spring-boot-1.3.1.RELEASE.jar:1.3.1.RELEASE]
at RApplication.main(RApplication.java:44) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_72]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_72]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_72]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_72]
at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:467) [spring-boot-maven-plugin-1.3.1.RELEASE.jar:1.3.1.RELEASE]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_72]

EDIT 2: The RApplication:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
@EnableScheduling
@EnableTransactionManagement
public class RApplication {

    public static void main(String[] args) {
        SpringApplication.run(RaumbuchungApplication.class, args); //this is the line mentioned in the stacktrace
    }
}

The UManager:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import de.mischok.hfmw.raumbuchung.data.URepository;
import de.mischok.hfmw.raumbuchung.types.U;
import de.mischok.hfmw.raumbuchung.types.UBuilder;

@Component
public class UManager {

    @Autowired
    URepository uRepository;

    @Transactional
    public U save(UBuilder uBuilder){

    }
}

The BController:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import UManager;

@Controller
@RequestMapping("my/Path")
public class BController {

    @Autowired
    UManager uManager;
}

These are the important parts of the code, as the other parts are not involved in the problem.


Solution

  • If you are not using xml spring configuration but you have an Application class (I guess RApplication from the stacktrace you posted) which does the configuration make sure to annotate with @EnableTransactionManagement

    e.g.:

    @Configuration  
    @ComponentScan 
    @EnableAutoConfiguration  
    @EnableJpaRepositories
    @EnableTransactionManagement
    public class Application { 
         //rest code goes here
    }
    

    EDIT The error shows that a proxy object failed, you should autowire an interface of the class (UManager) you want to inject not the class itself.

    e.g.

    Change your UManager class to implement an interface (and of course declare any public methods of UManager to the interface too)

    public class UManager implements IUManager
    

    and in your bController class autowire the interface not the class

    public class BController {
        @Autowired
        IUManager uManager;
    }