Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction issues with JDBC calls + AQ [DATAJDBC-9] #266

Closed
spring-projects-issues opened this issue May 11, 2011 · 1 comment
Closed

Transaction issues with JDBC calls + AQ [DATAJDBC-9] #266

spring-projects-issues opened this issue May 11, 2011 · 1 comment
Assignees
Labels
in: core type: bug

Comments

@spring-projects-issues
Copy link

@spring-projects-issues spring-projects-issues commented May 11, 2011

Koen Serneels opened DATAJDBC-9 and commented

=> Spring + Spring Data + Oracle AQ + DataSourceTransactionmanager (no XA)

The included sample uses a DefaultMessageListenerContainer to receive a message from an Oracle AQ.
Inside the message listener a second transaction is started, in that transaction (tx2) a new message is published (to a different queue) and a record is written to database.
Before the message listener ends, it also writes a record to database.
The latter happens in tx1, the tx started by the DefaultMessageListenerContainer when receiving the message.

Everything goes OK, there are no exceptions. However, the end result in the database is that there is only one record inserted instead of two.
It is the second record (from the DefaultMessageListenerContainer tx) that is inserted. The record from tx2 is gone, as it never happened.
The message produced by TX2 did endup on the queue (which makes it even weirder).

After some searching I saw a ROLLBACK being issued on the underlying JDBC connection.
This happens in the AQjmsSession, preClose method.
When the JMS Session (which is an AQjmsSession) is closed, the preclose is also called.
The preclose does a rollback when "!isInGlobalTransactionRechecked()" and "!isExternal()" test returns true:

  • The isInGlobalTransactionRechecked is only true when it detects an WEBLOGIC transaction manager obtained via JNDI (so this will never ever return true when running from a plain SE env).
    There is a JVM property: oracle.jms.useEmulatedXA, but that is TRUE by default (System.err.println(AQjmsConstants.useEmulatedXA())).
    In this case a EmulatedXAHandler is used, and its that handler that does the JNDI check for a WL TX manager (if not, it returns false).

  • The sExternal() can apparantly only be true if you let the AQ infrastructure use a sql.connection instead of a datasource.
    There are no such statics on AQjmsFactory. So maybe it might work if you obtain the JMS ConnectionFactory by pass it a username/password/url ...

It appears that this rollback is called all the time when a AQjmsSession closes.
However, normally a commit is done PRIOR the rollback, so the rollback does actually nothing (I have no idea why they do that, but anyway).
So first I confirmed my case by installing a custom AqJmsFactoryBeanFactory in the app context (instead of using the ns config).
In that custom AqJmsFactoryBeanFactory I changed the CloseSuppressingInvocationHandler, in its proxy invoke method I added:

if (method.getName().equals("rollback")) {
return null;
}

By swallowing the rollback the problem is solved: after that the second record also appeared in the database.

This did not explain why the outer transaction did commit
So I ran it a second time, but I just printed out when a call was made to commit or rollback.
It then looked like this:

Processing message...
start new transaction

  • commit
    message sended
    insert done
    ending new transaction
  • rollback
    insert done
    Done processing message.
  • commit
  • rollback

This explains why the record was never there, as rollback is issues on the end of transaction tx2
Then I did a third test and removed the new tx runner, so that everything runs in the SAME transaction (TX1).
The log then looks likes this:

Processing message...
start new transaction
message sended
insert done
ending new transaction
insert done

  • commit
  • rollback

So in that case the two records where there (without the need of swallowing a commit or rollback).

When using a new transaction, a commit is triggered right after sending the message.
My guess is that this makes a second commit on transaction end (from the tx manager) no longer necesary as the connection is already commited.
The rollback is however always executed when the session is closed (by AQ, not by spring) and that one rolls back my insert.

In case of a single tx, there is no commit called after the message has been sent.
So the commit is called on tx end, and everything gets inserted.
A rollback is then issues as usual on session close, but that does not roll anything back.

I checked why the commit is done in the case of tx2. In AQjmsProducer the method jdbcEnqueue is called when a message is send.
In that method it does a 'forceCommit' when 'isInGlobalTransactionRechecked()' is false OR its session is NOT transacted.

As we learned already, 'isInGlobalTransactionRechecked' returns false.
The transacted comes from the flag if a JMS session should be transacted yes or no.
The Spring JmsTemplate created a NON transacted session in my tx2, thats why the flag is false and a commit is forced.

In the transaction started by the DefaultMessageListenerContainer, the 'transacted' will probably be true.

We might get a step further if we suppress commits and closes on the connection (and run in transacted session when not using XA).
The question is, are still other unforeseen things like this waiting?


Affects: Ext 1.0 M1

Attachments:

@spring-projects-issues
Copy link
Author

@spring-projects-issues spring-projects-issues commented May 13, 2011

Koen Serneels commented

I gave the solution to my own question. This report does NOT seem to be a bug.
The real issue is that the JMS session I was using inside the transaction was NOT transactional.
Since we are comming from a JTA scenario, I forgot to set 'setSessionTransacted' to true on my JmsTemplate.
You can verify this in my attached sample, if you add "sender.setSessionTransacted(true)" you will see everything gets commited.

If the JmsSession is not transactional (and no JTA is running) a commit is forced inside the AQ machinery.
This is of course correct behaviour.
When a commit is already executed, the transaction manager does not bother to a second commit.
On close of the session (by the transaction manager) the oracle machinery issues a rollback as described.
I still don't know why though, it doesn't hurt, but it feels weird

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core type: bug
Projects
None yet
Development

No branches or pull requests

2 participants