JDBC事务管理

Demo1 - JDBC控制事务

  • connection.setAutoCommit(false);
  • connection.commit();
package com.lixinlei.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class LocalJDBCTransactionApplication {

    private static final Logger logger = LoggerFactory.getLogger(LocalJDBCTransactionApplication.class);

    public static void main(String[] args) throws SQLException {
        Connection connection = getConnection();
        connection.setAutoCommit(false);

        String plusSQL = "update t_user set amount = amount + 100 where username = ?";
        PreparedStatement plusPS = connection.prepareStatement(plusSQL);
        plusPS.setString(1, "superman"); 

        String minusSQL = "update t_user set amount = amount - 100 where username = ?";
        PreparedStatement minusPS = connection.prepareStatement(minusSQL);
        minusPS.setString(1,"batman");

        plusPS.executeUpdate();
        minusPS.executeUpdate();

        connection.commit();
        minusPS.close();
        plusPS.close();
        connection.close();
    }

    private static Connection getConnection() throws SQLException {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/dist_tran_course";
        String username = "root";
        String password = "MyNewPass4!";
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            logger.error(e.getLocalizedMessage());
        }
        return DriverManager.getConnection(url, username, password);
    }

}

Demo2 - 在事务中抛出异常

  • System.out.println(10/0);
  • connection.close();的时候回rollback;
package com.lixinlei.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class LocalJDBCTransactionApplication {

    private static final Logger logger = LoggerFactory.getLogger(LocalJDBCTransactionApplication.class);

    public static void main(String[] args) throws SQLException {
        Connection connection = getConnection();
        connection.setAutoCommit(false);

        String plusSQL = "update t_user set amount = amount + 100 where username = ?";
        PreparedStatement plusPS = connection.prepareStatement(plusSQL);
        plusPS.setString(1, "superman");

        System.out.println(10/0);

        String minusSQL = "update t_user set amount = amount - 100 where username = ?";
        PreparedStatement minusPS = connection.prepareStatement(minusSQL);
        minusPS.setString(1,"batman");

        plusPS.executeUpdate();
        minusPS.executeUpdate();

        connection.commit();
        minusPS.close();
        plusPS.close();
        connection.close();
    }

    private static Connection getConnection() throws SQLException {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/dist_tran_course";
        String username = "root";
        String password = "MyNewPass4!";
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            logger.error(e.getLocalizedMessage());
        }
        return DriverManager.getConnection(url, username, password);
    }

}

程序输出:

"D:\Program Files\Java\jdk1.8.0_111\bin\java" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2017.3\lib\idea_rt.jar=60152:D:\Program Files\JetBrains\IntelliJ IDEA 2017.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_111\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar;D:\workspace\IdeaU_2017_3\jdbctransactionlocal\target\classes;D:\repo\maven3.5.4\mysql\mysql-connector-java\5.1.39\mysql-connector-java-5.1.39.jar;D:\repo\maven3.5.4\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;D:\repo\maven3.5.4\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;D:\repo\maven3.5.4\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar" com.lixinlei.example.LocalJDBCTransactionApplication
Wed Jun 27 00:19:17 CST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.lixinlei.example.LocalJDBCTransactionApplication.main(LocalJDBCTransactionApplication.java:23)

Process finished with exit code 1

数据结果:

mysql> select * from t_user;
+----+----------+--------+
| id | username | amount |
+----+----------+--------+
|  1 | batman   |    100 |
|  2 | superman |    100 |
+----+----------+--------+
2 rows in set (0.00 sec)

Demo3

业务目标

  • 事务1:batman给superman转100,batman:0,superman:200;
  • 事务2:给superman加100,superman:300;
  • 最终结果:superman:300,batman:0;

程序执行过程

  • 事务1停在connection.commit(),即事务1未提交;
  • 事务2可以读到事务1执行之前的数据(superman:100,batman:100),但不会执行数据更改的代码,直到事务1提交(superman:200, batman:0);
  • 事务1提交后(superman:200, batman:0),事务2在之前读到的数据(superman:100)的基础上给superman加100,导致superman:200;
  • 最终结果,superman:200,batman:0,和业务目标不符;

结论

  • 可重复读事务1运行中,对所更改的数据加锁;
  • 允许别的事务读,因为读不需要获得锁,但读不到A对数据的修改;
  • 不允许别的事务写,因为写需要获得锁,需等事务1将锁释放;

示例代码 - 事务1

package com.lixinlei.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class LocalJDBCTransactionApplication {

    private static final Logger logger = LoggerFactory.getLogger(LocalJDBCTransactionApplication.class);

    public static void main(String[] args) throws SQLException {
        Connection connection = getConnection();
        connection.setAutoCommit(false);

        String plusSQL = "update t_user set amount = amount + 100 where username = ?";
        PreparedStatement plusPS = connection.prepareStatement(plusSQL);
        plusPS.setString(1, "superman");

        String minusSQL = "update t_user set amount = amount - 100 where username = ?";
        PreparedStatement minusPS = connection.prepareStatement(minusSQL);
        minusPS.setString(1,"batman");

        plusPS.executeUpdate();
        minusPS.executeUpdate();

        connection.commit();
        minusPS.close();
        plusPS.close();
        connection.close();
    }

    private static Connection getConnection() throws SQLException {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/dist_tran_course";
        String username = "root";
        String password = "MyNewPass4!";
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            logger.error(e.getLocalizedMessage());
        }
        return DriverManager.getConnection(url, username, password);
    }

}

示例代码 - 事务2

package com.lixinlei.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;

public class LocalJDBCTransactionApplication2 {

    private static final Logger logger = LoggerFactory.getLogger(LocalJDBCTransactionApplication2.class);

    public static void main(String[] args) throws SQLException {
        Connection connection = getConnection();

        String query = "select * from t_user";
        PreparedStatement ps = connection.prepareStatement(query);
        ResultSet rs = ps.executeQuery();
        int superManAccount = 0;
        while (rs.next()) {
            String username = rs.getString(2);
            int amount = rs.getInt(3);
            logger.info("{} has amount: {}", username, amount);
            if ("superman".equals(username)) {
                superManAccount = amount;
            }
        }
        logger.info("superManAccount: {}", superManAccount);

        String plusSQL = "update t_user set amount = ? where username = ?";
        PreparedStatement plusPS = connection.prepareStatement(plusSQL);
        plusPS.setInt(1, superManAccount + 100);
        plusPS.setString(2, "superman");
        plusPS.executeUpdate();

        plusPS.close();
        connection.close();
    }

    private static Connection getConnection() throws SQLException {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/dist_tran_course";
        String username = "root";
        String password = "MyNewPass4!";
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            logger.error(e.getLocalizedMessage());
        }
        return DriverManager.getConnection(url, username, password);
    }

}

Deme4 - FOR UPDATE

  • for update会对select的数据加锁,所以select一定要加where条件,否则全表锁对性能影响很大;
  • 正在运行的事务1会对数据加锁,导致事务2的select ...for update无法获得锁,继而无法读数据,继而无法读到事务1开始前的导致问题的数据,需等待事务1的完成,事务2才能获得锁;
  • for update相当于MySQL中的SERIALIAZABLE事务隔离级别;

实例代码 - 事务1

package com.lixinlei.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class LocalJDBCTransactionApplication {

    private static final Logger logger = LoggerFactory.getLogger(LocalJDBCTransactionApplication.class);

    public static void main(String[] args) throws SQLException {
        Connection connection = getConnection();
        connection.setAutoCommit(false);

        String plusSQL = "update t_user set amount = amount + 100 where username = ?";
        PreparedStatement plusPS = connection.prepareStatement(plusSQL);
        plusPS.setString(1, "superman");

        String minusSQL = "update t_user set amount = amount - 100 where username = ?";
        PreparedStatement minusPS = connection.prepareStatement(minusSQL);
        minusPS.setString(1,"batman");

        plusPS.executeUpdate();
        minusPS.executeUpdate();

        connection.commit();
        minusPS.close();
        plusPS.close();
        connection.close();
    }

    private static Connection getConnection() throws SQLException {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/dist_tran_course";
        String username = "root";
        String password = "MyNewPass4!";
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            logger.error(e.getLocalizedMessage());
        }
        return DriverManager.getConnection(url, username, password);
    }

}

实例代码 - 事务2

  • select * from t_user where id = 2 for update
package com.lixinlei.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;

public class LocalJDBCTransactionApplication2 {

    private static final Logger logger = LoggerFactory.getLogger(LocalJDBCTransactionApplication2.class);

    public static void main(String[] args) throws SQLException {
        Connection connection = getConnection();

        String query = "select * from t_user where id = 2 for update ";
        PreparedStatement ps = connection.prepareStatement(query);
        ResultSet rs = ps.executeQuery();
        int superManAccount = 0;
        while (rs.next()) {
            String username = rs.getString(2);
            int amount = rs.getInt(3);
            logger.info("{} has amount: {}", username, amount);
            if ("superman".equals(username)) {
                superManAccount = amount;
            }
        }
        logger.info("superManAccount: {}", superManAccount);

        String plusSQL = "update t_user set amount = ? where username = ?";
        PreparedStatement plusPS = connection.prepareStatement(plusSQL);
        plusPS.setInt(1, superManAccount + 100);
        plusPS.setString(2, "superman");
        plusPS.executeUpdate();

        plusPS.close();
        connection.close();
    }

    private static Connection getConnection() throws SQLException {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/dist_tran_course";
        String username = "root";
        String password = "MyNewPass4!";
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            logger.error(e.getLocalizedMessage());
        }
        return DriverManager.getConnection(url, username, password);
    }

}

你可能感兴趣的:(JDBC事务管理)