3

I'm working on an web application based on spring (spring-boot 1.5.13.RELEASE) connect to a MySQL 8 database. We choose to work with tomcat jdbc pool (the default pool for spring-boot). Production department ask us to work on a solution to avoid incomplete transaction using rollback-on-return property (we've set auto-commit=false too).

We can't just use annotation oriented transaction. We have to manage some of them programmatically. So we thought that the use of rollback-on-return will avoid "pending" transaction. Unfortunately, during test phase, we discover that if a request create a "pending" transaction, the end of the request is not going with the return of the connection. And thus, there is no rollback!

I already test if the connection is closed on a normal case, and so it is.

Here is my example code:

public CommandLineRunner commandLineRunner(CustomerManager customerManager, EngineManager engineManager, DataSource dataSource) {
    return (String... args) -> {
        log.info("Start commandLineRunner" + engineManager.getTransactionPartEngineInnodbStatus());

        Thread t = new Thread(() -> {
            try {
                customerManager.processII_notcomplete();
            } catch (Exception e) {
                System.err.println(e.getMessage());
            }
        });

        t.start();
        t.join();

        log.info("End commandLineRunner" + engineManager.getTransactionPartEngineInnodbStatus());
    };
}

public void processII_notcomplete() {
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED));

    log.info("processII [START]");

    Customer customer = customerRepository.findOne(1L);
    customer.lastName = "De " + customer.lastName;
    customerRepository.save(customer);
    log.info("processII: customerRepository.save(customer)" + engineManager.getTransactionPartEngineInnodbStatus() + "\n");

    // Intentionally don't commit the transaction to create the use case

    log.info("processII [END]");
}

I've found a JdbcInterceptor to log the borrow and the return of the connection:

public class JDBCLogInterceptor extends JdbcInterceptor {

    private static final Logger log = LoggerFactory.getLogger(JDBCLogInterceptor.class);

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (log.isDebugEnabled()) {
            String name = method.getName();
            if (CLOSE_VAL.equals(name)) {
                log.debug(String.format("Returning Connection to Pool [%s]", proxy));
            }
        }
        return super.invoke(proxy, method, args);
    }

    @Override
    public void reset(ConnectionPool connPool, PooledConnection conn) {
        if (connPool != null && conn != null) {
            if (log.isDebugEnabled()) {
                log.debug(String.format("Getting Connection [%s], Pool active=[%s], idle=[%s]", conn.toString(),
                        connPool.getActive(), connPool.getIdle()));
            }
        }
    }

    @Override
    public void disconnected(ConnectionPool connPool, PooledConnection conn, boolean finalizing) {
        if (connPool != null && conn != null) {
            if (log.isDebugEnabled()) {
                log.debug(String.format("Closing Connection [%s], Pool active=[%s], idle=[%s]", conn.toString(),
                        connPool.getActive(), connPool.getIdle()));
            }
        }
    }
}

And here is the pertinent log part:

ERROR 2019/01/29 16:28:13.975 - no-request --- mova.jpatest.JPATestConfig               - Start commandLineRunner
------------
TRANSACTIONS
------------
Trx id counter 269691
Purge done for trx's n:o < 269691 undo n:o < 0 state: running but idle
History list length 7
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 283703119152960, not started
0 lock struct(s), heap size 1136, 0 row lock(s) - [no-one]
DEBUG 2019/01/29 16:28:13.985 - no-request --- mova.jpatest.jdbc.JDBCLogInterceptor     - Getting Connection [PooledConnection[com.p6spy.engine.wrapper.ConnectionWrapper@63bf9fce]], Pool active=[1], idle=[9] - [no-one]
 INFO 2019/01/29 16:28:13.989 - no-request --- mova.jpatest.manager.CustomerManager     - processII [START] - [no-one]
 INFO 2019/01/29 16:28:13.995 - no-request --- p6spy                                    - statement --- select customer0_.id as id1_0_0_, customer0_.first_name as first_na2_0_0_, customer0_.last_name as last_nam3_0_0_ from customer customer0_ where customer0_.id=? - select customer0_.id as id1_0_0_, customer0_.first_name as first_na2_0_0_, customer0_.last_name as last_nam3_0_0_ from customer customer0_ where customer0_.id=1 - 01-29-19 16:28:13:995-2 [connection 8] - [no-one]
 INFO 2019/01/29 16:28:14.029 - no-request --- p6spy                                    - statement --- update customer set first_name=?, last_name=? where id=? - update customer set first_name='Jack', last_name='De De De De De De De Bauer' where id=1 - 01-29-19 16:28:14:29-0 [connection 8] - [no-one]
 INFO 2019/01/29 16:28:14.039 - no-request --- p6spy                                    - statement --- show engine innodb status; - show engine innodb status; - 01-29-19 16:28:14:39-6 [connection 8] - [no-one]
 INFO 2019/01/29 16:28:14.039 - no-request --- mova.jpatest.manager.CustomerManager     - processII: customerRepository.save(customer)
------------
TRANSACTIONS
------------
Trx id counter 269692
Purge done for trx's n:o < 269691 undo n:o < 0 state: running but idle
History list length 7
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 269691, ACTIVE 0 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 624, OS thread handle 20236, query id 47274 localhost 127.0.0.1 mohicane starting
show engine innodb status
Trx read view will not see trx with id >= 269691, sees < 269691
 - [no-one]
 INFO 2019/01/29 16:28:14.039 - no-request --- mova.jpatest.manager.CustomerManager     - processII [END] - [no-one]
DEBUG 2019/01/29 16:28:14.039 - no-request --- mova.jpatest.jdbc.JDBCLogInterceptor     - Getting Connection [PooledConnection[com.p6spy.engine.wrapper.ConnectionWrapper@3d646e1a]], Pool active=[2], idle=[8] - [no-one]
 INFO 2019/01/29 16:28:14.045 - no-request --- p6spy                                    - statement --- show engine innodb status; - show engine innodb status; - 01-29-19 16:28:14:45-2 [connection 9] - [no-one]
DEBUG 2019/01/29 16:28:14.045 - no-request --- mova.jpatest.jdbc.JDBCLogInterceptor     - Returning Connection to Pool [ProxyConnection[PooledConnection[com.p6spy.engine.wrapper.ConnectionWrapper@3d646e1a]]] - [no-one]
ERROR 2019/01/29 16:28:14.045 - no-request --- mova.jpatest.JPATestConfig               - End commandLineRunner
------------
TRANSACTIONS
------------
Trx id counter 269692
Purge done for trx's n:o < 269691 undo n:o < 0 state: running but idle
History list length 7
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 283703119153840, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 269691, ACTIVE 0 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 624, OS thread handle 20236, query id 47274 localhost 127.0.0.1 mohicane
Trx read view will not see trx with id >= 269691, sees < 269691 - [no-one]

My questions are:

  • Is there a way to make sure that pending transaction always be complete like we've expected with rollback-on-return?
  • Can we detect and return "unreturned" connection in this case?
  • It seems strange that such property exists and not managed in this case? Why would we use a rollback after a transaction we've already commit or rollback? Is there something I don't understand (sure it is :p ) ?

NB: Plese don't mind the ERROR log level, I'm a little color blind.

Edit: I try through an interceptor to detect current active Transaction using TransactionSynchronizationManager.isActualTransactionActive(). This way, I find the unfinished ones, but when I try to get it with TransactionAspectSupport.currentTransactionStatus() it return me null??? Can't understand...

Simplification

Is there a way to detect and rollback a forgotten programmatic transaction (as shown in the code below)?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Mohicane
  • 302
  • 2
  • 15
  • 1
    From https://github.com/brettwooldridge/HikariCP/wiki/%22My-benchmark-doesn't-show-a-difference.%22 : "Rollback-on-return Without rollback-on-return capability, a pool will allow transactions to "leak" across threads, potentially causing inconsistent DB state and nearly impossible to locate bugs. DBCP2 enables it by default. Tomcat can optionally enable it. ⚠️ Unfortunately, neither tracks transaction state, and therefore unconditionally rollback, which generates megabytes of performance killing unnecessary traffic to the database when enabled." – Boris Pavlović Jan 29 '19 at 15:22
  • @BorisPavlović Ok but why the connection is never return to the pool? No matter the property is true or false, if there is no return of the connection, is this not a problem? – Mohicane Feb 01 '19 at 09:13
  • 1
    It's not really clear how you're managing transactions. You get one, which starts a txn, but don't commit or rollback. Spring's docs https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html are useful, and the section on programmatic txn mgmt may help https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html#transaction-programmatic – Taylor Feb 12 '19 at 15:09
  • Yes it's the use case. We start transaction programmatically and "forget" to commit/rollback the transaction (I add a comment). Then Production department want to know if there is a way to detect this "open" transaction and rollback it. I've already read these documentations thanks. – Mohicane Feb 12 '19 at 16:10

0 Answers0