TX その25 完成

まあシリーズ化して25話まで続けてきたJBossTXネタですが,ここらで御しまい.作ろうとしていたものができあがったから.

できあがったものをちょっとだけ公開.例えばJBossからXAを使ってMySQLへ2本コネクションを張っていたとする.Commitする直前で片方のコネクションで障害が発生した場合,Commit呼び出しであったとしても全体としてはRollbackされないといけない.例えばMySQLのGeneral Logで言うならば,以下のようなログにならないといけない.

69 Query       XA START 0x6d697234322f35,0x31,0x101
70 Query       XA START 0x6d697234322f35,0x32,0x101
69 Query       INSERT INTO t1 VALUES(1)
70 Query       INSERT INTO t2 VALUES(1)
71 Query       SELECT COUNT(*) FROM t1
71 Query       SELECT COUNT(*) FROM t2
70 Query       SELECT connection_id()
71 Query       KILL 70
69 Query       XA END 0x6d697234322f35,0x31,0x101
70 Query       XA END 0x6d697234322f35,0x32,0x101
69 Query       XA PREPARE 0x6d697234322f35,0x31,0x101
69 Query       XA ROLLBACK 0x6d697234322f35,0x31,0x101
71 Query       SELECT COUNT(*) FROM t1
71 Query       SELECT COUNT(*) FROM t2

ID=69とID=70のコネクションがXAを使っているコネクション.ID=71はテスト結果を確認したりするのに使っているコネクション.

ID=70がCommitの直前でKILLされている.ID=69はCommit命令により"XA PREPARE"を行うが,全体としては失敗なため"XA COMMIT"ではなく"XA ROLLBACK"をその後行うわけで.

そしてこれは,以下のコードで再現できる.

public void testErrorAtGroupCommit() throws Exception {
    MysqlXADataSource xads = new MysqlXADataSource();
    
    XAConnection xaConn = xads.getXAConnection();
    XAResource xaResource = xaConn.getXAResource();
    Statement xaStmt = xaConn.getConnection().createStatement();
    
    XAConnection xaConn2 = xads.getXAConnection();
    XAResource xaResource2 = xaConn2.getXAResource();
    Statement xaStmt2 = xaConn2.getConnection().createStatement();
    
    Connection testConn = getConnectionWithProps(null);
    Statement testStmt = testConn.createStatement();
    
    testStmt.executeUpdate("DROP TABLE IF EXISTS t1");
    testStmt.executeUpdate("CREATE TABLE t1 (c1 int) ENGINE = InnoDB");
    testStmt.executeUpdate("DROP TABLE IF EXISTS t2");
    testStmt.executeUpdate("CREATE TABLE t2 (c1 int) ENGINE = InnoDB");
    
    TxManager tm = TxManager.getInstance();
    tm.begin();        
    Transaction tx = tm.getTransaction();
    tx.enlistResource(xaResource);
    tx.enlistResource(xaResource2);
    
    xaStmt.executeUpdate("INSERT INTO t1 VALUES(1)");
    xaStmt2.executeUpdate("INSERT INTO t2 VALUES(1)");
    
    // check the table data before trying to commit
    ResultSet rs = testStmt.executeQuery("SELECT COUNT(*) FROM t1");
    rs.next();
    assertEquals(0, rs.getInt(1));
    rs = testStmt.executeQuery("SELECT COUNT(*) FROM t2");
    rs.next();
    assertEquals(0, rs.getInt(1));
    
    rs = xaStmt2.executeQuery("SELECT connection_id()");
    rs.next();
    int id = rs.getInt(1);
    testStmt.executeUpdate("KILL " + id);
    
    try {
        tm.commit();
        fail();
    } catch (JBossRollbackException ex) {
        // ok if RollbackException is thrown
    }
    
    // check if transaction is rollbacked
    rs = testStmt.executeQuery("SELECT COUNT(*) FROM t1");
    rs.next();
    assertEquals(0, rs.getInt(1));
    rs = testStmt.executeQuery("SELECT COUNT(*) FROM t2");
    rs.next();
    assertEquals(0, rs.getInt(1));
    
    xaStmt.close();
    xaStmt2.close();
    testStmt.close();
    xaConn.close();
    xaConn2.close();
    testConn.close();
}

そういうことで,御しまい.