| Bug #116318 | useServerPrepStmts useLocalTransactionState cause first execute rollback failure | ||
|---|---|---|---|
| Submitted: | 9 Oct 2024 3:34 | Modified: | 19 Dec 2024 1:38 | 
| Reporter: | kang wang | Email Updates: | |
| Status: | Closed | Impact on me: | |
| Category: | Connector / J | Severity: | S2 (Serious) | 
| Version: | 9.0.0 | OS: | Any | 
| Assigned to: | CPU Architecture: | Any | |
   [9 Oct 2024 3:34]
   kang wang        
  
 
   [9 Oct 2024 6:26]
   kang wang        
  If I set any of useServerPrepStmts and useLocalTransactionState to true, I can successfully roll back the first time the project is started.
   [9 Oct 2024 8:13]
   MySQL Verification Team        
  Hello kang wang, Thank you for the report and test case. regards, Umesh
   [4 Dec 2024 19:00]
   Axyoan Marcelo        
  Posted by developer: Hello kang wang, Could you clarify what is the issue you are facing? I interpreted your report as "When setting useServerPrepStmts and useLocalTransactionState to true, the first rollback fails, but subsequent rollbacks are successful" I ran the testcase you provided but couldn't find any bug so I wanted to know if there's something I'm missing or if I misunderstood your report. Thank you for your report Regards, Axyoan
   [6 Dec 2024 11:18]
   kang wang        
  Sorry for the inconvenience, I’ve updated my code. The issue I’m currently facing is that in version 9.0.0, all inserts into cfg_user are successful, but when inserting into cfg_telephone, the operation correctly throws an exception. This indicates that the transaction is not being applied effectively.
sql:
CREATE TABLE `cfg_user` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `name` varchar(50) NOT NULL,
    `age` int(11) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Create telephone table with unique constraint on phone number
CREATE TABLE `cfg_telephone` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `phone` varchar(20) NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
code:
public class MySQLTransactionTest {
    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&useServerPrepStmts=true&useLocalTransactionState=true";
    private static final String USERNAME = "your_username";
    private static final String PASSWORD = "your_password";
    @Test
    void testTransactionRollback() {
        try {
            for (int i = 0; i < 5; i++) {
                final Connection conn = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
                conn.setAutoCommit(false);
                doInsertOneByOne(conn);
                conn.setAutoCommit(true); // After this point, the data is actually committed
                conn.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Test
    void testTransactionRollbackBatch() {
        try {
            for (int i = 0; i < 5; i++) {
                final Connection conn = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
                conn.setAutoCommit(false);
                doInsertBatch(conn);
                conn.setAutoCommit(true);
                conn.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private void doInsertBatch(Connection conn) throws SQLException {
        try (PreparedStatement st = conn.prepareStatement("insert into cfg_user(name, age) values(?, ?)")) {
            // First insert
            st.setString(1, "cfg");
            st.setInt(2, 18);
            st.addBatch();
            // Second insert (insert phone to trigger error)
            PreparedStatement st2 = conn.prepareStatement("insert into cfg_telephone(phone) values(?)");
            st2.setString(1, "17612176673");
            st2.executeUpdate();
            st.executeBatch();
            conn.commit(); // This line will not be reached
        } catch (Exception e) {
            e.printStackTrace();
            conn.rollback(); // This rollback will actually fail
        }
    }
    private void doInsertOneByOne(Connection conn) throws SQLException {
        try {
            // Insert user
            PreparedStatement st1 = conn.prepareStatement("insert into cfg_user(name, age) values(?, ?)");
            st1.setString(1, "cfg");
            st1.setInt(2, 18);
            st1.executeUpdate();
            // Insert phone (trigger error)
            PreparedStatement st2 = conn.prepareStatement("insert into cfg_telephone(phone) values(?)");
            st2.setString(1, "17612176673");
            st2.executeUpdate();
            conn.commit(); // This line will not be reached
        } catch (Exception e) {
            e.printStackTrace();
            conn.rollback(); // This rollback will actually fail
        }
    }
}
 
   [10 Dec 2024 16:00]
   Axyoan Marcelo        
  Posted by developer: Hello kang wang, Thank you for the information, I can confirm I see the behavior you are describing. This is definitely a bug. Once again, thank you for your report Regards, Axyoan
   [19 Dec 2024 1:38]
   Daniel So        
  Posted by developer: Added the following entry to the Connector/J 9.1.2 changelog: "When the connection properties useServerPrepStmts and useLocalTransactionState were both true and a PreparedStatement was used, a statement rollback would fail. This was because the PreparedStatement always reset the local transaction state. With this fix, Connector/J checks and makes sure that the transaction state is only reset when it should be."

