springboot入门——数据库事务的隔离级别及传播行为

事务处理

Spring中,数据库事务是通过AOP技术来提供服务的

声明式事务标注:@Transactional

public class UserServiceImpl im...{
	...
	@Override
	//一旦sql执行异常,事务自动回滚
	@Transactional
	public int insertUser(User user){
		return userDao.isertUser(user);
	}
	...
}

@Transactional注解有很多可配置的参数,这里只讨论isolation(隔离级别)和propagation(传播行为)

隔离级别

我们知道事务的四个特征(ACID):原子性、一致性、隔离性、持久性
由于互联网常面对高并发场景,隔离性是需掌握的重点。
隔离性:多个事务同时发生,有可能造成数据丢失更新(也就是数据不准)。

为了应对隔离性有可能带来的问题,推出了四种隔离级别

  1. 未提交读
    两个同时进行的事务,在a事务数据未提交时,b事务就能读取到a事务带来的数据上的变化,这种隔离级别会造成脏读
  2. 读写提交
    两个同时进行的事务,b事务只能读取a事务提交后的已变动数据,不能读取a事务提交前已变动的数据,但可以读取数据库原有数据。(比如数据库原有5,a事务向其中插入3,在a事务插入之后且提交之前,b事务只能读取原有的5,在提交之后读取的是8),我们可以看到括号中出现了这样的现象,由于我们无法控制b事务的读取点是在a事务提交前还是提交后,b事务的读取操作有可能出现两种结果(5或者8),这种现象我们称为:不可重复读。这是“读写提交”这种隔离级别会带来的问题。
  3. 可重复读
    该隔离级别的建立就是为了克服上边隔离级别出现的“不可重复读”问题。如果b事务在a事务提交前读的话,系统会禁止其读取,直到a事务提交后,系统才会允许b事务进行读取。这样就解决了b事务读取时会出现两种结果的问题。与此无关,还有一个可能出现的问题:幻读。幻读不像上边两种问题那样针对单条数据(解决脏读、不可重复读问题的方法其实是在单条数据上加了锁),它是面向多条数据产生的问题,比如:在a事务开始后且执行sql前,b事务读取了数据的count()总条数,a事务结束后,b事务再次读取数据的count()总条数时,两次读取的结果不一样。
  4. 序列化
    事务会按照顺序,一个一个的执行。它不会出现脏读、不可重复读、幻读问题。

spring中可以通过isolation参数来设置事务的隔离级别,也可以通过配置文件来设置全局的事务隔离级别。

@Transactional(isolation = Isolation.SERIALIZABLE)

spring采取的默认隔离级别是跟数据库相关的,如:mysql的默认隔离级别为可重复读

传播行为

方法之间调用,事务采取的策略问题
比如说批量任务,如果批量任务在同一普通事务中,一旦有一个执行异常,就会全部回滚,包括那些已经正常执行了的任务,这会耗费大量资源,肯定不是我们想要的效果。我们把批量任务看做一个事务,把其中的单个任务看做一个新的子方法,让子方法不在当前事务中执行,而是创建了一个新的事物去执行子方法,这样即使子方法异常,回滚的也只是当前子方法的事务。
传播行为有7种,这里只讲常用的3种:

  1. REQUIRED——默认的传播行为。如果当前存在事务则沿用,否则新建事务运行子方法
  2. REQUIRES_NEW——无论是否存在事务,都新建新事物运行方法
  3. NESTED——子方法异常时,只回滚子方法执行过的sql,不回滚当前方法的事务。它是利用了数据库的保存点(标志位)技术,在sql语句中存在保存点,在出错时,回滚到上一个保存点的位置,而不是全部回滚。有的数据库有这种技术,而有的则没有,Spring规定有则用,没有则自动使用REQUIRES_NEW的传播行为。

@Transactional自调用失效问题

...
@Override
@Transactional(propagetion = Progagation.REQUIRED)
public int insertUsers(List<User> userList){
	...
	for...{
		...
		insertUser(user);
		...
	}
}

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertUser(User user){
	return userDao.insertUser(user);
}

通常我们是通过A类去调用B类的方法的,而不是像图中这样调用本类的方法,这样做是没有效果的,因为:
Spring事务是依靠AOP实现的,在A类的被代理对象的方法中去调用B类代理对象的方法,才可以实现。而像上边代码那样,A类的被代理对象方法中,调用了自身的一个方法,这个方法只是单纯的一个方法而已,并没有经过@Transactional实现AOP(是说这个方法没有实现AOP,因为实现AOP是需要代理类的,A类的第一个方法我们在调用时可以看做A类代理类,第二个方法在这里只是单纯的一个方法。当然我们可以通过再获取一个A类(第二个方法)视为代理类,这样就相当于一个代理类的被代理对象方法内调用另一个代理类对象的方法)

public class A implements ...{
private Application applicationCxt = null;

//利用bean的生命周期中的接口,获取applicationContext上下文对象
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws ...{
	this.applicationCxt  = applicationContext;
}

@Override
@Transactional(propagetion = Progagation.REQUIRED)
public int insertUsers(List<User> userList){
	...
	//从Ioc容器中,取出另一个A的代理类对象
	A ontherA = applicationCxt  .getBean(A.class);
	for...{
		...
		ontherA.insertUser(user);
		...
	}
}

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertUser(User user){
	return userDao.insertUser(user);
}

}

这种方式尽量不要用,毕竟他侵入了Spring的代码。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

缔曦_deacy

码字不易,请多支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值