一、 事务概述
-
什么是事务
事务中的多个操作,要么完全成功,要么完全失败!
以张三转10000块到李四的账户为例:
给张三的账户减去10000元;
给李四的账户加上10000元。 -
事务的四大特性是:
l 原子性(Atomicity):事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
l 一致性(Consistency):事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
l 隔离性(Isolation):隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
l 持久性(Durability):一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。 -
MySQL中的事务
在默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务。
l 开启事务:start transaction;
l 结束事务:commit或rollback。
在执行SQL语句之前,先执行strat transaction,这就开启了一个事务(事务的起点),然后可以去执行多条SQL语句,最后要结束事务,commit表示提交,即事务中的多条SQL语句所做出的影响会持久化到数据库中。或者rollback,表示回滚,即回滚到事务的起点,之前做的所有操作都被撤消了!
二、 JDBC事务
在jdbc中处理事务,都是通过Connection完成的!
同一事务中所有的操作,都在使用同一个Connection对象!
jdbc处理事务的代码格式:
try {
con.setAutoCommit(false);//开启事务…
….
…
con.commit();//try的最后提交事务
} catch() {
con.rollback();//回滚事务
}
1.事务的并发读问题
脏读(dirty read):读到另一个事务的未提交更新数据,即读取到了脏数据;
不可重复读(unrepeatable read):对同一记录的两次读取不一致,因为另一事务对该记录做了修改;
幻读(虚读)(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录;
2.四大隔离级别
4个等级的事务隔离级别,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的。
-
SERIALIZABLE(串行化)
不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;
性能最差; -
REPEATABLE READ(可重复读)(MySQL)
防止脏读和不可重复读,不能处理幻读问题;
性能比SERIALIZABLE好 -
READ COMMITTED(读已提交数据)(Oracle)
防止脏读,没有处理不可重复读,也没有处理幻读;
性能比REPEATABLE READ好 -
READ UNCOMMITTED(读未提交数据)
可能出现任何事务并发问题
性能最好
三、Service事务
在Service中调用了JdbcUtils.beginTransaction()方法时,JdbcUtils要做准备好一个已经调用了setAuthCommitted(false)方法的Connection对象,因为在Service中调用JdbcUtils.beginTransaction()之后,马上就会调用DAO的方法,而在DAO方法中会调用JdbcUtils.getConnection()方法。这说明JdbcUtils要在getConnection()方法中返回刚刚准备好的,已经设置了手动提交的Connection对象。
public class JdbcUtils {
private static DataSource dataSource = new ComboPooledDataSource();
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
public static DataSource getDataSource() {
return dataSource;
}
public static Connection getConnection() throws SQLException {
Connection con = tl.get();
if(con == null) {
return dataSource.getConnection();
}
return con;
}
public static void beginTranscation() throws SQLException {
Connection con = tl.get();
if(con != null) {
throw new SQLException("事务已经开启,在没有结束当前事务时,不能再开启事务!");
}
con = dataSource.getConnection();
con.setAutoCommit(false);
tl.set(con);
}
public static void commitTransaction() throws SQLException {
Connection con = tl.get();
if(con == null) {
throw new SQLException("当前没有事务,所以不能提交事务!");
}
con.commit();
con.close();
tl.remove();
}
public static void rollbackTransaction() throws SQLException {
Connection con = tl.get();
if(con == null) {
throw new SQLException("当前没有事务,所以不能回滚事务!");
}
con.rollback();
con.close();
tl.remove();
}
}
转账示例:
public class AccountDao {
public void updateBalance(String name, double balance) throws SQLException {
String sql = "update account set balance=balance+? where name=?";
Connection con = JdbcUtils.getConnection();
QueryRunner qr = new QueryRunner();
qr.update(con, sql, balance, name);
}
}
public class AccountService {
private AccountDao dao = new AccountDao();
public void transfer(String from, String to, double balance) {
try {
JdbcUtils.beginTranscation();
dao.updateBalance(from, -balance);
dao.updateBalance(to, balance);
JdbcUtils.commitTransaction();
} catch(Exception e) {
try {
JdbcUtils.rollbackTransaction();
} catch (SQLException e1) {
throw new RuntimeException(e);
}
}
}
}
AccountService as = new AccountService();as.transfer("zs", "ls", 100);
service层逻辑:
- JdbcUtils.beginTranscation(); 通知JdbcUtils开启事务
执行JdbcUtils类中:
beginTranscation()方法:通过ThreadLocal对象,判断当前线程对应的Connection对象是否为空,如果不为空,说明当前线程已经开启了事务,并返回对应的Connection对象。若当前线程对应的Connection为空,则在Connection连接池中拿出一个赋给Connection对象。
getConnection()方法:判断当前线程对应的Connection对象是否为空,若为空,说明没有开启事务,从线程池中拿出一个Connection对象。若当前线程对应的Connection对象不为空,说明已经开启了事务,返回当前线程对应的Connection对象。 - dao.updateBalance(from, -balance);调用dao对象的增方法
执行Dao层中:updateBalance(String name, double balance)方法:通过JdbcUtils的getConnection()方法得到Connection对象,传入到qr.update(con, sql, balance. name)中。 - JdbcUtils.commitTransaction();
执行JdbcUtils类中:
commitTransaction()方法:判断当前线程对应的Connection对象是否为空,若为空,说明已经提交了或者没有开启事务(因为是在beginTranscation()方法中,才给当前线程对应的Connection赋值,如果没有开启事务,线程对应的Connection对象是一直为空的),抛异常。否则回滚事务,提交事务,把当前线程对应的Connection对象移除。
注:参考崔希凡老师的文档