事务
在Sping中,事务有两种方式来实现,一种是通过手动编程的方式来实现,一种是通过注解的方式来进行实现.
手动编程实现事务
@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;@AutowiredTransactionDefinition transactionDefinition;@Autowiredprivate UserService userService;@RequestMapping("/registry")public String registry(String name,String password){//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);userService.registeryUser(name,password);//提交事务dataSourceTransactionManager.commit(transactionStatus);return "注册成功";}
首先需要对DataSourceTransactionManager事务管理器进行定义,并注入Autowired,
@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;@AutowiredTransactionDefinition transactionDefinition;
对TransactionDefinition事务定义进行注入,在需要实现事务的方法里面开启事务
事务开启:
//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
事务提交
//提交事务dataSourceTransactionManager.commit(transactionStatus);
控制台事务提交显示
接下来演示事务进行回滚时的操作
// 事务进行回滚log.info("事务进行回滚中");dataSourceTransactionManager.rollback(transactionStatus);
运行结果:
可以通过观察发现,回滚后的日志没有出现事务commit
发送增加数据请求之前数据库:
发送增加数据请求之后数据库:
可以观察到数据库并没有执行数据的插入操作,可见事务回滚成功,并未进行提交,
使用注解实现事务
使用注解的时候,加在方法之前即可
@Transactinoal可以对方法或者类进行修饰
对方法进行修饰的时候,只对public修饰的方法生效,对其他方法不生效,也不报错,但是不推荐
对类进行修饰的时候,则默认对该类所有的public修饰的方法生效
public class TransactionalController {@Autowiredprivate UserService userService;@RequestMapping("/registry")@Transactionalpublic String registry(String name,String password){userService.registeryUser(name,password);return "注册成功";}
}
在程序正常进行无异常的时候,事务会进行正常提交,这一部分就不演示了,
接下来演示出现异常事务是否会进行回滚
代码:
@Transactionalpublic String registry(String name,String password){userService.registeryUser(name,password);int a=10/0;return "注册成功";}
可以看到事务进行回滚操作了,
可以看到数据库也是没有进行任何变化
public String registry(String name,String password){userService.registeryUser(name,password);try{int a=10/0;}catch (Exception e){e.printStackTrace();}return "注册成功";}
可以看到虽然程序运行时出错了,但是事务提交了,是因为程序运行时抛出的异常被捕获到了,进行处理之后,@Transactional注解会自动进行提交,不会进行回滚,如果还想进行回滚需要手动进行设置,
try{int a=10/0;}catch (Exception e){
// e.printStackTrace();TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}
可以通过这样设置,来手动进行事务的回滚
通过观察日志,我们可以发现事务并没有进行commit,而是进行了rollback.
@RequestMapping("/r2")@Transactionalpublic String registry2(String name,String password) throws IOException {userService.registeryUser(name,password);if(true){throw new IOException();}return "注册成功";}
这次我们让程序主动抛出异常,
可以发现,虽然主动抛出了异常,但是事务还是进行了提交
只有当异常为RuntimeException以及他的子类和error时才会进行回滚,其他异常则不会进行回滚
@Transactional(rollbackFor = Exception.class)
在将注解改成这样之后,将rollbackFor指定异常为Exception之后,该事务才会进行回滚.
事务的特性
rollbackFor:能指定事务回滚时的异常类型,可以指定多个异常类型
Isolation:事务的隔离级别,默认值为Isolation.DEFAULT
propagation:事务的传播机制,默认值为propagation.REQUIRED
1.Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.
2. Isolation.READ_UNCOMMITTED : 读未提交, 对应SQL标准中 READ UNCOMMITTED
3. Isolation.READ_COMMITTED : 读已提交,对应SQL标准中 READ COMMITTED
4. Isolation.REPEATABLE_READ : 可重复读, 对应SQL标准中 REPEATABLE READ
5. Isolation.SERIALIZABLE : 串⾏化, 对应SQL标准中 SERIALIZABLE
事务的传播机制
什么叫事务传播,当A方法和B方法同时被注解@Transactional进行修饰的时候,并且在A方法中调用B方法,那么A方法的事务和B方法的事务该如何开启呢,我们把这种现象称为事务的传播机制.
事务的传播机制分类
1.Propagation.REQUIRED 默认的事务传播机制,如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务
2.Propagation.MANDATORY 强制性.如果当前存在事务,则加入该事物,如果当前没有事务,则抛出异常
3.Propagation.SUPPORTS:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务的方式继续运行
4.Propagation.REQUIRES_NEW:创建一个新的事务.如果当前存在事务,则把当前事务挂起,也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW所修饰的内部方法都会开启一个新的事务
5.Propagation.NOT_SUPPORTED:以非事务的方式进行运行,如果当前存在事务,则把当前事务进行挂起,
6.Propagation.NEVER:以非事务的方式运行,如果有当前事务,则抛出异常
7.Propagation.NESTED:如果当前存在事务,则创建一个当前事务的嵌套事务,如果当前没有事务,则创建一个事务
conttroller类:
@RequestMapping("/test")
public class UserController2 {@Autowiredprivate UserService userService;@Autowiredprivate LogService logService;@RequestMapping("/u1")@Transactionalpublic String registry(String name,String password){userService.registeryUser(name,password);logService.inserLog(name,"注册成功");return "注册成功";}
LogService类:
@Service
public class LogService {@Autowiredprivate LogInfoMapper logInfoMapper;@Transactional(propagation =Propagation.REQUIRED)public void inserLog(String name,String op){logInfoMapper.insertLog(name,"用户注册");int a=10/0;}
}
Userservice类:
@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.REQUIRED)public void registeryUser(String name,String password){userInfoMapper.insert(name,password);}
}
可以发现两次操作是在同一个事务开启的,并且由于发生了异常进行回滚,进行的两次插入操作均失效,
@Transactional(propagation =Propagation.REQUIRES_NEW)
将事务的传播机制改为Propagation.REQUIRES_NEW之后
可以发现开启了两个事务,其中第二个事务由于发生异常进行了回滚操作,第一个事务成功提交了,说明这两个事务的隔离级别设置为Propagation.REQUIRES_NEW时,事务之间是相互不干扰的.
@Transactional(propagation =Propagation.NEVER)
在将事务的隔离级别改为NEVER的时候,程序直接抛出异常
@Transactional(propagation =Propagation.NESTED)
在将隔离级别改为NESTED的时候,可以发现,在其中的一个事务发生异常的时候,两个操作都会失效,都会进行回滚.
logInfoMapper.insertLog(name,"用户注册");try{ int a=10/0;}catch (Exception e){TransactionAspectSupport.
currentTransactionStatus().setRollbackOnly();}
http://127.0.0.1:8080/test/u1?name=123&password=1234
url请求访问之前数据库:
url请求访问之后数据库:
通过观察,我们可以发现只有log_info表的插入数据进行了回滚,然而user_info表中的插入数据并没有进行回滚,这是nested和required的区别.在于nested是一个嵌套事务,只要其中的一个事务把自己的异常处理好了并且即使rollback了也不会影响另外一个事务的正常提交.然而required由于是同一个事务,所以在一个事务中进行rollback了,另外一个也会自动进行rollback
mybatis运行日志
nested和required的区别
整个事务如果执行成功,那么两种级别是一样的.
如果其中的一个事务发生了异常,那么这个事务在nested中是可以进行局部回滚的,而required是做不到的.