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
Related
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);
}
When configuring multiple datasources in Spring Boot and Mybatis project, following Exception occurs:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type
'org.springframework.transaction.PlatformTransactionManager'
available: expected single matching bean but found 2:
primaryTx,secondTx at
org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1041)
~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345)
~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:384)
~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:272)
~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE] at
com.sun.proxy.$Proxy86.findByDomain(Unknown Source) ~[na:na]
start of project
#SpringBootApplication( exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class
})
#EnableTransactionManagement
public class BookSystemApplication {
}
datasource configuration
#Configuration
public class DataSourceConfig {
#Bean(name = "primaryDataSource")
#ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondDataSource")
#ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
}
}
Transaction
#Configuration
public class TransactionConfig {
#Autowired
#Qualifier("primaryDataSource")
private DataSource primary;
#Autowired
#Qualifier("secondDataSource")
private DataSource second;
#Bean(name="primaryTx")
public PlatformTransactionManager primaryTransaction() {
return new DataSourceTransactionManager(primary);
}
#Bean(name="secondTx")
public PlatformTransactionManager secondTransaction() {
return new DataSourceTransactionManager(second);
}
}
The problem here is that you are defining two beans as datasource and two beans as TransactionManager but you didn't specify which one of them is the primary one, this won't work because Spring needs one datasource bean and one TransactionManager bean to be defined as primary if more than one are defined.
What you should do here is to define, one of your datasources beans and one of your TransactionManager beans as Primary, so that Spring can run correctly, to do so you will need to use #Primary annotation.
#Bean(name = "primaryDataSource")
#Primary
#ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
Please refer to the Spring's Configure two datasources section from the Documentation.
First,thanks √ answer,it solved my problem perfectly.
And I can offer another way of thinking.Unnecessary #Primary to someone,you can define like #Transactional("primaryTx") in your service.
like this:
#Override
#Transactional("primaryTx")
public Test update() {
Test entity = new Test();
entity.setId(1L);
entity.setPhone("19900000050");
return testRepository.save(entity);
}
I am working on a Spring-MVC application in which I have 2 data-sources defined for different types of tasks. Currently, during migration from XML to Java, I stumbled upon a requirement to add a new HibernateTransactionManager object for #Transactional to work. For the 2 different data-sources, I have 2 separate SessionFactory instances. But when I try to create yet another instance of HibernateTransactionManager with secondary data-source, I get a non-unique exception.
Do I require a secondary HibernateTransactionManager instance for the config I am posting or will 1 suffice? If required, how can I create one? Thank you.
Error log :
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: primary_tx,extended_tx
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:368)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:367)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:271)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springfram
WebConfig.java :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.ourapp.spring"})
#EnableTransactionManagement
#EnableCaching
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean(name="primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
#Bean(name="extended_tx")
public HibernateTransactionManager txName() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(getExtendedSessionFactory().getObject());
return txName;
}
#Bean
#Qualifier("sessionFactory_origin")
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig().primaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
#Bean
#Qualifier("sessionFactory_extended")
public LocalSessionFactoryBean getExtendedSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig_Extended().secondaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
}
Typical Service Layer method :
#Service
#Transactional("primary_tx")
public class ChatRoomMembersServiceImpl implements ChatRoomMembersService{
private final ChatRoomMembersDAO chatRoomMembersDAO;
#Autowired
public ChatRoomMembersServiceImpl(ChatRoomMembersDAO chatRoomMembersDAO){
this.chatRoomMembersDAO = chatRoomMembersDAO;
}
}
Typical DAO layer method :
#Repository
#Transactional("primary_tx")
public class ChatRoomMembersDAOImpl implements ChatRoomMembersDAO{
#Autowired
#Qualifier(value = "sessionFactory_origin")
private SessionFactory sessionFactory;
#Autowired
#Qualifier(value = "sessionFactory_extended")
private SessionFactory sessionFactory_extended;
}
Now, whenever required, I am referring to the extended SessionFactory instance. Right now, I have annotated DAO layer methods requiring extended sessionFactory with secondary_tx, but it's not working. Thank you. :-)
The answer to your question
Do I require to define separate TransactionManager for 2 data sources?
is YES
Java8 onwards you can have same annotation(provided that the annotation is marked #Repeatable) multiple times on a method. Other way to accomplish this is to gave a custom annotation which takes care of one of the datasources.
The exception you are getting is because there are two qualifying beans for transation management. You need a Qualifier for this.
Instead of:
#Bean(name="primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
Use
#Bean #Qualifier("primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
Now, Let say You want to make the transactions in my method m1 as atomic. The following is what you need
#Transactional("primary_tx")
public void m1(){
}
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.
I am trying to configure a couple of datasources within Spring Batch. On startup, Spring Batch is throwing the following exception:
To use the default BatchConfigurer the context must contain no more thanone DataSource, found 2
Snippet from Batch Configuration
#Configuration
#EnableBatchProcessing
public class BatchJobConfiguration {
#Primary
#Bean(name = "baseDatasource")
public DataSource dataSource() {
// first datasource definition here
}
#Bean(name = "secondaryDataSource")
public DataSource dataSource2() {
// second datasource definition here
}
...
}
Not sure why I am seeing this exception, because I have seen some xml based configuration for Spring batch that declare multiple datasources. I am using Spring Batch core version 3.0.1.RELEASE with Spring Boot version 1.1.5.RELEASE. Any help would be greatly appreciated.
You must provide your own BatchConfigurer. Spring does not want to make that decision for you
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Bean
BatchConfigurer configurer(#Qualifier("batchDataSource") DataSource dataSource){
return new DefaultBatchConfigurer(dataSource);
}
...
AbstractBatchConfiguration tries to lookup BatchConfigurer in container first, if it is not found then tries to create it itself - this is where IllegalStateException is thrown where there is more than one DataSource bean in container.
The approach to solving the problem is to prevent from creation the DefaultBatchConfigurer bean in AbstractBatchConfiguration.
To do it we hint to create DefaultBatchConfigurer by Spring container using #Component annotation:
The configuration class where #EnableBatchProcessing is placed we can annotate with #ComponentScan that scan the package that contains the empty class that is derived from DefaultBatchConfigurer:
package batch_config;
...
#EnableBatchProcessing
#ComponentScan(basePackageClasses = MyBatchConfigurer.class)
public class MyBatchConfig {
...
}
the full code of that empty derived class is here:
package batch_config.components;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
import org.springframework.stereotype.Component;
#Component
public class MyBatchConfigurer extends DefaultBatchConfigurer {
}
In this configuration the #Primary annotation works for DataSource bean as in the example below:
#Configuration
public class BatchTestDatabaseConfig {
#Bean
#Primary
public DataSource dataSource()
{
return .........;
}
}
This works for the Spring Batch version 3.0.3.RELEASE
The simplest solution to make #Primary annotation on DataSource work might be just adding #ComponentScan(basePackageClasses = DefaultBatchConfigurer.class) along with #EnableBatchProcessing annotation:
#Configuration
#EnableBatchProcessing
#ComponentScan(basePackageClasses = DefaultBatchConfigurer.class)
public class MyBatchConfig {
I would like to provide a solution here, which is very similar to the one answered by #vanarchi, but I managed to put all the necessary configurations into one class.
For the sake of completeness, the solution here assumes that primary datasource is hsql.
#Configuration
#EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer {
#Bean
#Primary
public DataSource batchDataSource() {
// no need shutdown, EmbeddedDatabaseFactoryBean will take care of this
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase embeddedDatabase = builder
.addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql")
.addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql")
.setType(EmbeddedDatabaseType.HSQL) //.H2 or .DERBY
.build();
return embeddedDatabase;
}
#Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource());
factory.setTransactionManager(transactionManager());
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
private ResourcelessTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
//NOTE: the code below is just to provide developer an easy way to access the in-momery hsql datasource, as we configured it to the primary datasource to store batch job related data. Default username : sa, password : ''
#PostConstruct
public void getDbManager(){
DatabaseManagerSwing.main(
new String[] { "--url", "jdbc:hsqldb:mem:testdb", "--user", "sa", "--password", ""});
}
}
THREE key points in this solution:
This class is annotated with #EnableBatchProcessing and #Configuration, as well as extended from DefaultBatchConfigurer. By doing this, we instruct spring-batch to use our customized batch configurer when AbstractBatchConfiguration tries to lookup BatchConfigurer;
Annotate batchDataSource bean as #Primary, which instruct spring-batch to use this datasource as its datasource of storing the 9 job related tables.
Override protected JobRepository createJobRepository() throws Exception method, which makes the jobRepository bean to use the primary datasource, as well as use a different transactionManager instance from the other datasource(s).
The simplest solution is to extend the DefaultBatchConfigurer and autowire your datasource via a qualifier:
#Component
public class MyBatchConfigurer extends DefaultBatchConfigurer {
/**
* Initialize the BatchConfigurer to use the datasource of your choosing
* #param firstDataSource
*/
#Autowired
public MyBatchConfigurer(#Qualifier("firstDataSource") DataSource firstDataSource) {
super(firstDataSource);
}
}
Side Note (as this also deals with the use of multiple data sources): If you use autoconfig to run data initialization scripts, you may notice that it's not initializing on the datasource you'd expect. For that issue, take a look at this: https://github.com/spring-projects/spring-boot/issues/9528
You can define below beans and make sure you application.properties file has entries needed for
#Configuration
#PropertySource("classpath:application.properties")
public class DataSourceConfig {
#Primary
#Bean(name = "abcDataSource")
#ConfigurationProperties(prefix = "abc.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
#Bean(name = "xyzDataSource")
#ConfigurationProperties(prefix = "xyz.datasource")
public DataSource xyzDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
application.properties
abc.datasource.jdbc-url=XXXXX
abc.datasource.username=XXXXX
abc.datasource.password=xxxxx
abc.datasource.driver-class-name=org.postgresql.Driver
...........
...........
...........
...........
Here you can refer: Spring Boot Configure and Use Two DataSources
First, create a custom BatchConfigurer
#Configuration
#Component
public class TwoDataSourcesBatchConfigurer implements BatchConfigurer {
#Autowired
#Qualifier("dataSource1")
DataSource dataSource;
#Override
public JobExplorer getJobExplorer() throws Exception {
...
}
#Override
public JobLauncher getJobLauncher() throws Exception {
...
}
#Override
public JobRepository getJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
// use the autowired data source
factory.setDataSource(dataSource);
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return factory.getObject();
}
#Override
public PlatformTransactionManager getTransactionManager() throws Exception {
...
}
}
Then,
#Configuration
#EnableBatchProcessing
#ComponentScan("package")
public class JobConfig {
// define job, step, ...
}