文章目录
- 1 事务
- 1.1 事务概述
- 1.2 事务管理过程
- 1.3 事务特性ACID
- 2 事务隔离问题
- 2.1 脏读
- 2.2 不可重复读
- 2.3 幻读(虚读)
- 3 数据库的隔离级别
- 4 java中的事务管理
- 4.1 JDBC添加事务
- 4.2 DbUtils事务操作
- 5 Java经典三层架构
1 事务
1.1 事务概述
一组sql语句(insert、update、delete),全部成功整体才算成功,一个失败整体也算失败。
举个例子:
a 和 b的账户中都有1000元,a给b转账100元,a 转账完成后会是什么结果?
正常情况下:
update t_account set money = money -100 where name='a';
update t_account set money = money +100 where name='b';
结果: a=900; b=1100
异常情况下:
update t_account set money = money -100 where name='a';
发生异常;
update t_account set money = money +100 where name='b';
结果: a=900; b=1000
事务的出现 解决上面的问题。 特点:要么全成功,要么全失败。
1.2 事务管理过程
1.3 事务特性ACID
事务是并发控制的基本单元。所谓事务一个sql语句操作序列,这些操作要么都执行,要么都不执行,他是一个不可分割的工作单元。
例如:银行转账工作,从一个帐号扣款并使另一个帐号增款,这个两个操作,要么都执行,要么都不执行。
数据库的事务必须具备ACID特性,ACID是指 Atomic(原子性)、Consistensy(一致性)、Isolation(隔离型)和Durability(持久性)的英文缩写。
1、原子性(Atomicity)
一个事务中所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中如果发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行一样。
2、一致性(Consistency)
一个事务在执行之前和执行之后 数据库都必须处于一致性状态。
如果事务成功的完成,那么数据库的所有变化将生效。
如果事务执行出现错误,那么数据库的所有变化将会被回滚(撤销),返回到原始状态。
3、持久性:
指一个事务一旦被提交,它对数据库的改变将是永久性的,接下来即使数据库发生故障也不会对数据产生影响。
4、隔离性(Isolation)
多个用户并发的访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发的事务之间要相互隔离。
多个事务事件是相互独立的,多个事务事件不能相互干扰。
2 事务隔离问题
如果不考虑事务的隔离型,由于事务的并发,将会出现以下问题:
- 脏读 – 最严重,杜绝发生
- 不可重复读
- 幻读(虚读)
2.1 脏读
指一个事务读取了另外一个事务未提交的数据
2.2 不可重复读
在一个事务内多次读取表中的数据,多次读取的结果不同
注意:
和脏读的区别: 不可重复读是读取的已提交数据
2.3 幻读(虚读)
一个事务 读取 另一个事务 已经提交的数据,强调的是 记录数 的变化,常有sql类型为 insert和 delete。
虚读和不可重复读的区别:
虚读 强调的是数据表 记录数 的变化,主要是 insert 和 delete 语句。
不可重复读 强调的是数据表 内容 的变化,主要是 update 语句。
3 数据库的隔离级别
数据库共定义了4种隔离级别(限制由高到低, 性能从低到高):
serializable(串行化):避免 脏读、不可重复读、虚读情况的发生。(—— 最高限制,性能最低)
repeatable read(可重复读):可避免 脏读、不可重复读, 不可避免虚读。mysql采用可重复读。
read committed(读已提交):可避免 脏读,不可避免不可重复读、虚读。oracle采用读已提交。
read uncommitted(读未提交):不可避免 脏读、不可重复读、虚读。(—— 最低限制,性能最高)
mysql 默认选择的可重复读 – repeatable read
oracle 选择 读已提交 – read committed
4 java中的事务管理
4.1 JDBC添加事务
* JDBC实现转账,保证数据安全
* 使用事务技术
* 需求: tom -1000 , jerry+1000
*
* Connection连接对象方法:
* void setAutoCommit(false)设置事务的提交方式,传false阻止自动提交
*
* commit() 提交事务
*
* rollback() 回滚
public class JdbcTransaction {
public static void main(String[] args) {
Connection con = null;
PreparedStatement stmt = null;
try {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接对象
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "root", "root");
//阻止事务自动提交,开启事务
con.setAutoCommit(false);
//拼写tom-1000的SQL语句
String sqlOut = "update account set money = money - 1000 where name = 'tom'";
//获取SQL语句执行对象
stmt = con.prepareStatement(sqlOut);
//执行SQL
stmt.executeUpdate();
//int a = 1 / 0;
//拼写jerry+1000的SQL语句
String sqlIn = "update account set money = money + 1000 where name = 'jerry'";
stmt = con.prepareStatement(sqlIn);
stmt .executeUpdate();
//提交事务
con.commit();
}catch (Exception ex){
//程序执行catch,出现异常,SQL语句执行失败,回滚
ex.printStackTrace();
try {
//回滚
con.rollback();
}catch (Exception e){e.printStackTrace();}
}finally {
//释放资源
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.2 DbUtils事务操作
Connection对象的方法名 | 描述 |
conn.setAutoCommit(false) | 开启事务 |
new QueryRunner() | 创建核心类,不设置数据源(手动管理连接) |
query(conn , sql , handler, params ) 或 update(conn, sql , params) | 手动传递连接, 执行SQL语句CRUD |
DbUtils.commitAndCloseQuietly(conn) | 提交并关闭连接,不抛异常 |
DbUtils.rollbackAndCloseQuietly(conn) | 回滚并关闭连接,不抛异常 |
注意:
使用无参的QueryRunner的构造方法,只有获取了连接对象才可以对事务进行管理和控制,我们自己控制Connection连接。
案例介绍:
dao层:访问数据库
/**
* 对数据表account的操作
*/
public class AccountDao {
/**
* 定义方法,实现转账,更新账户的数据
* 传递参数,用户名,余额
*/
public void updateAccount(String name, double money , Connection con) throws SQLException {
//事务,不能传递连接池
QueryRunner qr = new QueryRunner();
//拼写更新的SQL
String sql = "update account set money = ? where name = ?";
qr.update(con,sql,money,name);
}
/**
* 定义方法: 查账户信息
* 传递参数,账户名,用户输入的
* 查询后结果
* BeanHandler封装, 结果集就是Account对象
*/
public Account queryAccount(String name, Connection con)throws SQLException{
//事务,不能传递连接池
QueryRunner qr = new QueryRunner();
//拼写查询账户的SQL
String sql = "select * from account where name = ?";
//执行SQL语句,封装为Account对象
Account account = qr.query(con,sql,new BeanHandler<Account>(Account.class),name);
return account;
}
}
service层,对数据进行运算
public class AccountService {
/**
* 创建方法,实现转账
* 参数,付款人,收款人,金额
*/
public void transfer(String fukuan, String shoukuan,double money){
//调用dao层,先查询账户的余额
AccountDao accountDao = new AccountDao();
Connection con = null;
try {
//业务层获取数据库连接对象
con = DruidUtils.getConnection();
//修改隔离级别con.setTransactionIsolation(Connection接口静态成员变量);
//开启事务,阻止自动提交
con.setAutoCommit(false);
//dao层方法,查询账户,付款人
Account accountFukuan = accountDao.queryAccount(fukuan, con);
//dao层方法,查询账户,收款人
Account accountShoukuan = accountDao.queryAccount(shoukuan, con);
System.out.println(accountFukuan);//Account(id=1, name=tom, money=10000.0)
System.out.println(accountShoukuan);//Account(id=2, name=jerry, money=10000.0)
//accountFukuan的余额 - 1000
accountFukuan.setMoney( accountFukuan.getMoney() - money);
//accountShoukuan的余额 + 1000
accountShoukuan.setMoney( accountShoukuan.getMoney() + money );
//调用dao层方法update转账
accountDao.updateAccount(fukuan, accountFukuan.getMoney(),con );
//int a = 1/0;
accountDao.updateAccount(shoukuan,accountShoukuan.getMoney(),con);
//SQL语句执行成功,提交事务
//DBUtils工具类,提交事务并释放资源
DbUtils.commitAndCloseQuietly(con);
}catch (SQLException ex){
ex.printStackTrace();
//SQL执行失败,回滚
// DBUtils工具类,回滚事务并释放资源
DbUtils.rollbackAndCloseQuietly(con);
}finally {
// DBUtils工具类,释放资源
DbUtils.closeQuietly(con);
}
}
}
5 Java经典三层架构