EasterBunnyBugSmasher
EasterBunnyBugSmasher

Reputation: 1482

2 local transactions (on different Datasources) in each other

I'm facing a problem with transactions. I have a method that needs to read data from one datasource, write it to another and then commit to the first datasource that the data have been read. (the data in the first datasource is in a queue and I need to commit to remove them from the queue).

I wouldn't worry about this if we had global JTA transactions, but we don't. Due to architectural reasons we can only use local transactions. (I'm not the architect)

Here is what I did (simplified):

@Transactional(value="tx1", propagation=REQUIRES_NEW)
public void copy() {
  try {
    Data data = read();
    service.write(data);
  } catch (Exception x) {...}
}

Service.java:

@Transactional(value="tx2", propagation=REQUIRES_NEW)
public void write(Data data) {
  ...
}

I know that I have a problem when the commit on the queue fails because I have already written the data. But that is not a problem. And not part of this question.

The problem is, I get following error:

2014.08.21 12:29:54 INFO : 21.08.2014 12:29:54 org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! JpaTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single JpaTransactionManager for all transactions on a single DataSource, no matter whether JPA or JDBC access.
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:311)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
    at sun.reflect.GeneratedMethodAccessor50.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    at org.springframework.osgi.service.importer.support.internal.aop.ServiceInvoker.doInvoke(ServiceInvoker.java:58)
    at org.springframework.osgi.service.importer.support.internal.aop.ServiceInvoker.invoke(ServiceInvoker.java:62)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:131)
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionIntercept [StdOut]
2014.08.21 12:29:54 INFO : or.java:119)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.osgi.service.util.internal.aop.ServiceTCCLInterceptor.invokeUnprivileged(ServiceTCCLInterceptor.java:56)
    at org.springframework.osgi.service.util.internal.aop.ServiceTCCLInterceptor.invoke(ServiceTCCLInterceptor.java:39)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.osgi.service.importer.support.LocalBundleContextAdvice.invoke(LocalBundleContextAdvice.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:131)
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:119)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at com.sun.proxy.$Proxy797.getTransaction(Unknown Source)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
    at org.springframewo [StdOut]
2014.08.21 12:29:54 INFO : rk.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at com.sun.proxy.$Proxy655.write(Unknown Source)

am I doing something that should not be done? Anyone have a good suggestion on what I should do differently?

Upvotes: 1

Views: 1843

Answers (1)

Serge Ballesta
Serge Ballesta

Reputation: 149185

This is only clues (nothing tested), but is much too long to fit in a comment.

You should first make sure that the error is really caused by the cascading of 2 @Transactional annotated methods by first making them independant (see my above comment). For the following of the post, I assume it is true.

You could try to use explicit programmatic transaction management, hoping that the less magic you ask form Spring, the less side effects you will get.

// Beans to be injected
PlatformTransactionManager tx1;
PlatformTransactionManager tx2;
TransactionDefinition td1 = new ;
TransactionDefinition td2;

SessionFactory sf1;
SessionFactory sf2;

// method using explicit transactions
public void copy(Serializable id) throws Exception {
    TransactionStatus status1 = tx1.getTransaction(td1);
    try {
        Session session1 = sf1.getCurrentSession();
        Object data = session1.get("Entity", id);
        TransactionStatus status2 = tx2.getTransaction(td1);
        try {
            Session session2 = sf2.getCurrentSession();
            session2.saveOrUpdate(data);
        }
        catch (Exception e2) {
            tx2.rollback(status2);
            throw e2;
        }
        tx2.commit(status2);
    }
    catch (Exception e1) {
        tx1.rollback(status1);
        throw e1;
    }
    tx1.commit(status1);
}

But because of this sentence in HibernateTransactionManager javadoc : JTA (usually through JtaTransactionManager) is necessary for accessing multiple transactional resources within the same transaction. The DataSource that Hibernate uses needs to be JTA-enabled in such a scenario (see container setup), I am not sure whether it can works outside JTA.

Another solution : According to those 2 posts Distributed transactions in Spring, with and without XA and Implementing Spring ChainedTransactionManager according to the “best efforts 1PC” pattern, you could also use the ChainedTransactionManager from spring-data-common.

Extract from first post :

Configuration of the ChainedTransactionManager

<bean id="transactionManager" class="com.springsource.open.db.ChainedTransactionManager">
  <property name="transactionManagers">
    <list>
      <bean
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
      </bean>
      <bean
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="otherDataSource" />
      </bean>
    </list>
  </property>
</bean>

... Remember that the order of the resources is significant. They are nested, and the commit or rollback happens in reverse order to the order they are enlisted (which is the order in the configuration)

But I am still unsure whether it can work outside JTA ...

And if none of this clues is successful, you will have to ask again your system architect whether JTA could not be an option.

Upvotes: 1

Related Questions