-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
Ravi Sanwal opened SPR-11791 and commented
This is a re-open of #15455.
Some work was done around this. We initially thought that the fix works. But seems like it doesn't.
Here is a summary of the fix:
catch (javax.jms.IllegalStateException ex) {
if (this.connectionFactory != null) {
try {
Method getDataSourceMethod = this.connectionFactory.getClass().getMethod("getDataSource");
Object ds = ReflectionUtils.invokeMethod(getDataSourceMethod, this.connectionFactory);
if (ds != null && TransactionSynchronizationManager.hasResource(ds)) {
// IllegalStateException from sharing the underlying JDBC Connection
// which typically gets committed first, e.g. with Oracle AQ --> ignore
return;
}
}
catch (Throwable ex2) {
if (logger.isDebugEnabled()) {
logger.debug("No working getDataSource method found on ConnectionFactory: " + ex2);
}
// No working getDataSource method - cannot perform DataSource transaction check
}
}
throw ex;
}
}
This is for ignoring the IllegalArgumentException
The problem with this fix is that the datasource object returned by the reflective invocation of getDataSource on the connection factory (which is oracle.jms.AQjmsQueueConnectionFactory) returns org.springframework.data.jdbc.config.oracle.AqJmsFactoryBeanFactory$TransactionAwareDataSource, which is a transactionally wrapped proxy (to use transactional datasource resource and also to suppress "close" etc on the connection returned) around the actual datasource. So TransactionSynchronizationManager.hasResource(ds) returns false.
I don't know what is an easy and clean fix to this.
But there is one way to deal with the problem.
org.springframework.data.jdbc.config.oracle.AqJmsFactoryBeanFactory$TransactionAwareDataSource is a org.springframework.jdbc.datasource.DelegatingDataSource which gives access to the actual target datasource.
So an instance check can be done and if matches, the target datasource can be retrieved.
By adding the following before checking the transaction synchronization manager for the datasource resource:
if(DelegatingDataSource.class.isInstance(ds)) {
ds = ((DelegatingDataSource)ds).getTargetDataSource();
}
The catch block would then look like:
catch (javax.jms.IllegalStateException ex) {
if (this.connectionFactory != null) {
try {
Method getDataSourceMethod = this.connectionFactory.getClass().getMethod("getDataSource");
Object ds = ReflectionUtils.invokeMethod(getDataSourceMethod, this.connectionFactory);
if(DelegatingDataSource.class.isInstance(ds)) {
ds = ((DelegatingDataSource)ds).getTargetDataSource();
}
if (ds != null && TransactionSynchronizationManager.hasResource(ds)) {
// IllegalStateException from sharing the underlying JDBC Connection
// which typically gets committed first, e.g. with Oracle AQ --> ignore
return;
}
}
catch (Throwable ex2) {
if (logger.isDebugEnabled()) {
logger.debug("No working getDataSource method found on ConnectionFactory: " + ex2);
}
// No working getDataSource method - cannot perform DataSource transaction check
}
}
throw ex;
}
}
Basically make the transaction synchronization check on the target if needed.
Affects: 3.2.5, 3.2.8, 4.0.1, 4.0.4
Issue Links:
- spring-jms 3.0.4 introduces a change that breaks using JMSTemplate (and for that matter any JMS resource) with Oracle AQ when JMS Session are used in SESSION_TRANSACTED mode. [SPR-10829] #15455 spring-jms 3.0.4 introduces a change that breaks using JMSTemplate (and for that matter any JMS resource) with Oracle AQ when JMS Session are used in SESSION_TRANSACTED mode.
Backported to: 3.2.9