Combination Resin/Spring/Oracle/XA Transactions works perfectly. The key thing here is to work correctly with DB connections.
All DB connections must be opened INSIDE your transaction. In this case this resource will be enlisted for the transaction implicitly. Our example code does so.
The problem was that we left db connection OPEN when started NEW transaction. This means that db connection provided by Resin's DB pool (aka UserConnection) hadn't been returned to the pool before beginning new transaction. For some reason Resin TransactionManager implementation added UNreturned db connection to the list of resources used in the inner transaction implicitly. And commit of inner transaction failed because Oracle knows that db connection has already been used in outer uncommitted yet transaction.
One can safely return db connection to the pool. After execution of inner transaction (where all uncommitted changes are not seen because resin will provide you new db connection inside new transaction) you may ask again for db connection in outer transaction and this db connection will be exactly same as the one before starting inner transaction, so you will see all your uncommitted changes. So from resin's connections pool will be provided connection associated with current UserTransaction.
One more important thing - if you use Spring to get connection from data source (DataSourceUtils) you MUST use DataSourceUtils to release connection before starting inner transactions to let Spring know that connection has been returned. Actually another advantage of Spring is that you can have your own additional TransactionSynchronization logic. You may also use data source directly (without Spring) and in this case you can call connection.close() to return it to the connections pool.
Finally the code of outer transaction (any transaction) should look like:
public void doOuterTransaction() throws Throwable {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("myTx");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES);
TransactionStatus status = txManager.getTransaction(def);
Connection connection = null;
Cache cache = null;
try {
try {
cache = ...; // get cache from CacheManager
connection = DataSourceUtils.getConnection(myDataSource);
// some business logic
} finally {
DataSourceUtils.releaseConnection(connection, myDataSource);
}
doInnerTransaction();
try {
cache = ...; // get cache from CacheManager
connection = DataSourceUtils.getConnection(myDataSource);
// some other business logic
} finally {
DataSourceUtils.releaseConnection(connection, myDataSource);
}
} catch (Throwable ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
}
So do not be afraid of getting and releasing of db connection. You have connections pool - it is fast. Calling close() on connection only returns it to the pool (method name "release" is much better!). Having active UserTransaction will NOT commit your changes until you call commit() explicitly.
Thanks everybody for your help!