文章目录
第一章 事务操作
- 事务概述
- 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
- 事务作用:保证在一个事务中多次SQL操作要么全都成功,要么全都失败.
1.1 mysql事务操作
- 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事务操作
- jdbc事务名图表:
Connection 对象的方法名 | 描述 |
---|---|
conn.setAutoCommit(false) | 开启事务 |
conn.commit() | 提交事务 |
conn.rollback() | 回滚事务 |
-
代码演示:
//事务模板代码 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事务操作
-
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
1.4.1 分析
-
开发中,常使用分层思想
- 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
-
不同层级结构彼此平等
-
分层的目的是:
- 解耦
- 可维护性
- 可扩展性
- 可重用性
-
不同层次,使用不同的包表示
- com.itheima 公司域名倒写
- com.itheima.dao dao层
- com.itheima.service service层
- com.itheima.domain javabean
- com.itheima.utils 工具
-
三层思想图解:
1.4.2 代码实现
-
工具类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(); } }
-
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>
-
步骤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(); } } }
-
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(); } } }
-
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 分析
- 在“事务传递Connection参数案例”中,我们必须传递Connection对象,才可以完成整个事务操作。如果不传递参数,是否可以完成?在JDK中给我们提供了一个工具类:ThreadLocal ,此类可以在一个线程中共享数据。
- java.lang.ThreadLocal 该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。
2.2 相关知识:ThreadLocal
-
java.lang.ThreadLocal 该类提供了线程局部(thread-local) 变量,用于在当前线程中共享数据。ThreadLocal 工具类底层就是相当于一个Map,key存放的当前线程,value存放需要共享的数据。
-
举例:
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(); } }
-
结论:向 ThreadLocal 对象中添加的数据只能在当前线程下使用。
2.3 结合案例使用
2.3.1 分析
2.3.2 代码实现
-
ThreadLocal优化程图解:
-
工具类
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); } } }
-
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(); } } }
-
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
- 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency):事务前后数据的完整性必须保持一致。
- 隔离性(Isolation):事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
- 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
3.2 并发访问问题
- 如果不考虑隔离性,事务存在3中并发访问问题。
- 脏读 :一个事务读到了另一个事务未提交的数据.
- 不可重复读 :一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
- 虚读 /幻读 :一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
3.3 隔离级别:解决问题
- 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
- read uncommitted :读未提交,一个事务读到另一个事务没有提交的数据。
- a)存在:3个问题(脏读、不可重复读、虚读)。
- b)解决:0个问题
- read committed :读已提交,一个事务读到另一个事务已经提交的数据。
- a)存在:2个问题(不可重复读、虚读)。
- b)解决:1个问题(脏读)
- repeatable read :可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
- a)存在:1个问题(虚读)。
- b)解决:2个问题(脏读、不可重复读)
- serializable :串行化,同时只能执行一个事务,相当于事务中的单线程。
- a)存在:0个问题。
- b)解决:3个问题(脏读、不可重复读、虚读)
- read uncommitted :读未提交,一个事务读到另一个事务没有提交的数据。
- 安全和性能对比
- 安全性: 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 再查询?-- 查询到事务开始前数据
- A窗口设置隔离级别
-
读已提交:read committed
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新、但不提交
- A再查询?–数据不变,解决问题【脏读】
- B提交
- A再查询?–数据改变,存在问题【不可重复读】
- A窗口设置隔离级别
-
可重复读:repeatable read
- A窗口设置隔离级别
- AB 同时开启事务
- A查询
- B更新, 但不提交
- A再查询?–数据不变,解决问题【脏读】
- B提交
- A再查询?–数据不变,解决问题【不可重复读】
- A提交或回滚
- A再查询?–数据改变,另一个事务
- A窗口设置隔离级别
-
串行化:serializable
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新?–等待(如果A没有进一步操作,B将等待超时)
- A回滚
- B 窗口?–等待结束,可以进行操作