How to share a transaction between datasources when using AbstractRoutingDataSource to switch the active data source?
So far, without the transaction the queries get executed on both databases correctly, but when I start a transaction, everything executes on the same database (i.e. I cannot switch to the second database anymore).
Any ideas?
@Transactional
public void crossDbTransactionTest() {
// Selects a datasource from my pool of AbstractRoutingDataSources
DbConnectionContextHolder.setDbConnectionByYear(2012);
// execute something in the first database
this.executeSomeJpaQuery("xyz");
// switch to the second database
DbConnectionContextHolder.setDbConnectionByYear(2011);
// execute something in the second database
this.executeSomeJpaQuery("xyz"); // on any errors rollback changes in both databases
}
EDIT1 (added configuration files):
persistence.xml:
<persistence-unit name="primarnaKonekcija" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" />
<property name="hibernate.max_fetch_depth" value="1" />
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.JBossTransactionManagerLookup" />
</properties>
</persistence-unit>
spring-jpa.xml:
<!-- Shared DB credentials -->
<context:property-placeholder location="classpath:config.properties" />
<!-- DB connections by year -->
<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" abstract="true">
<property name="driverClassName" value="${db.driver}" />
<property name="username" value="${db.user}" />
<property name="password" value="${db.password}" />
</bean>
<bean id="dataSource" class="myPackage.DbConnectionRoutingDataSource">
<!-- Placeholder that is replaced in BeanFactoryPostProcessor -->
<property name="targetDataSources">
<map key-type="int">
<entry key="0" value-ref="placeholderDs" />
</map>
</property>
<property name="defaultTargetDataSource" ref="placeholderDs" />
</bean>
<!-- EntityManager configuration -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="primarnaKonekcija" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.SQLServerDialect" />
<property name="showSql" value="true" />
</bean>
</property>
</bean>
<tx:annotation-driven />
<tx:jta-transaction-manager />
EDIT 2:
Tried switching everything to JTA and JNDI provided datasources.
Changing transaction-type=”RESOURCE_LOCAL” to transaction-type=”JTA” didn’t work either – JtaStatusHelper throws an NullPointerException, saying that the transactionManager is null.
EDIT 3:
Added JBossTransactionManagerLookup to persistence.xml, now I get the “Adding multiple last resources is disallowed” when switching to second datasource within the transaction.
EDIT 4:
Tried setting JBOSS so I get past that error – database switching works now with the expected warning: “Multiple last resources have been added to the current transaction. This is transactionally unsafe and should not be relied upon.”.
Gonna try to configure MSSQL XA driver in JBOSS next.
EDIT 5:
After configuring MSSQL XA, everything works as intended, will post an answer with steps needed to set this up.
This is a solution that I wouldn’t recommend unless you’re having no other choice. Level 2 cache just can’t possibly work with a solution such as this, but it’s a (stable) solution that I was forced to use to buy time until the underlying legacy databases are merged into one.
First in JBoss standalone.xml set up your database connections as XA datasources. If using MS SQL server, follow the instructions how to properly set up XA at http://msdn.microsoft.com/en-us/library/aa342335.aspx
standalone.xml
Then I set up my entityManager bean that uses my implementation of the AbstractRoutingDataSource as its dataSource.
This is the Spring powered JPA setup without using the persistence.xml file; as far as I know, this is the only the way to get automatic package scanning of entities when using JBoss 7.
springJpaConfig.xml
I use ExampleDS as the default in AbstractRoutingDataSourceMyDB because you have to provide a defaultTargetDataSource, but I always want to select a valid DB manually, hence if anyone tries to access the DB without first manually selecting the connection they will try to execute their query on the non existant ExampleDS database which will throw an exception (very hacky, but it gets the job done).
In the BeanFactoryPostProcessor I now need to fill in the list of my datasources:
DatasourceRegisteringBeanFactoryPostProcessor.java
This is my implementation of AbstractRoutingDataSource which allows me to switch connections at runtime:
AbstractRoutingDataSourceMyDB.java
Beware that you have to call entitymanager.flush() and clear() before you change the current connection from within your DAO classes or all pending operations in that transaction’s scope will get executed on the new connection on transaction commit. This is because Hibernate session is oblivious that the connection ever changed, as far as it knows – it’s always the same database.
So in your DAO you can do this now:
SomeTableDAO.java
So there you go, while it’s not pretty – it can be done 🙂