Spring AOP not injecting transaction in #Transactional method from a proxied class - java

We have an application that we are migrating from Spring Boot 1 and Hibernate 3.6.8 to latest Spring Boot 2 and Hibernate 5.2.17.
Everything seems to be ok, except that there are some methods that we are calling and are not getting a transaction injected. Like this service:
#Service
public class BreakFixProcessorBO extends BaseProcessorBO {
#Transactional
public void process(String xml){
[...]
It's called from this other class:
#Service
public class BreakFixAmerInternalConsumer extends BaseConsumer {
private BreakFixProcessorBO breakFixProcessorBO;
#Autowired
public BreakFixAmerInternalConsumer(BreakFixProcessorBO breakFixProcessorBO) {
this.breakFixProcessorBO = breakFixProcessorBO;
}
#Override
protected void proccess(String messageBody) {
breakFixProcessorBO.process(messageBody);
}
Many other code call repositories use #Transactional, and many services also use #Transactional, and many of them work correctly. Except that some of them aren't, and we can't find any difference.
I have been reading a lot of documentation on how transactions work on proxied classes. And it seems that when the class implements an interface behaviour changes a bit.
So I tried to create an interface just with the method:
public interface BreakFixProcessorBO_TransactionTest {
void process(String xml);
}
And have the service implement it:
#Service
public class BreakFixProcessorBO extends BaseProcessorBO implements BreakFixProcessorBO_TransactionTest {
Does not matter what we do. We are having the following exception on some specific services:
Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3505) ~[hibernate-core-5.2.17.Final.jar!/:5.2.17.Final]
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1427) ~[hibernate-core-5.2.17.Final.jar!/:5.2.17.Final]
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423) ~[hibernate-core-5.2.17.Final.jar!/:5.2.17.Final]
at org.springframework.orm.hibernate5.SessionFactoryUtils.flush(SessionFactoryUtils.java:147) ~[spring-orm-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:95) ~[spring-orm-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:96) ~[spring-tx-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:922) ~[spring-tx-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:730) ~[spring-tx-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714) ~[spring-tx-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:532) ~[spring-tx-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304) ~[spring-tx-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at d.s.a.bizo.processor.BreakFixProcessorBO$$EnhancerBySpringCGLIB$$e171e8a9.process(<generated>) ~[classes/:na]
at d.s.a.consumer.BreakFixAmerInternalConsumer.proccess(BreakFixAmerInternalConsumer.java:25) ~[classes/:na]
at d.s.a.consumer.BaseConsumer.onMessage(BaseConsumer.java:16) ~[classes/:na]
at d.s.a.consumer.BreakFixAmerInternalConsumer.onMessage(BreakFixAmerInternalConsumer.java:31) ~[classes/:na]
at sun.reflect.GeneratedMethodAccessor218.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_162]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_162]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:181) ~[spring-messaging-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:114) ~[spring-messaging-5.0.7.RELEASE.jar!/:5.0.7.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:51) ~[spring-rabbit-2.0.4.RELEASE.jar!/:2.0.4.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:182) ~[spring-rabbit-2.0.4.RELEASE.jar!/:2.0.4.RELEASE]
... 10 common frames omitted
It seems that Spring AOP is proxying successfully the instance... but no transaction is created.
I tried Propagation.REQUIRES_NEW, and it behaves the same.
Our PlatformTransactionManager is defined together with our datasource:
#Configuration
#EnableTransactionManagement(proxyTargetClass = true)
#EnableAspectJAutoProxy(proxyTargetClass = true)
#RefreshScope
public class DBConfiguration extends DatabaseConfiguration implements EnvironmentAware {
#Primary
#Bean
public LocalSessionFactoryBean sessionFactory() {
final LocalSessionFactoryBean annotationSessionFactoryBean = new LocalSessionFactoryBean();
annotationSessionFactoryBean.setDataSource(dataSource());
annotationSessionFactoryBean.setPackagesToScan("d.s.a");
annotationSessionFactoryBean.setHibernateProperties(hibernateProperties());
return annotationSessionFactoryBean;
}
#Primary
#Bean
public DataSource dataSource() {
return createDataSource(propertyResolver);
}
#Primary
#Bean
public PlatformTransactionManager transactionManager() {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory().getObject());
return txManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
And our application uses most auto-configuration available:
#SpringBootApplication
#EnableAutoConfiguration(
exclude = {
JmsAutoConfiguration.class,
CamelAutoConfiguration.class
})
#ImportResource({"classpath:/META-INF/spring/applicationContext.xml"})
#ComponentScan(basePackages = "d.s.a")
public class OurApplication extends SpringBootServletInitializer implements WebApplicationInitializer {
public static void main(String[] args) {
if(System.getProperty("debug") != null) { //-Ddebug=true should be a feature
args = Arrays.copyOf(args, args.length+1);
args[args.length-1] = "--debug";
}
SpringApplication.run(OurApplication.class, args);
}
}
The applicationContext.xml is just some old task runner definitions we haven't migrated yet.
What are we missing, or is it a Spring bug?

Have you tried
#Override
#Transactional
protected void proccess(String messageBody) {
breakFixProcessorBO.process(messageBody);
}

Related

Spring Hibernate not committing data

I have a Spring MVC application we're developing, using Spring 4.2.3 and Hibernate 5.0.6. In some of our testing we found that some of our data is not being saved to the database (Oracle). The code is as follows:
#Service
#Transactional
public class MyService {
#Autowired
private MyDao dao;
public void saveCustomer(Customer c) {
dao.saveCustomer(c);
}
}
#Repository
#Transactional
public class MyDao {
#Autowired
private SessionFactory sessionFactory; // Hibernate's SessionFactory
public void saveCustomer(Customer cust) {
sessionFactory.getCurrentSession().clear();
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().save(cust);
}
}
To be honest, I just saw that #Transactional on MyDao, as I'm writing this. That probably is wrong, since #Transactional is already on the service layer.
We're using Java Config....showing the relevant beans....
#Configuration
#EnableWebMvc
#EnableTransactionManagement
#ComponentScan({"my.package"})
public class Config extends WebMvcConfigurerAdapter {
#Bean(name = "sessionFactory")
public SessionFactory getSessionFactory(ComboPooledDataSource dataSource) {
LocalSessionFactoryBuilder sessionBuilder = new LocalSessionFactoryBuilder(dataSource);
sessionBuilder.scanPackages("my.entities");
sessionBuilder.addProperties(getHibernateProperties());
return sessionBuilder.buildSessionFactory();
}
#Bean(name = "transactionManager")
public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager transactionManager = new HibernateTransactionManager(sessionFactory);
return transactionManager;
}
}
My question is, other than the #Transactional on MyDao, which shouldn't be there, is there anything else that stands out to anyone as to why Customer does not save to the database? Are we even doing transactions correctly? I've never liked using Hibernate's SessionFactory, but that's the way we went. Are we using it correctly within a Spring Transaction?
Thanks!
Chris

Spring boot hibernate 5 integration error

I am trying to integrate spring Boot with Hibernate 5 using Java configs.
It has been some time since I have used Hibernate.
I am getting the below mentioned exception. Can someone please suggest how to resolve this ?
Following is my java config file followed by the exception stack trace.
package com.example;
import com.example.dao.CustomerDao;
import com.example.model.Customer;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.Properties;
#SpringBootApplication
#EnableTransactionManagement
public class SpringHibernateDemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringHibernateDemoApplication.class, args);
}
#Bean
public DataSource dataSource() throws ClassNotFoundException {
//SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
// dataSource.setDriverClass((Class<? extends Driver>)Class.forName("com.mysql.jdbc.Driver"));
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/customerDB");
dataSource.setUsername("root");
dataSource.setPassword("welcome");
return dataSource;
}
#Bean
public LocalSessionFactoryBean sessionFactory() throws ClassNotFoundException {
Properties properties = new Properties();
properties.put("hibernate.dialect","org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.hbm2ddl.auto","create");
// properties.put("hibernate.connection.provider_class","org.hibernate.connection.DatasourceConnectionProvider");
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setDataSource(dataSource());
localSessionFactoryBean.setPackagesToScan("com.example.model");
localSessionFactoryBean.setHibernateProperties(properties);
/*SessionFactory sessionFactory = new LocalSessionFactoryBuilder(dataSource()).
addAnnotatedClass(Customer.class).setProperties(properties).buildSessionFactory();*/
return localSessionFactoryBean;
}
#Bean(name = "hibernateTemplate")
public HibernateTemplate hibernateTemplate() throws ClassNotFoundException {
return new HibernateTemplate(sessionFactory().getObject());
}
#Bean
public CustomerDao customerDao() {
return new CustomerDao();
}
#Override
public void run(String... args) throws Exception {
Customer customer = new Customer();
customer.setFistName("amar");
customer.setLastName("dev");
customer.setEmail("amar#gmail.com");
customerDao().saveCustomer(customer);
}
}
The exception.
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:809) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:790) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:777) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
at com.example.SpringHibernateDemoApplication.main(SpringHibernateDemoApplication.java:23) [main/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_74]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_74]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_74]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_74]
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) [idea_rt.jar:na]
Caused by: java.lang.IllegalStateException: Already value [org.springframework.orm.jpa.EntityManagerHolder#10753c9] for key [org.hibernate.internal.SessionFactoryImpl#ba5d30] bound to thread [main]
at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:190) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.orm.hibernate5.SpringSessionContext.currentSession(SpringSessionContext.java:127) ~[spring-orm-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:454) ~[hibernate-core-5.2.0.Final.jar:5.2.0.Final]
at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:326) ~[spring-orm-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:309) ~[spring-orm-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.orm.hibernate5.HibernateTemplate.save(HibernateTemplate.java:616) ~[spring-orm-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at com.example.dao.CustomerDao.saveCustomer(CustomerDao.java:11) ~[main/:na]
at com.example.dao.CustomerDao$$FastClassBySpringCGLIB$$fb200467.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
at com.example.dao.CustomerDao$$EnhancerBySpringCGLIB$$22cf791b.saveCustomer(<generated>) ~[main/:na]
at com.example.SpringHibernateDemoApplication.run(SpringHibernateDemoApplication.java:71) [main/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:806) [spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
The problem is you aren't defining a HibernateTransactionManager next to that Spring Boot detects hibernate and JPA and automatically configures it. Both technologies try to start a transaction and that fails. Instead work with the framework, Spring Boot already auto configures a DataSource and EntityManagerFactory (the JPA variant of a SessionFactory) for you.
Missing HibernateTransactionManager
If you really want to use plain Hibernate add a transaction manager.
#Bean
public HibernateTransactionManager transactionManager(SessionFactory sf) {
return new HibernateTransactionManager(sf);
}
Use Spring Boot to configure DataSource
I would however urge you to use the framework.
Create a file called application.properties in src/main/resources and put the following properties in it.
spring.datasource.url=jdbc:mysql://localhost:3306/customerDB
spring.datasource.username=root
spring.datasource.password=welcome
Now you can remove the DataSource and you can rewrite your sessionFactory method to take a DataSource as method argument.
#Bean
public LocalSessionFactoryBean sessionFactory(DataSource ds) throws ClassNotFoundException {
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setDataSource(dataSource());
... // Other settings here
return localSessionFactoryBean;
}
Use JPA instead of plain Hibernate
However instead of doing this I suggest removing everything and add the following to your configuration (the application.properties).
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL57InnoDBDialect
Now in your SpringHibernateDemoApplication you can remove everything but your CustomerDao (unless that is annotated with #Repository you could remove that as well). Spring Boot already detects and enables transactions so you can also remove the #EnableTransactionManagement.
#SpringBootApplication
public class SpringHibernateDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringHibernateDemoApplication.class, args);
}
#Bean
public CustomerDao customerDao() {
return new CustomerDao();
}
#Bean
public CommandLineRunner runner(CustomerDao dao) {
return new CommandLineRunner() {
#Override
public void run(String... args) throws Exception {
Customer customer = new Customer();
customer.setFistName("amar");
customer.setLastName("dev");
customer.setEmail("amar#gmail.com");
dao.saveCustomer(customer);
}
};
}
}
The only thing for you to do is to replace the use of HibernateTemplate (which isn't recommended in the first place!) with the use of an EntityManager. See the JPA Section of the reference guide.
#Repository
public class CustomerDao {
#PersistenceContext
private EntityManager em;
public void saveCustomer(Customer customer) {
em.persist(customer);
}
}
Use Spring Data JPA
However if you want to really reduce your code I suggest adding the spring-boot-starter-data-jpa. This will add Spring Data JPA and allows you to remove your current CustomerDao and simply replace it with an interface. (Code below assumes Customer has an id of type Long.).
public interface CustomerDao extends JpaRepository<Customer, Long> {}
Now replace the saveCustomer with save in the CommandLineRunner. (I added the COmmandLineRunner as an inner bean to be able to inject the CustomerDao into the method as an argument).
#SpringBootApplication
public class SpringHibernateDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringHibernateDemoApplication.class, args);
}
#Bean
public CommandLineRunner runner(CustomerDao dao) {
return new CommandLineRunner() {
#Override
public void run(String... args) throws Exception {
Customer customer = new Customer();
customer.setFistName("amar");
customer.setLastName("dev");
customer.setEmail("amar#gmail.com");
dao.save(customer);
}
};
}
}
I strongly suggest a read of the Spring Boot reference guide and learn what it is and what it does. I would also suggest learning new and accepted technologies like JPA and how Spring projects like Spring Data JPA can help you with those.
The root issue you have is basically the usage of LocalSessionFactoryBean with spring-data which already defines a EntityManagerFactory instance; one would probably have no reason to use it directly but HibernateTemplate might appear as a good reason; if you really need to use HibernateTemplate than create it like below such that to avoid the creation of an additional EntityManagerFactory instance:
#Bean
public HibernateTemplate hibernateTemplate(EntityManagerFactory entityManagerFactory) {
return new HibernateTemplate(entityManagerFactory.unwrap(SessionFactory.class));
}
PS: this solution is incomplete because I don't know how to further integrate the correctly created HibernateTemplate (which works with reads but not writes) with the existing transaction manager

Spring Transaction doesn't Rollback at RuntimeException in #Transactional method

I have this DB configuration:
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = "com.mycompany")
public class DBConfiguration {
#Bean(destroyMethod = "close")
public javax.sql.DataSource dataSource() {
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost/v2");
ds.setUsername("java");
ds.setPassword("mypass");
ds.setInitialSize(5);
ds.setMaxActive(10);
ds.setMaxIdle(5);
ds.setMinIdle(2);
ds.setRemoveAbandoned(true);
ds.setLogAbandoned(true);
return ds;
}
#Bean
public DataSourceTransactionManager txManager()
{
DataSourceTransactionManager tx= new DataSourceTransactionManager(dataSource());
return tx;
}
}
QUESTION UPDATED
I have some trouble to understand how #Transaction annotation works, please consider this scenario:
#Service
public class FirstService {
#Transactional //<--- this annotation seems to be mandatory for my rollback but I don't want it.
public void test() throws Exception{
secondService.insert();
}
}
#Service
public class SecondService {
#Transactional //<-- I would like to have only this method in transaction
protected void insert() throws Exception{
dao.insertEntity(new Entity()); //<<--- this is an SQL insert command
throw new RuntimeException("Rollback test");
}
}
Test code:
#RequestMapping("/test") #ResponseBody
public void test() throws Exception{
firstService.test();
}
Dao:
public void insertEntity(Entity e) {
getJdbcTemplate().update(SQL_INSERT,e.getCode(),e.getName());
}
This test WORKS, thrown exception could rollback the transaction.
Why if I omit the #Transaction annotation on the firstService there is not rollback?
Seems that from #Controller to #Service the txmanager looks for #Transaction annotation, from #Service to (another) #Service or #Component it doesn't look for it.
This works for me:
#Bean(name = "transactionManager")
#Primary
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
Please update the spring version and maybe some logs or debug logs and see if there is an issue with Transaction
There were two error.
First error was that I cannot nest two method (first not transactional, second transactional) in the same object then I have to separate it in two objects (as you can see in updated question).
Second error was that #Transaction annotation shuold be applied to public methods not to private or protected.

Spring Data - #Transactional annotation in Service class raises AopConfigException

I am using Spring and Hibernate and I am successfully autowiring a Repository inside the contructor of a Service class. When i try to add #Transactional methods in my Service class i get an AopConfigException that it is about the generation of CGLib class.
My Configuration consists in 3 files.
An AppInitializer class that implements WebApplicationInitializer , a WebMvcConfig class that extends WebMvcConfigurerAdapter and lastly a PersistentContext class.
AppInitializer
public class AppInitializer implements WebApplicationInitializer {
private static final String CONFIG_LOCATION = "com.project.app.config";
private static final String MAPPING_URL = "/";
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext context = getContext();
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet",
new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping(MAPPING_URL);
}
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation(CONFIG_LOCATION);
return context;
}
WebMvcConfig
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = { "com.project.app" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private Environment env;
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("hello");
}
#Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
}
}
PersistentContext
#Component
#EnableJpaRepositories("com.project.app.services.repositories")
#EnableTransactionManagement
#PropertySource("classpath:application.properties")
public class PersistenceContext {
#Autowired
private Environment env;
#Bean
#Primary
public DataSource dataSource() throws ClassNotFoundException {
DataSource ds = new DataSource();
ds.setUrl(env.getProperty(SystemSettings.DS_URL));
ds.setUsername(env.getProperty(SystemSettings.DS_USERNAME));
ds.setPassword(env.getProperty(SystemSettings.DS_PASSWORD));
ds.setDriverClassName(env.getProperty(SystemSettings.DS_DRIVER));
return ds;
}
#Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPackagesToScan("com.project.app.services.entities");
// .. Set Properties..
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
#Bean
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
The Repository INTERFACE extends CrudRepository
StopRepository
#Repository
#RepositoryRestResource(collectionResourceRel = "stop", path = "stop")
public interface StopRepository extends CrudRepository<StopJPA, Long> {
#Override
StopJPA save(StopJPA persisted);
}
The Entity class.
StopJPA
#Entity
#Table(name = "stop")
public class StopJPA implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "stop_description")
private String stopDescription;
#Column(name = "id_stop", nullable = false)
private String idStop;
public StopJPA() {
}
public StopJPA(String stopDescription, String idStop) {
this.stopDescription = stopDescription;
this.idStop = idStop;
}
// .. Getters & Setters ..
}
And the Service class implementation:
StopService
#Service
final class RepoStopService {
private StopRepository stopRepository;
#Autowired
RepoStopService(StopRepository stopRepository) {
this.stopRepository = stopRepository;
}
#Transactional
public StopDTO create(StopDTO newEntry) {
StopJPA created = new StopJPA(newEntry.getStopDescription(), newEntry.getIdStop());
created = stopRepository.save(created);
return EntitiesConverter.mapEntityIntoDTO(created);
}
}
Unfortunately when i try to run it on server i get this exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'repoStopService' defined in file [C:..\RepoStopService.class]: Initialization of bean failed;
Caused by: org.springframework.aop.framework.AopConfigException:Could not generate CGLIB subclass of class [class ..RepoStopService]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class ..RepoStopService
I understand that Spring uses either JDK proxy or CGLib. The default behavior if we autowire an interface (here its the repository) is to have a JDK proxy?
what need to be changed in order to make it work?
The exception message is quite clear
AopConfigException:Could not generate CGLIB subclass of class [class ..RepoStopService
First of all it is complaining about the fact that it cannot create a proxy for your service it doesn't mention your repositories at all. Your service is a class without an interface and it is also final.
The #Transactional is on a method in your service. Spring needs to create a proxy for your service to be able to start and commit the transaction at that point. As your service isn't implementing an interface it tries to create a class based proxy, however it cannot because your class is marked final.
To fix, remove the final or create an interface to be able to use JDK Dynamic proxies instead of Cglib based class proxies.
Note: Depending on the version of Spring used, it will still fail when removing the final keyword as on earlier versions it is also required to have a no-arg default constructor and you only have a single argument constructor.

Autowired property is null - Spring Boot Configuration

I am stuck with null values in an autowired property. I am hoping I could get some help.
We are using for the project spring-boot version 0.5.0.M6.
The four configuration files with beans are in one package and are sorted by "area":
Data source configuration
Global method security configuration (as we use Spring-ACL)
MVC configuration
Spring Security configuration
The main method that bootstraps everything is in the following file:
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableAutoConfiguration(exclude = {
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
SecurityAutoConfiguration.class,
ThymeleafAutoConfiguration.class,
ErrorMvcAutoConfiguration.class,
MessageSourceAutoConfiguration.class,
WebSocketAutoConfiguration.class
})
#Configuration
#ComponentScan
public class IntegrationsImcApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = SpringApplication.run(
IntegrationsImcApplication.c lass, args);
}
}
The first file that holds the data source configuration beans is as follows (I have omitted some method body parts to make it more readable):
#EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
#Configuration
public class RootDataSourceConfig
extends TomcatDataSourceConfiguration
implements TransactionManagementConfigurer {
#Override
public DataSource dataSource() {
return jpaDataSource();
}
public PlatformTransactionManager annotationDrivenTransactionManager() {
return jpaTransactionManager();
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
#Bean(name="jpaDataSource")
public DataSource jpaDataSource() {......}
#Bean(name = {"transactionManager","txMgr"})
public JpaTransactionManager jpaTransactionManager() {......}
#Bean(name = "entityManagerFactory")
public EntityManagerFactory jpaEmf() {......}
}
And here is the next configuration file, that depends on the data source from above. It has about 20 beans related to ACL configuration, but it fails on the firsts bean that uses data source:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
public class RootGlobalMethodSecurityConfig
extends GlobalMethodSecurityConfiguration
implements Ordered {
#Autowired
public DataSource dataSource;
#Override
public int getOrder() {
return IntegrationsImcApplication.ROOT_METHOD_SECURITY_CO NFIG_ORDER;
}
#Bean
public MutableAclService aclService()
throws CacheException, IOException {
MutableJdbcAclService aclService = new MutableJdbcAclService(
dataSource, aclLookupStrategy(), aclCache());
aclService.setClassIdentityQuery("SELECT ##IDENTITY");
aclService.setSidIdentityQuery("SELECT ##IDENTITY");
return aclService;
}
...................................
}
Basically invoking aclService() throws an error as dataSource is null. We have tried ordering the configuration files by implementing the Ordered interface. We also tried using #AutoConfigureAfter(RootDataSourceConfig.class) but this did not help either. Instead of doing #Autowired on the DataSource we also tried injecting the RootDataSourceConfig class itself, but it was still null. We tried using #DependsOn and #Ordered on those beans but again no success. It seems like nothing can be injected into this configuration.
The console output at the startup is listing the beans in the order we want them, with data source being the first. We are pretty much blocked by this.
Is there anything weird or unique we are doing here that is not working? If this is as designed, then how could we inject data source differently?
Repo: github
Eager initialization of a bean that depends on a DataSource is definitely the problem. The root cause is nothing to do with Spring Boot or autoconfiguration, but rather plain old-fashioned chicken and egg - method security is applied via an aspect which is wrapped around your business beans by a BeanPostProcessor. A bean can only be post processed by something that is initialized very early. In this case it is too early to have the DataSource injected (actually the #Configuration class that needs the DataSource is instantiated too early to be wrapped properly in the #Configuration processing machinery, so it cannot be autowired). My proposal (which only gets you to the same point with the missing AuthenticationManager) is to declare the GlobalMethodSecurityConfiguration as a nested class instead of the one that the DataSource is needed in:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
protected static class ActualMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Autowired
#Qualifier("aclDaoAuthenticationProvider")
private AuthenticationProvider aclDaoAuthenticationProvider;
#Autowired
#Qualifier("aclAnonymousAuthenticationProvider")
private AnonymousAuthenticationProvider aclAnonymousAuthenticationProvider;
#Autowired
#Qualifier("aclExpressionHandler")
private MethodSecurityExpressionHandler aclExpressionHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(aclDaoAuthenticationProvider);
auth.authenticationProvider(aclAnonymousAuthenticationProvider);
}
#Override
public MethodSecurityExpressionHandler createExpressionHandler() {
return aclExpressionHandler;
}
}
i.e. stick that inside the RootMethodSecurityConfiguration and remove the #EnableGlobalMethodSecurity annotation from that class.
I might have resolved the problem.
GlobalMethodSecurityConfiguration.class has the following setter that tries to autowire permission evaluators:
#Autowired(required = false)
public void setPermissionEvaluator(List<PermissionEvaluator> permissionEvaluators) {
....
}
And in my case the aclPermissionEvaluator() bean needs aclService() bean, which in turn depends on another autowired property: dataSource. Which seems not to be autowired yet.
To fix this I implemented BeanFactoryAware and get dataSource from beanFactory instead:
public class RootMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration implements BeanFactoryAware {
private DataSource dataSource;
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.dataSource = beanFactory.getBean("dataSource", DataSource.class);
}
....
}
After this, other exception showed up, whereSecurityAutoConfiguration.class is complaining about missing AuthenticationManager, so I just excluded it from #EnableAutoConfiguration. I am not sure if its ideal, but I have custom security configuration, so this way everything works ok.

Categories