Is there a way to define a default transaction manager in Spring - spring

I have an existing application that uses the Hibernate SessionFactory for one database. We are adding another database for doing analytics. The transactions will never cross so I don't need JTA, but I do want to use JPA EntityManager for the new database.
I've set up the EntityManager and the new transaction manager, which I've qualified, but Spring complains that I need to qualify my existing #Transactional annotations. I'm trying to find a way to tell Spring to use the txManager one as the default. Is there any way of doing this? Otherwise I'll have to add the qualifier to all the existing #Transactional annotations which I would like to avoid if possible.
#Bean(name = "jpaTx")
public PlatformTransactionManager transactionManagerJPA() throws NamingException {
JpaTransactionManager txManager = new JpaTransactionManager(entityManagerFactory());
return txManager;
}
#Bean
public PlatformTransactionManager txManager() throws Exception {
HibernateTransactionManager txManager = new HibernateTransactionManager(sessionFactory());
txManager.setNestedTransactionAllowed(true);
return txManager;
}
Error I'm getting
No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2:
Thanks

I was able to solve this using the #Primary annotation
#Bean(name = "jpaTx")
public PlatformTransactionManager transactionManagerJPA() throws NamingException {
JpaTransactionManager txManager = new JpaTransactionManager(entityManagerFactory());
return txManager;
}
#Bean
#Primary
public PlatformTransactionManager txManager() throws Exception {
HibernateTransactionManager txManager = new HibernateTransactionManager(sessionFactory());
txManager.setNestedTransactionAllowed(true);
return txManager;
}

Since the same type of bean is produced from two methods, you must qualify the #Transactional annotation with the named bean. An easy way around to suite your need will be to use two different Spring application contexts. One operating with the old data source and one operating with new. Each of these contexts will have only one method producing the PlatformTransactionManager instance.

Related

How does a bean, with #Autowired, gets its dependency if the dependency's interface does not exist in the container

I am learning Spring framework and I am stuck now about dependency injection.
In this question, I am asking about Java Configuration.
I know #Autowired annotation automatically wires the dependency and the bean by referencing a qualified implementation class of the declared interface.
However, I do not know how the function, transactionManager(SessionFactory sessionFactory), gets its SessionFactory argument instance when there is no bean returns SessionFactory in my studying code.
#Bean
public LocalSessionFactoryBean sessionFactory(){
// create session factorys
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
// set the properties
sessionFactory.setDataSource(securityDataSource());
sessionFactory.setPackagesToScan(env.getProperty("hiberante.packagesToScan"));
sessionFactory.setHibernateProperties(getHibernateProperties());
return sessionFactory;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
// setup transaction manager based on session factory
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
I assume that the transactionManager() gets its argument from sessionFactory(). But sessionFactory() does not return SessionFactory instance, it returns LocalSessionFactoryBean. The LocalSessionFactoryBean does not implement SessionFactory either, it implements FactoryBean<SessionFactory>.
In this case, doesn't transactionManager() suppose to use argument type LocalSessionFactoryBean or FactoryBean<SessionFactory> instead of having SessionFactory? Like: transactionManager(FactoryBean<SessionFactory> sessionFactory)
I am confused with how transactionManager() gets its dependency.
Thank you,
If am understanding it right you are telling spring to create session factory for you using the configurations provided via LocalSessionFactoryBean, see details here, it creates hibernate session factory for you and auto wire it. Actually its Spring frameworks common practice to save developer work here by letting you create session factory with minimum code.

Need to configure my JPA layer to use a TransactionManager (Spring Cloud Task + Batch register a PlatformTransactionManager unexpectedly)

I am using Spring Cloud Task + Batch in a project.
I plan to use different datasources for business data and Spring audit data on the task. So I configured something like:
#Bean
public TaskConfigurer taskConfigurer() {
return new DefaultTaskConfigurer(this.singletonNotExposedSpringDatasource());
}
#Bean
public BatchConfigurer batchConfigurer() {
return new DefaultBatchConfigurer(this.singletonNotExposedSpringDatasource());
}
whereas main datasource is autoconfigured through JpaBaseConfiguration.
The problem comes when SimpleBatchConfiguration+DefaultBatchConfigurer expose a PlatformTransactionManager bean, since JpaBaseConfiguration has a #ConditionalOnMissingBean on PlatformTransactionManager. Therefore Batch's PlatformTransactionManager, binded to the spring.datasource takes place.
So far, this seems to be caused because this bug
So I tried to emulate what JpaBaseConfiguration does, defining my own PlatformTransactionManager over my biz datasource/entityManager.
#Primary
#Bean
public PlatformTransactionManager appTransactionManager(final LocalContainerEntityManagerFactoryBean appEntityManager) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(appEntityManager.getObject());
this.appTransactionManager = transactionManager;
return transactionManager;
}
Note I have to define it with a name other than transactionManager, otherwise Spring finds 2 beans and complains (unregardless of #Primary!)
But now it comes the funny part. When running the tests, everything runs smooth, tests finish and DDLs are properly created for both business and Batch/Task's databases, database reads work flawlessly, but business data is not persisted in my testing database, so final assertThats fail when counting. If I #Autowire in my test PlatformTransactionManager or ÈntityManager, everything indicates they are the proper ones. But if I debug within entityRepository.save, and execute org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus(), it seems the DatasourceTransactionManager from Batch's configuration is overriding, so my custom exposed PlatformTransactionManager is not being used.
So I guess it is not a problem of my PlatformManager being the primary, but that something is configuring my JPA layer TransactionInterceptor to use the non primary but transactionManager named bean of Batch.
I also tried with making my #Configuration implement TransactionManagementConfigurer and override PlatformTransactionManager annotationDrivenTransactionManager() but still no luck
Thus, I guess what I am asking is whether there is a way to configure the primary TransactionManager for the JPA Layer.
The problem comes when SimpleBatchConfiguration+DefaultBatchConfigurer expose a PlatformTransactionManager bean,
As you mentioned, this is indeed what was reported in BATCH-2788. The solution we are exploring is to expose the transaction manager bean only if Spring Batch creates it.
In the meantime you can set the property spring.main.allow-bean-definition-overriding=true to allow bean definition overriding and set the transaction manager you want Spring Batch to use with BatchConfigurer#getTransactionManager. In your case, it would be something like:
#Bean
public BatchConfigurer batchConfigurer() {
return new DefaultBatchConfigurer(this.singletonNotExposedSpringDatasource()) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new MyTransactionManager();
}
};
}
Hope this helps.

Using ObjectDB with Spring Data JPA "com.objectdb.jpa.EMF is not an interface"

Overflowers
I'm trying to get ObjectDB (2.7.6_01, latest) with Spring Data JPA (2.1.4, seemingly latest).
The docs for Spring Data JPA say that a version 2.1 JPA provider is needed. AFAIKT ObjectDB's JPA provider is 2.0 ... not sure if this is the problem or not.
But my problem is this exception:
Caused by: java.lang.IllegalArgumentException: com.objectdb.jpa.EMF is not an interface
Which is causing:
EntityManagerFactory interface [class com.objectdb.jpa.EMF] seems to conflict with Spring's EntityManagerFactoryInfo mixin - consider resetting the 'entityManagerFactoryInterface' property to plain [javax.persistence.EntityManagerFactory]
I'm happy that the ObjectDB entity manager factory is being picked properly by my code, but Spring's CGLIB wrapper around this class (EMF) is not working out.
Anyone got any ideas?
Gradle dependencies:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compile files('libs/objectdb-jee.jar')
compileOnly 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Then, either of these two #Beans (one or the other, not both) cause the same EMF exception above:
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
final ObjectdbJpaVendorAdapter vendorAdapter = new ObjectdbJpaVendorAdapter();
return vendorAdapter;
}
Or
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
final ObjectdbJpaVendorAdapter vendorAdapter = new ObjectdbJpaVendorAdapter();
vendorAdapter.setShowSql(true);
vendorAdapter.setGenerateDdl(false);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.example.demo.persistence");
factory.setDataSource(dataSource());
factory.afterPropertiesSet();
return factory;
}
I've got a no-op DataSource #Bean to keep some facet of Spring happy, but I don't think it's playing an active role in this problem.
No spring.jpa.* set at all.
Cheers
The Beans that you have to provide are much more simple:
#Bean
#ConfigurationProperties("app.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name="entityManagerFactory")
public EntityManagerFactory getEntityManagerFactoryBean() {
// this is the important part - here we use a local objectdb file
// but you can provide connection string to a remote objectdb server
// in the same way you create objectdb EntityManagerFactory not in Spring
return Persistence.createEntityManagerFactory("spring-data-jpa-test.odb");
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(emf);
return txManager;
}
With the above (and the proper dependencies in your pom.xml) there is no need for any additional configuration (i.e. no need for any configuration in application.properties).
A simple working example can be found here.

Error: java.lang.IllegalArgumentException: Property 'transactionManager' is required even after it is declared

I have already declared the transactionManager in my Config file as below.
#Bean
public HibernateTransactionManager transactionManager() {
return new HibernateTransactionManager(sessionFactory().getObject());
}
Even after the declaration, I see the error that Property 'transactionManager' is not found.
Trying to configure hibernate using Spring and Hibernate Contextual sessions.
Configured SessionFactory using LocalSessionFactoryBean.
Try to use interface instead of concrete class
#Bean
public TransactionManager transactionManager() {
return new HibernateTransactionManager(sessionFactory().getObject());
}

InvalidDataAccessApiUsageException: Executing an update/delete query Spring XML to Java config

I'm trying to convert spring xml configuration to java configuration. This works perfectly through XML configuration. However, it throws the following exception if I use java config initializer. This happens when it tries to run JQL. The application starts properly though (with all JPA mapping initialized).
org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:410) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:216) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslat
Following is my persistence initializer class. Bit of reading suggested me this is related to transactions are not being started properly. I've put debug points to each of these methods but transactionManager method never gets executed during server startup or any later time. I'm not sure what am I doing wrong :(. Same code based works perfectly when persistence is initialized through persistence.xml.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "au.mypkg")
public class DatabaseConfig {
#Bean(name = "dataSource")
#Primary
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:jboss/datasources/mydb");
}
#PersistenceContext(unitName = "persistenceUnit")
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws Exception {
..........
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
final JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setTransactionManagerName(JBOSS_TRANSACTION_MANANGER);
return transactionManager;
}
Error occurs when accessing this method on Dao
public void updateById(final Long id) {
final String sqlQuery = "UPDATE testtable w SET w.LAST_ACCESSED = :date WHERE w.testtable_ID = :testid";
final Query query = dao.createNativeQuery(sqlQuery);
query.setParameter("date", new Date());
query.setParameter("testid", id);
query.executeUpdate();
}
I had the some problem and I resolved it by just adding #Transactional annotation on the service method that perform delete or update.
In my case it was a method that call a repository method which execute a delete by jpql like this by I think it can solve you problem too:
#Modifying
#Query("delete from OtherPayment otherPayment " +
"where otherPayment.otherPaymentParam.id = :otherPaymentParamId")
void deleteByOtherPaymentParamId(#Param("otherPaymentParamId") Long otherPaymentParamId);
Finally figured out what was going on with it. The reason this wasn't hitting the debug point was, EnableTransactionManagement imposes to auto-configure transactions for you. So if your transaction manager configured with default name which is in my case, it wouldn't try to call my method to configure transactions. The only way to get around with this is to use a different name for your transaction manager and pass that ref as a parameter on enableJPARepositories annotation.If you use one of the default names, it wouldn't make this call.
#Primary
#Bean(name = "myRealTransactionManager")
public PlatformTransactionManager transactionManager() {
final JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setTransactionManagerName(JBOSS_TRANSACTION_MANANGER);
return transactionManager;
}
.. and then
#EnableJpaRepositories(basePackages = "au.mypkg", transactionManagerRef = "myRealTransactionManager"
The other problem was I have had used setdatasource as opposed to setJtaDataSource on LocalContainerEntityManagerFatoryBean.

Resources