事务
如果后台服务器发生问题,则数据信息应该回滚,而不是提交操作.
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
事物的四大特点:
- 原子性:事务不可拆分,要么全部执行成功,要么全部执行失败
- 一致性:永远不违背数据库完整性的约束
- 持久性:提交读数据改变是永久的
- 隔离性:两个事物之间彼此隔离
spring boot的事务控制
@Transactional
注解service层的数据库的增删改的实现类方法
如果发生运行时异常,则事务撤销回滚
如果编译发生异常时,默认不回滚
@Override
@Transactional
public Integer updateStatus(User user) {
int token = userMapper.updateById(user);
return token;
}
属性说明
noRollbackFor={} 遇到某种异常不回滚
rollbackFor={} 遇到某种异常回滚
spring事务失效的12种场景
- 权限符错误,不是public
- 修饰符错误final,static
- 内部类调用
- 未被spring管理
- 多线程调用
- 表不支持事务
- 事务未开启
一,事务不生效
如果访问权限不是public,
java的访问权限主要有四种:private私有的、default默认的、protected受保护的、public公开的,它们的权限从左到右,依次变大。
如果事务方法的访问权限不是public,而是private、default或protected的话,spring则不会提供事务功能。
二,方法被错误修饰final,static
有时候如果方法不想被子类重新定义,可以使用final进行定义,普通情况没问题,但是事务
不支持final和static修饰。
原因:spring事务底层是依靠AOP,也就是jdk的动态代理或者cglib帮助我们生成了代理类,在代理类中实现事务的功能。如果某个类使用了final和static修饰,那么它的代理类中就无法使用该方法,添加事务的功能
三,方法内部调用
有时候我们需要在service类的某个方法中调用另一个事务方法
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public void add(UserModel userModel) {
userMapper.insertUser(userModel);
updateStatus(userModel);
}
@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}
我们看到在事务方法add中,直接调用事务方法updateStatus,前面说到了updateStatus方法拥有事务的原因是spring aop产生了代理对象,但是这个方法会直接调用this对象的方法,不生成代理,所以updateStatus方法不会生成事务
解决方法,1.新加一个service类
在原来的类中,注入调用另一个类的事务方法
@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;
public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}
@Servcie
public class ServiceB {
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
解决方法,2.或者在该类中注入自己
@Servcie
public class ServiceA {
@Autowired
prvate ServiceA serviceA;
public void save(User user) {
queryData1();
queryData2();
serviceA.doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
问:这种方法会不会出现循环依赖问题?
不会,因为spring ioc内部的三级缓存保证了它,不会出现循环依赖问题
具体参考:spring:我是如何解决循环依赖的?
解决方法3,通过AopContent
在service类中使用AopContext.currentProxy()获取代理对象,
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
4,未被spring管理
没有@Service注解,没有生成供spring管理的注解,自然不会生成对应事务
5,多线程调用
多线程情况中,两个方法不在一个线程中,获取到的数据库连接不同,产生的事务不是相同的事务,无法执行回滚操作
6,表不支持事务
在mysql5之前,默认的数据库引擎是myisam,,它的索引文件与数据文件是分开储存的,适用于查多写少的单表操作,性能比innodb更好,但是它不支持事务。此外,myisam还不支持行锁和外键。
有时候我们在开发的过程中,发现某张表的事务一直都没有生效,那不一定是spring事务的锅,最好确认一下你使用的那张表,是否支持事务。
7.未开启事务
springboot会通过DataSourceTransactionManagerAutoConfiguration类帮助开启了事务,只需要配置spring.datasource
相关参数即可。
如果是传统的spring项目,需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。
<!-- 配置事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 用切点把事务切进去 -->
<aop:config>
<aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
如果在pointcut标签中的切入点匹配规则,配错了话,有些类的事务也不会生效
二,事务不回滚
1.错误的传播性
我们在使用@Transactional时,是可以指定propagation参数的
该参数的作用是指定事务的传播性,的spring目前支持7种传播性
- REQUIRED如果当前上下文中存在事务,那么加入该事务,如果不存在事务,那么自己创建一个事务,这就是默认的传播属性值
- SUPPORTS如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方法运行
- BESTED如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
- NEVER如果当前上下文中存在事务,则抛出异常,否则在无事务环境运行
- MANDATORY判断当前上下文中存在事务,否则抛出异常
- REQUIRES_NEW每次都会新建一个事务,并将上下文中的事务挂起,执行当前新建事务。完成之后上下文事务恢复执行
- NOT_SUPPORTED如果当前上下文中存在事务,则挂起当前事务,执行新的事务
在设置prpagation时设置错误的参数时:
@Service
public class UserService {
@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
NEVER如果当前上下文中存在事务,则抛出异常,否则在无事务环境运行。
这类传播特性不支持事务,有事务反而会抛出异常
目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。
2.自己吞了异常
开发者手动添加try…catch捕获处理了异常
这种情况下不会回滚,因为spring没有收到异常信息,异常被吞掉了
3.手动抛了别的异常
spring只支持回滚运行时异常RuntimeException,和Error对于普通的非运行时异常不会回滚
4.自动定义了回滚异常
我们可以使用@Transactional的rollbackFor蚕食设定收到什么异常才回滚,如果这个值与接受的异常不符,spring也不会回滚
即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。
这是为什么呢?
因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成:Exception或Throwable。
5,嵌套事务回滚多了
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
roleService.doOtherThing();
/*部分回滚
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}*/
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
在该程序中,原本是只希望回滚RoleService中的doOtherThing方法,而add中的userMapper.insertUser方法不会回滚,但是实时是两个条件都会回滚
如何解决,保存回滚点?
将内部嵌套事务在try/catch中,不继续向上传递异常,这样就可以保证,出现异常时,只回滚内部事务,不影响外部事物。
三,其他
1.大事务问题
在使用spring事务时,有个很让人头疼的事情,那就是大事务问题,通常情况下,我们会在方法上添加@Transanctional注解,添加事务功能。
在一个事务中添加大量的非事务方法,将普通方法与需要添加事务的集中写在一个类中,将导致整个事务非常耗时,造成大事务问题
@Service
public class UserService {
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
query1();
query2();
query3();
roleService.save(userModel);
update(userModel);
}
}
在add方法中,真正需要执行事务的只有roleService.save(userModel);和update(userModel);
但是整个方法都会被包含在事务中,整个事务的耗时增加,造成大事务问题
关于大事务问题的危害,可以阅读一下我的另一篇文章《让人头痛的大事务问题到底要如何解决?》,上面有详细的讲解。
2.编程式事务
上面的内容都是基于@Transantional注解的,它的事务叫做声明式事务
其实spring还有另一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种是位于叫做:编程使事务
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}
在spring中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的execult方法中就实现了事务的功能。
相较于@transactional注解声明式事务,更推荐使用TransactionTemplate的编程事务,原因:
避免由于spring aop问题导致事务失效
可以更加仔细控制事务的范围,更加直观
建议在项目中少使用@Transactional注解开启事务。但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用@Transactional注解开启事务开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。