为什么要使用事务?
Transaction(事务),是我们在操作数据库中必不可少的一个东西,在数据安全方面有重要作用。
那么为什么要用事务呢?事务的作用简单举个最常见的转账例子,
A账户给B账户转账1元钱,在这种交易的过程中,数据安全问题值得思考: 如何同时保证上述交易中,A账户总金额减少一元,B账户总金额增加一元?假设A转完账了,B账户还没来得及增加,但是这时服务器崩溃了,那么就会发生A减少了1元,B没有增加1元的数据错误问题。而事务就是为了解决这些问题而产生的。上述问题靠的是事务的原子性解决,即要么全部成功,要么全部取消。如果事务失败,则回滚到开始状态。除了原子性,还有隔离,持久,一致性等三大特性,相信大家都已经知道了,这里就不详细赘述了。
如何使用事务?
回到我们今天文章的重点,既然事务是我们跟数据库打交道时的常用的核心功能,我们如何使用呢?这里以最常用的SpringBoot框架为例,教大家如何快速上手使用和日常开发中常见的问题解决。
SpringBoot大大简化了事务的使用,不需要像以前MVC一样去大量配置XML文件,只需要通过注解即可使用事务,而且不需要引入额外的Jar包。
这里注意一点,很多文章说需要在主启动类添加开启事务的注解@EnableTransactionManagement,声明一下,对SpringBoot来说,其实是不需要的。
SpringBoot通过自动配置,已经在项目启动时给我们注入了事务管理器。这里简单看下,我们在自动配置加载文件中,搜索事务的自动配置类,点进去可以看到当缺失事务管理Bean的时候,会注入默认的事务管理器Bean。看到这,我们就会联想到如果需要配置多数据源,或者更换默认的事务管理器,就可以指定自己的Bean,而SpringBoot就不会自动装配默认Bean,这里也可以看出SpringBoot自动配置的精髓了,妙不可言。这里就不详细展开了,不是我们此篇的重点,大家在使用时记得有这么一回事就行了。
前面说了那么多,其实就是告诉大家一句话,SpringBoot默认已经具备事务管理的功能了,不需要引入额外的jar包,也不需要在主启动类添加启用注解,我们可以直接使用注解@Transactional来开启事务。
@Transactional具体如何使用
那么注解@Transactional如何使用呢?很简单,只需要在需要开启事务的类或者方法上,添加此注解即可。添加在类上,则开启此类中的所有方法的事务。
下面我们说下在@Transactional注解使用过程中的一些常见问题和细节:
- @Transactional注解一般用在哪一层?
我们既可以添加在Controller层,也可以添加在Service层,甚至可以在Dao层等。但是通常我们是在Service层使用注解的。
- 在Controller添加事务,通常对系统性能要求比较高,比如我们在Controller层调用Service失败了,立马就要回滚,但是实际上Service层会有多步操作,许多操作不那么重要,不需要回滚。而加在Controoler层,相当于在入口就回滚,对系统安全性等要求很高才需要。
- 在Dao层添加事务,针对的就是单个操作回滚,对安全性能要求比较低。
- 在Service添加事务,针对的是多个操作组合回滚,也就是我们上文提到的为什么要使用事务时举的转账例子。我们通常是根据业务情况,来组合几个数据库操作。
所以在Service开启事务,我们可以更合理的规划事务,根据实际情况来针对数据库组合操作来开启事务。
- @Transactional的rollbackFor属性
@Transactional注解什么时候生效回滚呢?这个是由rollbackFor属性控制的。默认是rollbackFor=RuntimeException.class,即发生运行时异常回滚。通常我们不管是运行时异常还是受检异常,都需要进行回滚,所以一般我们会设置rollbackFor = Exception.class。即发生任何类型异常,都进行回滚。
注意,如果异常被我们用try-catch捕获,而没有继续抛出,事务是不进行回滚的。
如果需要回滚生效,就需要在catch中继续抛出异常,或者通过代码手动进行回滚。
@Transactional(rollbackFor = Exception.class)
public void test(){
try{
...
}catch{
...
//继续抛出异常回滚
throw new Exception();
}
}
@Transactional(rollbackFor = Exception.class)
public void test(){
try{
...
}catch{
...
//手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
- @Transactional的propagation属性
propagation属性是控制事务的传播行为的,有如下几个值:
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
首先默认值是REQUIRED。这个是什么意思呢?就是标注此注解的方法,默认开启事务。 比如方法A调用了标注@Transactional(propagation = Propagation.REQUIRED)的方法B,那么有两种情况:
- 情况1:A本身有一个事务,那么此时B就会加入A的事务。
- 情况2:A本身没有事务,那么此时B就会单独给自己开一个新事务。
所以,标注此注解的意思就是,我自己默认开启事务,其它方法有事务,那我就加入,如果没有,那我就自己新建一个事务。这也是最常用的方式。
另外几种事务传播属性:
- SUPPORTS,支持事务,有事务就加入,没有就不执行事务。
- Mandatory 强制事务,如果已经存在一个事务,加入当前事务。如果没有一个活动的事务,则抛出异常。
- required_new 新事务,总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起,新启动一个新的。
- Not_support 不支持事务,总是非事务地执行,并挂起任何存在的事务。
- Never 禁止事务, 总是非事务地执行,如果存在一个活动事务,则抛出异常
- Nested 嵌套的 如果有就嵌套、没有就开启事务。这个和默认REQUIRED的区别,主要在失败回滚上,嵌套有保存点的概念,只会局部回滚,而默认属性REQUIRED是全部回滚。
这个传播属性,也需要在使用时仔细斟酌一下进行设置,当然大多数情况下默认值就可以满足了,大家在使用事务时不要忘记即可。
- 事务失效的情况
这个也是使用Spring框架操作事务时经常遇到的问题,有时候很奇怪,为啥我开启了事务,没有生效呢?这个情况比较多,给大家分享几点最常见的情况。
首先,我们要明确,Spring框架事务管理,是通过代理类来实现的,所以通常失效的场景都是Spring代理出问题了。以下是常见的一些需要注意的场景:
- 访问权限是private,或者final修饰等,无法正常生成代理类。
- Bean未由Spring管理,比如一个类没有加@Service,@Controller或者@Compent等注解,Spring根本没有管理它,又谈何代理。
- 方法互相调用导致失效
第3点我们展开说下。分为两种大的情况,比如现在有方法A和方法B。
情况1:A和B在一个类中 A加@Transactional开启事务,B无论加不加注解,B也都会有事务。 A不加@Transactional开启事务,无论B加不加注解开启事务,AB都是无事务的。
情况2:A和B不在一个类中 A加@Transactional开启事务,B无论加不加注解,AB都会有事务。 A不加@Transactional开启事务,B加注解开启事务,则A无事务,B有事务。 AB都不开启,那肯定都没有事务,就不说了。
所以同一个类中的方法调用要注意,如果A方法没开启事务,B是无论如何不会有事务生效的。
到这,SpringBoot如何使用事务,和一些使用过程中常见的问题就说明的差不多了,相信照着文章,完成初步的使用已经没有太多问题了。
我是创作者卡兰,热衷用简单易懂的小文章,分享总结自己的技术经验,欢迎大家关注、点赞和收藏。