• 廖雪峰Java15JDBC编程-3JDBC接口-4JDBC事务


    1 数据库事务:Transaction

    1.1 定义

    • 若干SQL语句构成的一个操作序列
    • 要么全部执行成功
    • 要么全部执行不成功

    1.2 数据库事务具有ACID特性:

    • Atomicity:原子性

      一个事务虽有若干SQL语句构成,但它本身是一个原子操作,要么全部成功,要么全部失败

    • Consistency:一致性

      一个事务在开始前或结束之后,数据库的数据是完整的,不存在冲突和数据不一致的情况

    • Isolation:隔离型

      多个事务并发执行的时候,事务之间是隔离的,一个事务不应该影响其他事务运行的结果

    • Durability:持久性

      一个事务一旦成功完成,这个事务对数据库的更改会持久的保留在数据库中,并且不会被回滚。

    1.3 为什么需要数据库事务?

    例子:新建accounts表。

    CREATE table accounts (id int not null auto_increment, name varchar(10), balance DECIMAL(5,2) ,PRIMARY KEY(id)) ENGINE=INNODB DEFAULT charset=utf8;
    INSERT INTO accounts(name,balance) VALUES ("小明",550.00),("小红",120.00),("小军",870.60),("小白",120.00),("小兵",199.01);
    

    转账操作,把100元从小明的账户转给小红。

    update accounts set balance = balance - 100 where id = 1;
    update accounts set balance = balance + 100 where id = 2;
    
    情况一:如果这两条语句执行成功,小明的账户减少100,变为450元,小红的账号增加100,变为220元。
    情况二:但如果在执行万第一条update语句后,由于各种原因,第二条update没有执行,会出现什么结果呢? 小明的钱已经扣了,小红的钱却未加上,这个时候应用逻辑出现了不一致的情况。

    事务的作用:使用数据库事务可以保证多个SQL语句全部执行或全部取消。转账结果只有2种:
    1.小明的钱成功转给了小红
    2.小明的钱没有成功转给小红,退回小明账户。小明和小红的钱保持不变。

    1.4 事务隔离级别

    事务隔离是由数据库允许多个连接同时执行事务引起的。在Java程序中,当一个线程操作一个数据库连接的时候,另一个线程可能也在通过一个数据库连接更新数据。他们之间就会遇到隔离级别的问题。
    事务隔离会遇到3个问题:

    • 1.脏读 Dirty Read
    • 2.非重复复读 Non Repeatable Read
    • 3.幻读 Phantom

    1.4.1 Dirty Read 脏读:B读到了A未提交成功的数据Bob

    -- 事务A:
    update students set name = 'Bob' where id = 1;
    rollback;
    -- 事务B: 
    select * from students where id = 1;
    commit;
    

    事务A修改了修改了一个数据,但还没有提交。这个时候,事务B读取了A没有提交的更新结果,如果事务A回滚,事务B读到的就是脏数据。

    1.4.2 Non Repeatable Read 非重复读:在事务B中,2次select查询得到的结果不一致,这是因为事务A在2次select之间修改了数据,并且进行了提交。

    而我们在同一个事务中,对于同样的select语句,希望返回同样的结果。这里出现的结果不一致的情况,就是Non Repeatable Read。

    -- 事务A
    update students set name='Bob' where id = 1;
    commit;
    -- 事务2
    select * from students where id = 1; -- 小明
    select * from students where id = 1; -- Bob
    
    

    1.4.3 Phantom Read:幻读

    在事务B中两次select条件为id=99的记录都是空,仿佛id=99的记录不存在。但实际上,事务A已经insert了一条id为99的记录。
    当事务B第三次操作id=99的记录,就会多了一条记录。

    -- 事务A
    insert into students values(99, 1,'Bob', 'M');
    commit;
    -- 事务B
    select * from students where id = 99; --empty
    select * from students where id = 99; --empty
    update students set name 'Alice' where id = 99;
    commit;
    select * from students where id = 99; --Alice
    

    1.4.4 数据库隔离级别:为了避免这三种问题,数据库定义了数据库隔离级别

    isolation levelDirty ReadNon Repeatable ReadPhantom Read
    Read Uncommitted会出现会出现会出现
    Read Committed会出现会出现
    Repeatable Read会出现
    Serializable
    但是隔离级别越高,数据运行的时候,要加的锁就越多,能够同时并发执行的事务就越少,所以要选择一个合适的隔离级别。

    2 JDBC事务代码

        conn = openConnection();
        try{
            conn.setAutoCommit(false); //开始一个事务
            //执行多条SQL语句
            insert(); update(); delete();
            conn.commit(); //提交一个事务
        }catch(Exception e){
            conn.rollback(); //出现异常,就回滚这个事务
        }finally{
            conn.setAutoCommit(true); //恢复数据库原来的事务模式,即结束事务
            conn.close(); //关闭连接
        }
    
    package com.feiyangedu.sample.pop3;
    
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public class JdbcTx {
        static final String JDBC_URL = "jdbc:mysql://localhost:13306/test0828?characterEncoding=utf8&useSSL=false&serverTimeZone=UTC";
        static final String JDBC_USER = "root";
        static final String JDBC_PASSWORD = "123456";
    
        public static void main(String[] args) throws SQLException{
            List<Student> students = getAllStudents();
            for(Student student:students){
                System.out.println(student);
            }
            Connection conn = null;
            try{
                conn = getConnection();
                conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                conn.setAutoCommit(false);
                updateName(conn, students.get(0).id, "Bob");
                updateName(conn, students.get(1).id, "Alice");
                conn.commit();
                System.out.println("commit ok");
            }catch (Exception e){
                e.printStackTrace();
                conn.rollback();
            }finally {
                if(conn != null){
                    try{
                        conn.setAutoCommit(true);
                        conn.close();
                    }catch (SQLException e){
                        System.err.println(e);
                    }
    
                }
            }
            System.out.println("第一次事务结束后");
            students = getAllStudents();
            for(Student student:students){
                System.out.println(student);
            }
            try{
                conn = getConnection();
                conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                conn.setAutoCommit(false);
                updateName(conn, students.get(0).id,"张三");
                updateName(conn, students.get(1).id,"李四");
                throw new RuntimeException("第二次事务出错");
            }catch (Exception e){
                conn.rollback();
            }finally {
                if(conn != null){
                    try{
                        conn.setAutoCommit(true);
                        conn.close();
                    }catch (Exception e){
                        System.err.println(e);
                    }
                }
            }
            System.out.println("第二次事务结束");
            students = getAllStudents();
            for(Student student:students){
                System.out.println(student);
            }
        }
        static void updateName(Connection conn, long id, String name) throws SQLException{
            try(PreparedStatement ps = conn.prepareStatement("update students set name=? where id=?")){
                ps.setObject(1, name);
                ps.setObject(2, id);
                ps.executeUpdate();
            }
        }
        static List<Student> getAllStudents() throws SQLException{
            try(Connection conn = getConnection()){
                try(PreparedStatement ps = conn.prepareStatement("select * from students")){
                    ResultSet rs = ps.executeQuery();
                    List<Student> list = new ArrayList<>();
                    while(rs.next()){
                        long id = rs.getLong("id");
                        long classId = rs.getLong("class_id");
                        String name = rs.getString("name");
                        String gender = rs.getString("gender");
                        list.add(new Student(id,classId,name,gender));
                    }
                    return list;
                }
            }
        }
    
        static Connection getConnection() throws SQLException {
            return DriverManager.getConnection(JDBC_URL,JDBC_USER,JDBC_PASSWORD);
        }
    }
    

    3. 数据库事务总结

    • 具有ACID特性
      * Atomicity:原子性
      * Consistency:一致性
      * Isolation:隔离型
      * Durability:持久性
    • JDBC提供了事务的支持
  • 相关阅读:
    将自己的工作环境全面移植到C++最后一道工序:能用MFC制作简单的图形界面
    Finally, the working environment has been moved to C++
    统计方面的书籍【zz】
    zz sql 通配符以及转义字符用法
    转载学习并实现DES加密解密算法(三)
    【资源分享】2009版大陆汉语常用字.txt下载
    自己实现的C++Trim()
    nmake命令(windows下的makefile)
    c++对象内存模型【内存布局】
    UML类图关系(VPUML工具绘图)
  • 原文地址:https://www.cnblogs.com/csj2018/p/11444149.html
Copyright © 2020-2023  润新知