事务总结

一、 事务概述

  1. 什么是事务
    事务中的多个操作,要么完全成功,要么完全失败!
    以张三转10000块到李四的账户为例:
    给张三的账户减去10000元;
    给李四的账户加上10000元。

  2. 事务的四大特性是:
    l 原子性(Atomicity):事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
    l 一致性(Consistency):事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
    l 隔离性(Isolation):隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
    l 持久性(Durability):一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。

  3. 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个等级的事务隔离级别,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的。

  1. SERIALIZABLE(串行化)
    不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;
    性能最差;

  2. REPEATABLE READ(可重复读)(MySQL)
    防止脏读和不可重复读,不能处理幻读问题;
    性能比SERIALIZABLE好

  3. READ COMMITTED(读已提交数据)(Oracle)
    防止脏读,没有处理不可重复读,也没有处理幻读;
    性能比REPEATABLE READ好

  4. 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层逻辑:

  1. JdbcUtils.beginTranscation(); 通知JdbcUtils开启事务
    执行JdbcUtils类中:
    beginTranscation()方法:通过ThreadLocal对象,判断当前线程对应的Connection对象是否为空,如果不为空,说明当前线程已经开启了事务,并返回对应的Connection对象。若当前线程对应的Connection为空,则在Connection连接池中拿出一个赋给Connection对象。
    getConnection()方法:判断当前线程对应的Connection对象是否为空,若为空,说明没有开启事务,从线程池中拿出一个Connection对象。若当前线程对应的Connection对象不为空,说明已经开启了事务,返回当前线程对应的Connection对象。
  2. dao.updateBalance(from, -balance);调用dao对象的增方法
    执行Dao层中:updateBalance(String name, double balance)方法:通过JdbcUtils的getConnection()方法得到Connection对象,传入到qr.update(con, sql, balance. name)中。
  3. JdbcUtils.commitTransaction();
    执行JdbcUtils类中:
    commitTransaction()方法:判断当前线程对应的Connection对象是否为空,若为空,说明已经提交了或者没有开启事务(因为是在beginTranscation()方法中,才给当前线程对应的Connection赋值,如果没有开启事务,线程对应的Connection对象是一直为空的),抛异常。否则回滚事务,提交事务,把当前线程对应的Connection对象移除。

注:参考崔希凡老师的文档

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值