事务是一件完整的事情,可以包含多个操作单元,这些操作单元要么全部成功,要么全部失败。
MySQL中的事务
MySQL的默认事务是自动提交的。 开启自动事务:
- 关闭自动事务
sql
set autocommit = off; --针对当前会话有效
- 开启一个手动事务
sql
start transaction;--开启事务
...
...
commit;--提交
rollback;--回滚
注意:
oracle是手动事务 事务必须提交或者回滚
创建数据库: create database day19; use day19; create table account( name varchar(20), money int ); insert into account values (‘a’,2000); insert into account values (‘b’,2000);
java中的事务控制
connection连接的api
void setAutocommit() //开启事务
void commit() //提交事务
void rollback() //回滚
void rollback(Savepoint sp) //还原到指定的还原点
Savepoint setSavepoint()//设置还原点
步骤分析:
- 数据库和表
- 创建一个项目 导入jar包 驱动,c3p0,dbUtils 工具类、配置文件 datasourceutils c3p0.properties
- 编写一个account.jsp 需要有汇款人 收款人 金额 点击转账或者提交的时候提交到servlet(AccountServlet)
- AccountServlet操作: 获取三个参数 调用service完成转账 打印信息
- service操作 编写一个转账方法 account() 给汇款人减钱,给收款人加钱。 在service层需要控制事务, 若操作全部成功,则提交事务; 若有异常,则事务回滚
需要在service层使用connection对象开始事务,为了保证所有操作在一个事务中,必须保证在service层和dao层所使用的connection是同一个。 方案一:把connection向下传递 方法二:ThreadLocal将使用的连接和当前线程绑定
set(T value) 将value和当前线程绑定 等价于map.put(Thread.currentThread,value); T get() 获取和当前线程绑定的内容 等价于map.get(Thread.currentThread); remove() 解绑和当前线程绑定的内容 等价于map.remove(Thread.currentThread);
在service层绑定,在dao层获取,在service层finally块中释放
使用DBUtils控制事务
三个核心对象:
QueryRunner 操作sql语句
DBUtiles 控制事务,释放资源
ResultSetHandler 封装结果集
QueryRunner: 构造方法:
new QueryRunner(); //手动事务,调用方法的时候,需要手动传入一个connection对象,conn需要手动释放资源,手动管理事务
update(Connection conn,String sql ...);
query(Connection conn,String sql ...);
事务的总结:
事务的特性: ACID
- 原子性:事务里面的操作不可分割,要么全部成功,要么全部失败。
- 一致性:事务提交或回滚之后的状态要和其他的业务状态保持一致。
- 隔离性:一个事务的操作尽量不要受其他事务的干扰。
- 持久性:一旦事务提交或者回滚,终要将数据持久化保存到数据库。
不考虑隔离性出现的读问题:
- 脏读:一个事物读取到另一个事务没有提交的数据
- 不可重复读:在一个事务中查询两次结果不一致(针对update操作)
- 幻读(虚读):在一个事务中,两次查询结果不一致。(针对insert操作)
通过设置数据库的隔离级别可以避免一些问题的发生:
-
隔离级别:
- read uncommitted : 读未提交 三个问题全存在
- read committed : 读提交,可避免脏读(Oracle默认隔离级别)
- repeatable read : 可重复读 避免脏读不可重复读(MySQL默认隔离级别)
- serializable : 串行化 可避免所有问题