“Local transaction already has 1 non-XA Resource:

2020-02-26 12:13发布

问题:

After reading previous questions about this error, it seems like all of them conclude that you need to enable XA on all of the data sources. But:

  1. What if I don't want a distributed transaction? What would I do if I want to start transactions on two different databases at the same time, but commit the transaction on one database and roll back the transaction on the other?
  2. I'm wondering how my code actually initiated a distributed transaction. It looks to me like I'm starting completely separate transactions on each of the databases.

Info about the application:

The application is an EJB running on a Sun Java Application Server 9.1

I use something like the following spring context to set up the hibernate session factories:

<bean id="dbADatasource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jdbc/dbA"/>
</bean>

<bean id="dbASessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dbADatasource" />
    <property name="hibernateProperties">
        hibernate.dialect=org.hibernate.dialect.Oracle9Dialect
        hibernate.default_schema=schemaA
    </property>
    <property name="mappingResources">
        [mapping resources...]
    </property>
</bean>

<bean id="dbBDatasource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jdbc/dbB"/>
</bean>

<bean id="dbBSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dbBDatasource" />
    <property name="hibernateProperties">
        hibernate.dialect=org.hibernate.dialect.Oracle9Dialect
        hibernate.default_schema=schemaB
    </property>
    <property name="mappingResources">
        [mapping resources...]
    </property>
</bean>

Both of the JNDI resources are javax.sql.ConnectionPoolDatasoure's. They actually both point to the same connection pool, but we have two different JNDI resources because there's the possibility that the two, completely separate, groups of tables will move to different databases in the future.

Then in code, I do:

sessionA = dbASessionFactory.openSession();
sessionB = dbBSessionFactory.openSession();
sessionA.beginTransaction();
sessionB.beginTransaction();

The sessionB.beginTransaction() line produces the error in the title of this post - sometimes. I ran the app on two different sun application servers. On one runs it fine, the other throws the error. I don't see any difference in how the two servers are configured although they do connect to different, but equivalent databases.

So the question is

  1. Why doesn't the above code start completely independent transactions?
  2. How can I force it to start independent transactions rather than a distributed transaction?
  3. What configuration could cause the difference in behavior between the two application servers?

Thanks.

P.S. the stack trace is:

Local transaction already has 1 non-XA Resource: cannot add more resources. 
at com.sun.enterprise.distributedtx.J2EETransactionManagerOpt.enlistResource(J2EETransactionManagerOpt.java:124) 
at com.sun.enterprise.resource.ResourceManagerImpl.registerResource(ResourceManagerImpl.java:144) 
at com.sun.enterprise.resource.ResourceManagerImpl.enlistResource(ResourceManagerImpl.java:102) 
at com.sun.enterprise.resource.PoolManagerImpl.getResource(PoolManagerImpl.java:216) 
at com.sun.enterprise.connectors.ConnectionManagerImpl.internalGetConnection(ConnectionManagerImpl.java:327) 
at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:189) 
at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:165) 
at com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:158) 
at com.sun.gjc.spi.base.DataSource.getConnection(DataSource.java:108) 
at org.springframework.orm.hibernate3.LocalDataSourceConnectionProvider.getConnection(LocalDataSourceConnectionProvider.java:82) 
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:446) 
at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:167) 
at org.hibernate.jdbc.JDBCContext.connection(JDBCContext.java:142) 
at org.hibernate.transaction.JDBCTransaction.begin(JDBCTransaction.java:85) 
at org.hibernate.impl.SessionImpl.beginTransaction(SessionImpl.java:1354) 
at [application code ...]

回答1:

1 Why doesn't the above code start completely independent transactions?

The app. server manages the transaction for you which can, if necessary, be a distributed transaction. It enlists all the participants automatically. When there's only one participant, you don't notice any difference with a plain JDBC transaction, but if there are more than one, a distributed transaction is really needed, hence the error.

2 How can I force it to start independent transactions rather than a distributed transaction?

You can configure the datasource to be XA or Local. The transactional behavior of Spring/Hibernate can also be configured to use either regular JDBC transactions or delegate the management of transactions to the JTA distributed transaction manager.

I suggest you switch the datasource to non-XA and try to configure Spring/Hibernate to use the JDBC transactions. You should find the relevant information in the documentation, here what I suspect is the line to change:

<bean id="txManager" 
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager" />

This should essentially means that you are not using the app. server distributed transaction manager.

3 What configuration could cause the difference in behavior between the two application servers?

If you have really exactly the same app and configuration, this means that in one case only one participant is enlisted in the dist. transaction, while there are two in the 2nd case. One participant corresponds to one physical connection to a database usually. Could it be that in one case, you use two schema on two different databases, while in the 2nd case you use two schema on the same physical database? A more probable explanation would be that the datasource were configured differently on the two app. server.

PS: If you use JTA distributed transactions, you should use UserTransaction.{begin,commit,rollback} rather than their equivalent on the Session.



回答2:

After reading previous questions about this error, it seems like all of them conclude that you need to enable XA on all of the data sources.

No, not all, all except one (as the exception is saying) if your application server supports Logging Last Resource (LLR) optimization (which allows to enlist one non-XA resource in a global transaction).

Why doesn't the above code start completely independent transactions?

Because you aren't. When using beginTransaction() behind EJB Session Beans, Hibernate will join the JTA transaction (refer to the documentation for full details). So the first call just works but the second call means enlisting another transactional resource in the current transaction. And since none of your resources are XA, you get an exception.

How can I force it to start independent transactions rather than a distributed transaction?

See @ewernli answer.

What configuration could cause the difference in behavior between the two application servers?

No idea. Maybe one of them is using at least one XA datasource.