事务&ThreadLocal

事务&ThreadLocal

  • 理解事务的概念
  • 理解脏读,不可重复读,幻读的概念及解决办法
  • 能够在MySQL中使用事务
  • 能够在JDBC中使用事务
  • 能够在DBUtils中使用事务
  • 能够理解ThreadLocal的作用

第一章 事务操作

事物概述

  • 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败
  • 事务作用:保证在一个事务中多次SQL操作要么全都成功,要么全都失败.
  • 事务前提:保证是在一个Connection下操作

1.1 mysql事务操作

sql语句描述
start transaction开启事务
commit提交事务
rollback回滚事务
  • 准备数据
# 创建一个表:账户表.
create database webdb;
# 使用数据库
use webdb;
# 创建账号表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
# 初始化数据
insert into account values (null,'jack',10000);
insert into account values (null,'rose',10000);
insert into account values (null,'tom',10000);
  • 操作
    • MYSQL中可以有两种方式进行事务的管理:
      • 自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。
      • 手动提交:先开启,再提交
  • 方式1:手动提交
start transaction;
update account set money=money-1000 where name='jack';
update account set money=money+1000 where name='rose';
commit;
#或者
rollback;
  • 方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制
show variables like '%commit%';
* 设置自动提交的参数为OFF:
set autocommit = 0; -- 0:OFF 1:ON

1.2 jdbc事务操作

Connection 对象的方法名描述
conn.setAutoCommit(false)开启事务
conn.commit()提交事务
conn.rollback()回滚事务

代码演示

/*
    使用原生JDBC(6步)完成转账案例
        update account set money=money-1000 where name='jack';
        update account set money=money+1000 where name='rose';
    事务:保证一组(一个conn)sql语句要么全部执行成功,要么全部执行失败
        数据库是有事务概念,执行sql语句,就会开启事务,执行成功会提交事务,执行失败会回滚事务
            mysql数据库事务默认都是自动的,自动开启事务,自动提交事务,自动回滚事务
            oracle数据库事务默认都是手动的,手动开启事务,手动提交事务,手动回滚事务
            注意:
                事务一旦结束(提交,回滚),那么数据就永久的保存在数据库
    在Connection接口中有操作事务的方法
        void setAutoCommit(boolean autoCommit) 将此连接的自动提交模式设置为给定状态。
            autoCommit - 为 true 表示启用自动提交模式;不写默认
                         为 false 表示禁用自动提交模式,
                         执行sql语句之前,手动的开启事务
        void commit() 一组sql语句都执行成功,提交事务
        void rollback() 一组sql中有一条执行失败,回滚事务; 把数据回滚到事务开启之前
    在Connection接口中有设置事务隔离级别的方法
         void setTransactionIsolation(int level) 试图将此 Connection 对象的事务隔离级别更改为给定的级别。
         参数:
            int level:
                    serializable(8) > repeatable read(4) > read committed(2) > read uncommitted(1)
                Connection接口中定义的常量(建议)
                    int TRANSACTION_NONE             = 0;
                    int TRANSACTION_READ_UNCOMMITTED = 1;
                    int TRANSACTION_READ_COMMITTED   = 2;
                    int TRANSACTION_REPEATABLE_READ  = 4;
                    int TRANSACTION_SERIALIZABLE     = 8;
 */
public class Demo01JDBC {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stat = null;
        try {
            //注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //获取数据库连接对象Connection
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05","root","root");

            //设置数据库的隔离级别为read committed(2)
            //conn.setTransactionIsolation(2);
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);


            //开启事务
            conn.setAutoCommit(false);

            //获取执行者对象Statement
            stat = conn.createStatement();
            //执行sql语句,获取结果
            int row1 = stat.executeUpdate("update account set money=money-1000 where name='jack';");
            System.out.println(0/0);
            int row2 = stat.executeUpdate("update account set money=money+1000 where name='rose';");
            //处理结果
            if(row1>0 && row2>0){
                System.out.println("转账成功!");

                //提交事务
                conn.commit();
            }
        } catch (Exception e) {
            System.out.println("转账失败!");
            e.printStackTrace();

            try {
                //回滚事务
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

        } finally {
            //释放资源
            if(stat!=null){
                try {
                    stat.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

1.3 DBUtils事务操作

Connection对象的方法名描述
conn.setAutoCommit(false)开启事务
new QueryRunner()创建核心类,不设置数据源(手动管理连接)
query(conn , sql , handler, params ) 或update(conn, sql , params)手动传递连接, 执行SQL语句CRUD
DbUtils.commitAndCloseQuietly(conn)提交并关闭连接,不抛异常
DbUtils.rollbackAndCloseQuietly(conn)回滚并关闭连接,不抛异常

代码演示

	//事务模板代码
public void demo02() throws SQLException{
	// 获得连接
	Connection conn = null;
	try {
		//#1 开始事务
		conn.setAutoCommit(false);
		//.... 加钱 ,减钱
		//#2 提交事务
		DbUtils.ommitAndCloseQuietly(conn);
	} catch (Exception e) {
		//#3 回滚事务
		DbUtils.rollbackAndCloseQuietly(conn);
		e.printStackTrace();
		}
	}

1.4 案例:JDBC事务分层(dao、service)传递Connection

分析
在这里插入图片描述

  • 开发中,常使用分层思想
    • 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
  • 不同层级结构彼此平等
  • 分层的目的是:
    • 解耦
    • 可维护性
    • 可扩展性
    • 可重用性

代码实现

  • 工具类C3P0Utils
/*
    C3P0连接池的工具类XML版本:使用C3P0连接池获取数据库连接对象Connection并返回

    连接池有一个规范接口
        javax.sql.DataSource接口
        定义了一个从连接池中获取连接的方法
        Connection getConnection() 尝试建立与此 DataSource 对象所表示的数据源的连接。
     C3P0实现了连接池的规范接口DataSource
        com.mchange.v2.c3p0.ComboPooledDataSource类 implements DataSource接口
        重写了getConnection方法
     使用步骤:
        1.在成员位置创建一个静态的ComboPooledDataSource对象
        2.把c3p0-config.xml复制到当前模块的src下;
            C3P0就会自动的解析xml,获取数据库连接信息给ComboPooledDataSource对象赋值
        3.定义一个静态方法,从ComboPooledDataSource对象中获取数据库连接对象Connection并返回
        4.定义一个释放资源的方法
 */
public class C3P0UtilsXML {
    //1.在成员位置创建一个静态的ComboPooledDataSource对象
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    //3.定义一个静态方法,从ComboPooledDataSource对象中获取数据库连接对象Connection并返回
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            /*
                获取数据库连接对失败,让程序停止下来
                把编译异常,转换为运行时异常
             */
            throw new RuntimeException("获取数据库连接对象失败"+e);
        }
    }

    //定义一个方法,返回连接池对象,给QueryRunner使用
    public static DataSource getDataSource(){
        return dataSource;
    }

    //4.定义一个释放资源的方法
    public static void close(ResultSet rs, Statement stat, Connection conn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(stat!=null){
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn!=null){
            try {
                conn.close();//把连接在归还给连接池
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
  • c3p0-config.xml
<c3p0-config>
    <!-- 使用默认的配置读取连接池对象 -->
    <default-config>
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/day05</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <!-- 连接池参数 -->
        <!-- 初始连接数 -->
        <property name="initialPoolSize">5</property>
        <!-- 最大连接数 -->
        <property name="maxPoolSize">10</property>
        <!-- 最大等待时间 -->
        <property name="checkoutTimeout">2000</property>
        <!-- 最大空闲回收时间 -->
        <property name="maxIdleTime">1000</property>
    </default-config>
</c3p0-config>
  • 步骤1:编写入口程序
/*
    创建转账案例的web层
        使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
        创建AccountSerivce对象
        调用转账方法,接收转账结果
        对结果进行判断,给用户展示结果
 */
public class AccountWeb {
    public static void main(String[] args) {
        //使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入付款人姓名:");
        String fromName = sc.next();
        System.out.print("请输入收款人姓名:");
        String toName = sc.next();
        System.out.print("请输入转账金额:");
        double money = sc.nextDouble();

        //创建AccountSerivce对象
        AccountService serivce = new AccountService();
        //调用转账方法,接收转账结果
        boolean b = serivce.transferAccount(fromName, toName, money);
        //对结果进行判断,给用户展示结果
        if (b){
            System.out.println("转账成功!");
        }else{
            System.out.println("转账失败!");
        }
    }
}
  • service层
/*
    转账案例的Service层:接收web的传递的数据,调用dao层的方法,接收结果;把结果返回给web层
    定义一个转账方法:
        参数接收web传递的数据(付款人姓名,收款人姓名,转账金额)
        使用C3P0连接池获取Connection
        开启事务
        创建AccountDao对象
        调用减钱和加钱方法,接收结果
        对结果进行判断
        都执行成功,提交事务
        有异常,回滚事务
        把结果返回给web层
        释放资源
 */
public class AccountService {
    //定义一个转账方法
    public boolean transferAccount(String fromName,String toName,double momey){
        //使用C3P0连接池获取Connection
        Connection conn = C3P0UtilsXML.getConnection();

        //定义返回的结果
        boolean flag = false;

        try {
            //开启事务
            conn.setAutoCommit(false);

            //创建AccountDao对象
            AccountDao dao = new AccountDao();
            //调用减钱和加钱方法,接收结果
            int row1 = dao.fromAccount(conn, fromName, momey);
            System.out.println(0/0);
            int row2 = dao.toAccount(conn, toName, momey);
            //对结果进行判断
            if(row1>0 && row2>0){
                flag = true;
                //都执行成功,提交事务
                conn.commit();
            }

        } catch (Exception e) {
            e.printStackTrace();
            //有异常,回滚事务
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

        } finally {
            //释放资源
            DbUtils.closeQuietly(conn);
        }
        //把结果返回给web层
        return flag;
    }
}
  • dao层
/*
    创建转账案例的Dao层:用于对account表进行增删改查
    注意:
        一张表-->一个dao
    定义两个方法:
        一个减钱,一个加钱
 */
public class AccountDao {
    /*
        定义减钱方法
        参数:
            Connection conn:两个方法使用同一个Connection,从而保证使用同一个事务
            String fromName:付款人姓名
            double money:转账金额
        返回值:
            int:影响数据库的有效行数
     */
    public int fromAccount(Connection conn,String fromName,double money) throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用update方法执行sql语句,接收结果
        int row = qr.update(conn,"update account set money=money-? where name=?;",money,fromName);
        //返回结果
        return row;
    }

    /*
        定义加钱方法
        参数:
            Connection conn:两个方法使用同一个Connection,从而保证使用同一个事务
            String toName:收款人姓名
            double money:转账金额
        返回值:
            int:影响数据库的有效行数
     */
    public int toAccount(Connection conn,String toName,double money) throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用update方法执行sql语句,接收结果
        int row = qr.update(conn,"update account set money=money+? where name= ?;",money,toName);
        //返回结果
        return row;
    }
}

第二章 ThreadLocal

2.1分析

在“事务传递Connection参数案例”中,我们必须传递Connection对象,才可以完成整个事务操作。如果不传递参数,是否可以完成?在JDK中给我们提供了一个工具类:ThreadLocal,此类可以在一个线程中共享数据。
java.lang.ThreadLocal该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。

2.2相关知识:ThreadLocal

java.lang.ThreadLocal 该类提供了线程局部(thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具类底层就是相当于一个Map,key存放的当前线程,value存放需要共享的数据。

举例

/*
    java.lang.ThreadLocal<T>类:该类提供了线程局部 (thread-local) 变量。

    ThreadLocal的底层原理:使用一个Map集合,对Map进行了封装
        Map<当前线程Thread,Object> map = new HashMap<当前线程Thread,Object>();
        当前线程Thread:static Thread currentThread()
        在使用方法的时候,省略了Map集合的key,只使用了Map集合的vlaue,key默认就是用的当前正在执行的线程

        程序在main方法中执行:main线程
        程序在run方法中执行:新创建的线程
    ThreadLocal构造方法:
        ThreadLocal() 创建一个线程本地变量。
    ThreadLocal的成员方法:
        void set(T value)  往ThreadLocal中添加数据
            此方法相当于Map集合的put方法
                put(Thread.currentThread(),Value);
        T get()  通过当前线程,获取当前线程保存的值
            此方法相当于Map集合的get方法
               value =  get(Thread.currentThread());
        void remove() 通过当前线程,移除ThreadLocal中保存的健值
             此方法相当于Map集合的remove方法
                remove(Thread.currentThread())
    线程局部(自己) (thread-local) 变量。
        哪个线程往ThreadLocal中存储的数据,只有哪个线程能使用,其他线程不能使用
            mian线程存储的数据,只有main线程能使用
            Thread-0线程存储的数据,只有Thread-0线程能使用
            ...
 */
public class Demo01ThreadLocal {
    public static void main(String[] args) {
        //创建ThreadLocal对象,泛型使用String
        ThreadLocal<String> tl = new ThreadLocal<>();

        //使用set方法往ThreadLocal中添加数据
        //底层 map.put(main线程,"main-->添加的数据")
        tl.set(Thread.currentThread().getName()+"-->添加的数据");

        //使用get方法获取ThreadLocal中保存的数据
        //底层 map.get(main线程)
        String s = tl.get();
        System.out.println(s);//main-->添加的数据

        new Thread(new Runnable() {
            @Override
            public void run() {
                //使用get方法获取ThreadLocal中保存的数据
                //底层 map.get(Thread-0线程)
                System.out.println(tl.get());//null

                //使用set方法往ThreadLocal中添加数据
                //底层 map.put(Thread-0线程,"main-->添加的数据")
                tl.set(Thread.currentThread().getName()+"-->添加的数据");

                System.out.println(tl.get());//Thread-0添加的数据
            }
        }).start();
    }
}

使用ThreadLocal存储Connection

/*
    好处:
        一个线程存储的Connection,多次获取使用的是同一个
        多线程并发的使用ThreadLocal存取数据,互相之间互不影响
 */
public class Demo02ThreadLocal {
    public static void main(String[] args) {
        //创建ThreadLocal对象,泛型使用Connection
        ThreadLocal<Connection> tl = new ThreadLocal<>();

        //使用连接池获取Connection
        Connection conn1 = C3P0UtilsXML.getConnection();

        //使用set方法往ThreadLocal中添加Connection
        tl.set(conn1);

        //使用get方法获取ThreadLocal中存储的Connection
        Connection conn2 = tl.get();

        System.out.println("---------------main方法中-->main线程--------------------");
        System.out.println(conn1);//@2f8dad04
        System.out.println(conn2);//@2f8dad04
        System.out.println(conn1==conn2);//true
        System.out.println(tl.get());//@2f8dad04
        System.out.println(tl.get());//@2f8dad04
        System.out.println(tl.get());//@2f8dad04
        System.out.println(tl.get());//@2f8dad04
        System.out.println(tl.get());//@2f8dad04


        new Thread(new Runnable() {
            @Override
            public void run() {
                //使用set方法往ThreadLocal中添加Connection
                tl.set(C3P0UtilsXML.getConnection());
                //使用get方法获取ThreadLocal中存储的Connection
                System.out.println("---------------run方法中-->Thread-x线程--------------------");
                System.out.println(tl.get());//@394bad33
                System.out.println(tl.get());//@394bad33
                System.out.println(tl.get());//@394bad33
                System.out.println(tl.get());//@394bad33
                System.out.println(tl.get());//@394bad33
                System.out.println(tl.get());//@394bad33
                System.out.println(tl.get());//@394bad33
                System.out.println(tl.get());//@394bad33

            }
        }).start();

    }
}

结论:向ThreadLocal对象中添加的数据只能在当前线程下使用。

结合案例使用

在这里插入图片描述

代码实现

工具类

/*
    使用ThreadLocal优化程序
 */
public class C3P0UtilsXMLTL {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    //创建一个静态的ThreadLocal对象,存储Connection
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();

    public static Connection getConnection(){
        //从ThreadLocal中获取Connection
        Connection conn = tl.get();// get(当前线程)
        if(conn==null){
            try {
                //从连接池中获取Connection
                conn = dataSource.getConnection();
                //把获取到conn存储到ThreadLocal-->初始化一个Connection
                tl.set(conn); // put(当前线程,conn)
            } catch (SQLException e) {
               //将编译时异常 转换 运行时 , 以后开发中运行时异常使用比较多的。
               throw new RuntimeException(e);
            }
        }
        return conn;
    }

    public static DataSource getDataSource(){
        return dataSource;
    }

    public static void close(ResultSet rs, Statement stat, Connection conn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(stat!=null){
            try {
                stat.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn!=null){
            try {
                conn.close();//把连接在归还给连接池
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

service层

/*
    转账案例的Service层:接收web的传递的数据,调用dao层的方法,接收结果;把结果返回给web层
    定义一个转账方法:
        参数接收web传递的数据(付款人姓名,收款人姓名,转账金额)
        使用C3P0连接池获取Connection
        开启事务
        创建AccountDao对象
        调用减钱和加钱方法,接收结果
        对结果进行判断
        都执行成功,提交事务
        有异常,回滚事务
        把结果返回给web层
        释放资源
 */
public class AccountService {
    //定义一个转账方法
    public boolean transferAccount(String fromName,String toName,double momey){
        //使用C3P0连接池获取Connection
        Connection conn = C3P0UtilsXMLTL.getConnection();

        //定义返回的结果
        boolean flag = false;

        try {
            //开启事务
            conn.setAutoCommit(false);

            //创建AccountDao对象
            AccountDao dao = new AccountDao();
            //调用减钱和加钱方法,接收结果
            int row1 = dao.fromAccount(fromName, momey);
            int row2 = dao.toAccount(toName, momey);
            //对结果进行判断
            if(row1>0 && row2>0){
                flag = true;
                //都执行成功,提交事务
                conn.commit();
            }

        } catch (Exception e) {
            e.printStackTrace();
            //有异常,回滚事务
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

        } finally {
            //释放资源
            DbUtils.closeQuietly(conn);
        }
        //把结果返回给web层
        return flag;
    }
}
/*
    转账案例的Service层:接收web的传递的数据,调用dao层的方法,接收结果;把结果返回给web层
    定义一个转账方法:
        参数接收web传递的数据(付款人姓名,收款人姓名,转账金额)
        使用C3P0连接池获取Connection
        开启事务
        创建AccountDao对象
        调用减钱和加钱方法,接收结果
        对结果进行判断
        都执行成功,提交事务
        有异常,回滚事务
        把结果返回给web层
        释放资源
 */
public class AccountService2 {
    //定义一个转账方法
    public boolean transferAccount(String fromName,String toName,double momey){
        //使用C3P0连接池获取Connection
        Connection conn = C3P0UtilsXMLTL.getConnection();

        //定义返回的结果
        boolean flag = false;

        try {
            //开启事务
            conn.setAutoCommit(false);

            //创建AccountDao对象
            AccountDao dao = new AccountDao();
            //调用减钱和加钱方法,接收结果
            int row1 = dao.fromAccount(fromName, momey);
            System.out.println(0/0);
            int row2 = dao.toAccount(toName, momey);
            //对结果进行判断
            if(row1>0 && row2>0){
                flag = true;
                //都执行成功,提交事务
                conn.commit();
            }

        } catch (Exception e) {
            e.printStackTrace();
            //有异常,回滚事务
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

        } finally {
            //释放资源
            DbUtils.closeQuietly(conn);
        }
        //把结果返回给web层
        return flag;
    }
}

dao层

/*
    创建转账案例的Dao层:用于对account表进行增删改查
    注意:
        一张表-->一个dao
    定义两个方法:
        一个减钱,一个加钱
 */
public class AccountDao {
    /*
        定义减钱方法
        参数:
            String fromName:付款人姓名
            double money:转账金额
        返回值:
            int:影响数据库的有效行数
     */
    public int fromAccount(String fromName,double money) throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用update方法执行sql语句,接收结果
        int row = qr.update(C3P0UtilsXMLTL.getConnection(),"update account set money=money-? where name=?;",money,fromName);
        //返回结果
        return row;
    }

    /*
        定义加钱方法
        参数:
            String toName:收款人姓名
            double money:转账金额
        返回值:
            int:影响数据库的有效行数
     */
    public int toAccount(String toName,double money) throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用update方法执行sql语句,接收结果
        int row = qr.update(C3P0UtilsXMLTL.getConnection(),"update account set money=money+? where name= ?;",money,toName);
        //返回结果
        return row;
    }
}

编写入口程序

/*
    创建转账案例的web层
        使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
        创建AccountSerivce对象
        调用转账方法,接收转账结果
        对结果进行判断,给用户展示结果
 */
public class AccountWeb {
    public static void main(String[] args) {
        //使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
        /*Scanner sc = new Scanner(System.in);
        System.out.print("请输入付款人姓名:");
        String fromName = sc.next();
        System.out.print("请输入收款人姓名:");
        String toName = sc.next();
        System.out.print("请输入转账金额:");
        double money = sc.nextDouble();*/

        //创建AccountSerivce对象
        AccountService serivce = new AccountService();
        //调用转账方法,接收转账结果
        boolean b = serivce.transferAccount("jack", "rose", 1000);
        //对结果进行判断,给用户展示结果
        if (b){
            System.out.println("jackToRose转账成功!");
        }else{
            System.out.println("jackToRose转账失败!");
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                //创建AccountSerivce对象
                AccountService2 serivce = new AccountService2();
                //调用转账方法,接收转账结果
                boolean b = serivce.transferAccount("jack", "tom", 1000);
                //对结果进行判断,给用户展示结果
                if (b){
                    System.out.println("jackToTom转账成功!");
                }else{
                    System.out.println("jackToTom转账失败!");
                }
            }
        }).start();
    }
}

第三章 事务总结

3.1 事务特性:ACID

  • 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency)事务前后数据的完整性必须保持一致。
  • 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
  • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

3.2 并发访问问题

如果不考虑隔离性,事务存在3中并发访问问题。

  1. 脏读:一个事务读到了另一个事务未提交的数据.
  2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结
    果不一致。
    在这里插入图片描述
  3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果
    不一致。
    在这里插入图片描述

3.3 隔离级别:解决问题

  • 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
  1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
    a)存在:3个问题(脏读、不可重复读、虚读)。
    b)解决:0个问题
  2. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
    a)存在:2个问题(不可重复读、虚读)。
    b)解决:1个问题(脏读)
  3. repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
    a)存在:1个问题(虚读)。
    b)解决:2个问题(脏读、不可重复读)
  4. serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
    a)存在:0个问题。
    b)解决:3个问题(脏读、不可重复读、虚读)
  • 安全和性能对比
    • 安全性:serializable > repeatable read > read committed > read uncommitted
    • 性能:serializable < repeatable read < read committed < read uncommitted
  • 常见数据库的默认隔离级别
    • MySql:repeatable read
    • Oracle:read committed

3.4 演示演示

  • 隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】
  • 查询数据库的隔离级别
show variables like '%isolation%';
或
select @@tx_isolation;

在这里插入图片描述

  • 设置数据库的隔离级别
    • set session transactionisolation level级别字符串
    • 级别字符串:readuncommitted read committed repeatable read serializable
    • 例如:set session transaction isolation level read uncommitted;
  • 读未提交:readuncommitted
    • A窗口设置隔离级别
      • AB同时开始事务
      • A 查询
      • B 更新,但不提交
      • A 再查询?-- 查询到了未提交的数据
      • B 回滚
      • A 再查询?-- 查询到事务开始前数据
  • 读已提交:read committed
    • A窗口设置隔离级别
      • AB同时开启事务
      • A查询
      • B更新、但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据改变,存在问题【不可重复读】
  • 可重复读:repeatable read
    • A窗口设置隔离级别
      • AB 同时开启事务
      • A查询
      • B更新, 但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据不变,解决问题【不可重复读】
      • A提交或回滚
      • A再查询?–数据改变,另一个事务
  • 串行化:serializable
    • A窗口设置隔离级别
    • AB同时开启事务
    • A查询
      • B更新?–等待(如果A没有进一步操作,B将等待超时)
      • A回滚
      • B 窗口?–等待结束,可以进行操作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值