jdbc事务+DBUtils事务+ThreadLocal

第一章 事务操作

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

1.1 mysql事务操作

  1. mysql语句描述图表:
sql语句描述
start transaction开启事务
commit提交事务
rollback回滚事务
  1. 准备数据

    # 创建一个表:账户表.
    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);
    
  2. 操作

    • MYSQL中可以有两种方式进行事务的管理:

    • 自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。

    • 手动提交:先开启,再提交

  3. 方式1:手动提交

    start transaction;
      update account set money=money-1000 where name='jack';
      update account set money=money+1000 where name='rose';
      commit;
      #或者
      rollback;  
    
  4. 方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制

     show variables like '%commit%';
      * 设置自动提交的参数为OFF:
      set autocommit = 0; -- 0:OFF 1:ON
    

1.2 jdbc事务操作

  1. jdbc事务名图表:
Connection 对象的方法名描述
conn.setAutoCommit(false)开启事务
conn.commit()提交事务
conn.rollback()回滚事务
  1. 代码演示:

    //事务模板代码
    public void demo01() throws SQLException{
        // 获得连接
        Connection conn = null;
        try {
        	//#1 开始事务
        	conn.setAutoCommit(false);
        	//.... 加钱 ,减钱
        	//#2 提交事务
        	conn.commit();
        } catch (Exception e) {
        	//#3 回滚事务
        	conn.rollback();
        } finally{
        	// 释放资源
        	conn.close();
        }
    }
    

1.3 DBUtils事务操作

  1. DBUtils事务图解:

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

    //事务模板代码
    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

在这里插入图片描述

1.4.1 分析

  1. 开发中,常使用分层思想

    • 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
  2. 不同层级结构彼此平等

  3. 分层的目的是:

    • 解耦
    • 可维护性
    • 可扩展性
    • 可重用性
  4. 不同层次,使用不同的包表示

    • com.itheima 公司域名倒写
    • com.itheima.dao dao层
    • com.itheima.service service层
    • com.itheima.domain javabean
    • com.itheima.utils 工具
  5. 三层思想图解:
    在这里插入图片描述

1.4.2 代码实现

  1. 工具类C3P0Utils

    public class C3P0Utils {
        //创建一个C3P0的连接池对象(使用c3p0-config.xml中default-config标签中对应的参数)
        public static DataSource ds = new ComboPooledDataSource();
        //从池中获得一个连接
        public static Connection getConnection() throws SQLException {
        return ds.getConnection();
        }
    }	
    
  2. c3p0-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
        <c3p0-config>
        <!-- 使用默认的配置读取连接池对象 -->
        <default-config>
            <!-- 连接参数 -->
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/webdb</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>
    
  3. 步骤1:编写入口程序

    public class App {
        public static void main(String[] args) {
            try {
                String outUser = "jack";
            	String inUser = "rose";
            	Integer money = 100;
            	//2 转账
            	AccountService accountService = new AccountService();
            	accountService.transfer(outUser, inUser, money);
            	//3 提示
            	System.out.println("转账成功");
            } catch (Exception e) {
            	//3 提示
            	System.out.println("转账失败");
            	e.printStackTrace();
            }
        }
    }
    
  4. service层

    public class AccountService {
        /**
        * 事务管理方式:向下传递Connection。有侵入性。使用DBUtils
        * 业务层事务管理转账的方法
        * @param from
        * @param to
        * @param money
        */
        public void transfer(String from, String to, double money) {
        //调用dao层
        AccountDao accountDao = new AccountDao();
        //DBUtils进行事务处理的原理,是在Service层获得连接,以保证事务处理过程中的Connection对象为同一
        个Connection。
            //因为必须保证连接为同一个连接,所以在业务层获得连接,再将连接传递到持久层,代码具有侵入性。
        //DBUtils使用的方法
        Connection conn = null;
        try {
        	//获得连接
        	conn = C3P0Utils.getConnection();
        	//设置事务不自动提交
        	conn.setAutoCommit(false);
        	//调用持久层
        	accountDao.outMoney(conn,from,money);
        	//如果有异常
        	//int a = 1 / 0 ;
        	accountDao.inMoney(conn,to,money);
        	//提交事务,并安静的关闭连接
        	DbUtils.commitAndCloseQuietly(conn);
        } catch (SQLException e) {
        	//有异常出现时,回滚事务,并安静的关闭连接
        	DbUtils.rollbackAndCloseQuietly(conn);
        	e.printStackTrace();
        	}
        }
    }
    
  5. dao层

    public class AccountDao {
        /**
        * 付款方法
        * @param conn 连接对象
        * @param from 付款人
        * @param money 金额
        */
        public void outMoney(Connection conn, String from, double money) {
        QueryRunner qr = new QueryRunner();
        try {
        	String sql = "update account set money = money - ? where name = ?";
       	 	qr.update(conn, sql, money,from);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
        }
        /**
        * 收款方法
        * @param conn 连接对象
        * @param to 收款人
        * @param money 金额
        */
        public void inMoney(Connection conn, String to, double money) {
        QueryRunner qr = new QueryRunner();
        try {
        	String sql = "update account set money = money + ? where name = ?";
        	qr.update(conn, sql, money,to);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
       }
    }
    

第二章 ThreadLocal

2.1 分析

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

2.2 相关知识:ThreadLocal

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

  2. 举例:

    public class ThreadLocalDemo {
        public static void main(String[] args) {
        ThreadLocal<String> mainThread = new ThreadLocal<>();
        mainThread.set("传智播客");
        System.out.println(mainThread.get());//传智播客
        new Thread(()->{
        System.out.println(mainThread.get());//null
        }).start();
        }
    }
    
  3. 结论:向 ThreadLocal 对象中添加的数据只能在当前线程下使用。

2.3 结合案例使用

在这里插入图片描述

2.3.1 分析

在这里插入图片描述

2.3.2 代码实现

  1. ThreadLocal优化程图解:
    在这里插入图片描述

  2. 工具类

    public class C3P0Utils {
        //创建一个C3P0的连接池对象(使用c3p0-config.xml中default-config标签中对应的参数)
        public static DataSource ds = new ComboPooledDataSource();
        //给当前线程绑定 连接
        private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();
        /**
        * 获得一个连接
        */
        public static Connection getConnection(){
            try {
            	//#1从当前线程中, 获得已经绑定的连接
                Connection conn = local.get();
            	if(conn == null){
           	 //#2 第一次获得,绑定内容 – 从连接池获得
            	conn = ds.getConnection();
            	//#3 将连接存 ThreadLocal
            	local.set(conn);
            }
            	return conn; //获得连接
            } catch (Exception e) {
            	//将编译时异常 转换 运行时 , 以后开发中运行时异常使用比较多的。
            	throw new RuntimeException(e);
            	/*类与类之间 进行数据交换时,可以使用return返回值。也可以使用自定义异常返回值,调用者try{}
            	catch(e){ e.getMessage() 获得需要的数据}
            	此处可以编写自定义异常。
            */
            //throw new MyConnectionException(e);
            }
        }
    }
    
  3. service层

    public class AccountService {
        /**
        * 事务管理方式:向下传递Connection。有侵入性。使用DBUtils
        * 业务层事务管理转账的方法
        * @param from
        * @param to
        * @param money
        */
        public void transfer(String from, String to, double money) {
            //调用dao层
            AccountDao accountDao = new AccountDao();//DBUtils进行事务处理的原理,是在Service层获得连接,以保证事务处理过程中的Connection对象为同一
            个Connection。
    //因为必须保证连接为同一个连接,所以在业务层获得连接,再将连接传递到持久层,代码具有侵入性。
            //DBUtils使用的方法
            Connection conn = null;
            try {
            	//获得连接
            	conn = C3P0Utils.getConnection();
            	//设置事务不自动提交
                conn.setAutoCommit(false);
            	//调用持久层
            	accountDao.outMoney(from,money);
            	//如果有异常
                //int a = 1 / 0 ;
            	accountDao.inMoney(to,money);
            	//提交事务,并安静的关闭连接
            	DbUtils.commitAndCloseQuietly(conn);
            } catch (SQLException e) {
            	//有异常出现时,回滚事务,并安静的关闭连接
            	DbUtils.rollbackAndCloseQuietly(conn);
            	e.printStackTrace();
            }
        }
    }
    
  4. dao层

    public class AccountDao {
        /**
        * 付款方法
        * @param from 付款人
        * @param money 金额
        */
        public void outMoney(String from, double money) {
            QueryRunner qr = new QueryRunner();
            try {
            Connection conn = C3P0Utils.getConnection();
            String sql = "update account set money = money - ? where name = ?";
            qr.update(conn, sql, money,from);
            } catch (SQLException e) {
            e.printStackTrace();
            }
            }
            /**
            * 收款方法
            * @param to 收款人
            * @param money 金额
            */
            public void inMoney(String to, double money) {
            QueryRunner qr = new QueryRunner();
            try {
            Connection conn = C3P0Utils.getConnection();
            String sql = "update account set money = money + ? where name = ?";
            qr.update(conn, sql, money,to);
            } catch (SQLException e) {
            e.printStackTrace();
            }
        }
    }
    

第三章 事务总结

3.1 事务特性:ACID

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

3.2 并发访问问题

  1. 如果不考虑隔离性,事务存在3中并发访问问题。
    • 脏读 :一个事务读到了另一个事务未提交的数据.
    • 不可重复读 :一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
    • 虚读 /幻读 :一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

3.3 隔离级别:解决问题

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

3.4 演示演示

  1. 隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】

  2. 脏读图解:
    在这里插入图片描述

  3. 不可重复读图解:
    在这里插入图片描述

  4. 虚读幻读图解:
    在这里插入图片描述

  5. 查询数据库的隔离级别

    show variables like '%isolation%';select @@tx_isolation;
    

在这里插入图片描述

  1. 设置数据库的隔离级别

    • set session transactionisolation level 级别字符串
    • 级别字符串: readuncommitted 、 read committed 、 repeatable read 、 serializable
    • 例如: set session transaction isolation level read uncommitted;
  2. 读未提交:readuncommitted

    • A窗口设置隔离级别
      • AB同时开始事务
      • A 查询
      • B 更新,但不提交
      • A 再查询?-- 查询到了未提交的数据
      • B 回滚
      • A 再查询?-- 查询到事务开始前数据
  3. 读已提交:read committed

    • A窗口设置隔离级别
      • AB同时开启事务
      • A查询
      • B更新、但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据改变,存在问题【不可重复读】
  4. 可重复读:repeatable read

    • A窗口设置隔离级别
      • AB 同时开启事务
      • A查询
      • B更新, 但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据不变,解决问题【不可重复读】
      • A提交或回滚
      • A再查询?–数据改变,另一个事务
  5. 串行化:serializable

    • A窗口设置隔离级别
    • AB同时开启事务
    • A查询
      • B更新?–等待(如果A没有进一步操作,B将等待超时)
      • A回滚
      • B 窗口?–等待结束,可以进行操作
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值