事务概述:
事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全部失败
事务作用:保证在一个事务中多次SQL操作要么全部成功,要么全部失败.
Mysql操作事务
典型的编程式事务:
# 创建一个表:账户表. 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语句提交一次事务。
手动提交:先开启,再提交
手动提交:
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
jdbc事务操作
public void demo01() throws SQLException{ // 获得连接 Connection conn = null; try { //#1 开始事务 conn.setAutoCommit(false); //.... 加钱 ,减钱 //#2 提交事务 conn.commit(); } catch (Exception e) { //#3 回滚事务 conn.rollback(); Connection对象的方法名 描述 conn.setAutoCommit(false) 开启事务 new QueryRunner() 创建核心类,不设置数据源(手动管理连接) query(conn , sql , handler, params ) 或 update(conn, sql , params) 手动传递连接, 执行SQL语句CRUD DbUtils.commitAndCloseQuietly(conn) 提交并关闭连接,不抛异常 DbUtils.rollbackAndCloseQuietly(conn) 回滚并关闭连接,不抛异常 1.3 DBUtils事务操作 代码演示 1.4 案例:JDBC事务分层(dao、service)传递Connection 分析 } finally{ // 释放资源 conn.close(); } }
DBUtils事务操作
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(); } }
JDBC事务分层(dao、service)传递Connection
开发中,常使用分层思想
不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
不同层级结构彼此平等
分层的目的是:
解耦
可维护性
可扩展性
可重用性
不同层次,使用不同的包表示
com.qingmu 公司域名倒写
com.qingmu.dao dao层
com.qingmu.service service层
com.qingmu.domain javabean
com.qingmu.utils 工具
工具类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> 步骤1:编写入口程序 service层 <!-- 连接参数 --> <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(); } } }
事务总结:
事务的四种特性:ACID
原子性:原子性是指事务是一个不可分割的工作单位,事务中的操作要么都成功,要么都不成功.
隔离性:事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间的数据要相互隔离.
持久性:持久性指的是一个事务一旦被提交,他对数据库中数据的改变就是永久性的,接下来及时数据库发生故障也不应该对其有任何影响.
一致性:事务前后数据的完整性必须保持一致.
并发访问问题:
如果不考虑隔离性,事务存在3中并发访问问题.
1.脏读:一个事务读到了另一个事务未提交的数据
2.不可重复读:一个事务读到了另一个事务已经提交了的数据,引发另一个事务,在十五中的多次查询不一致.
3.虚读/幻读:一个事务读到了另一个事务已经提交的数据,导致另一个事务,在事务中多次查询的结果不一致.
隔离级别:解决问题
数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a)存在:3个问题(脏读、不可重复读、虚读)。
b)解决:0个问题
2. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a)存在:2个问题(不可重复读、虚读)。
b)解决:1个问题(脏读)
3. repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a)存在:1个问题(虚读)。
b)解决:2个问题(脏读、不可重复读)
1. 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
演示演示
隔离级别演示参考:资料/隔离级别操作过程.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 窗口?--等待结束,可以进行操作