spring事务管理包含两种情况,编程式事务、声明式事务。而声明式事务又包括基于注解@Transactional和tx+aop的方式。编程式事务管理使用TransactionTemplate或者PlatformTransactionManager。对于编程式事务spring推荐使用TransactionTemplate。
编程式事务TransactionTemplate需要手动在代码中处理事务,不推荐使用,也不符合spring的思想,因为它直接耦合代码,有时间自己看一下源码。
声明式事务管理分类
声明式事务管理也有两种常用的方式,
一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。
显然基于注解的方式更简单易用,更清爽。
配置文件中都有相应的注解,这样无需对后台的代码做配置,只需要注意对应的service层中的方法需要按照配置文件中的传播行为里的方法名字开头,比如想对添加方法加上事务,就需要insertXXX()这样来命名。
对应的service接口中的方法
package com.zshuai.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.zshuai.dao.UserInfoMapper;
import com.zshuai.pojo.UserInfo;
import com.zshuai.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserInfoMapper userMapper;
@Override
public void addUser() {
try {
UserInfo pojo = new UserInfo();
pojo.setId(1);
pojo.setUid(1);
pojo.setName("zshuai");
pojo.setPassword("aaaaaaa");
pojo.setSex(1);
pojo.setAddress("北京市海淀区");
userMapper.insert(pojo);
System.out.println("插入完毕");
int i = 1 / 0;
UserInfo entity = userMapper.selectByPrimaryKey(1);
System.out.println(entity.getName() + "查询的名字");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("除数为0");
}
}
}
只需要添加了service注解以及命名格式正确即可。
会发现配置文件少了好多东西。
使用这样的方式需要在service层中想开启事务的方法上添加@Transactional注解。这样就自动开启了。
package com.zshuai.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zshuai.dao.UserInfoMapper;
import com.zshuai.pojo.UserInfo;
import com.zshuai.service.UserService;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserInfoMapper userMapper;
@Transactional
public void addUser() {
try {
UserInfo pojo = new UserInfo();
pojo.setId(1);
pojo.setUid(1);
pojo.setName("zshuai");
pojo.setPassword("aaaaaaa");
pojo.setSex(1);
pojo.setAddress("北京市海淀区");
userMapper.insert(pojo);
System.out.println("插入完毕");
int i = 1 / 0;
UserInfo entity = userMapper.selectByPrimaryKey(1);
System.out.println(entity.getName() + "查询的名字");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("除数为0");
}
}
}
两种添加注解的方式都可以
项目demo结构
demo下载路径:https://download.csdn.net/download/weixin_37701177/11444763
Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚。
如果一个方法抛出Exception或者Checked异常,Spring事务管理默认不进行回滚。
Throwable分为Error和Exception(RuntimeException)
Throwable是所有异常的根,java.lang.Throwable
Error是错误,java.lang.Error
Exception是异常,java.lang.Exception
所以,涉及事务的方法如果是直接进行try-catch的话,会导致事务回滚失效。
因为默认情况下spring事务只在发生未被捕获的 RuntimeException时才回滚(运行时异常)
try catch这种把整个包裹起来,这种业务方法也就等于脱离了spring事务的管理,因为没有任何异常会从业务方法中抛出,全被捕获并“吞掉”,导致spring异常抛出触发事务回滚策略失效。
解决方案:
解决方案1:
try {
doSomething();
} catch (Exception e) {
throw new RuntimeException();
}
catch的时候new成spring默认的异常RuntimeException();
解决方案2:
try {
doSomething();
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚.
解决方案3:
改变默认方式
在@Transaction注解中定义noRollbackFor和RollbackFor指定某种异常是否回滚。
@Transaction(noRollbackFor=RuntimeException.class)
@Transaction(RollbackFor=Exception.class)
这样就改变了默认的事务处理方式。
事务是一组原子性的SQL查询,是一个独立的工作单元。要么所有全部执行成功,要么全部执行失败。这种思想反映到数据库上,就是多条SQL语句,要么所有执行成功,要么所有执行失败。
事务的特性
锁就是防止其他事务访问指定资源的手段。锁是实现并发控制的主要方法,是多个用户能够同时操纵同一个数据库中的数据而不发生数据不一致现象的重要保障。 一般来说,锁可以防止脏读、不可重复读和幻读。
A事务读取B事务尚未提交的更改操作,并在这个数据的基础上进行操作。如果恰巧B事务回滚,那么A事务读到的数据根本是不被承认的。
同一事务中,用同样的操作读取两次,得到的记录数不相同。
注意:幻读重点在新增或删除。
不可重复读是指A事务读取了B事务已经提交的更改数据。同一事务中,两次读取同一数据,得到的内容不同。
注意:不可重复读重点在修改。
事务T1读取了数据,并执行了一些操作,然后更新数据。事务T2也做相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引起错误。
对于上述并发引发的问题,数据库通过锁机制是可以解决的。
在READ UNCOMMITTED 级别,事务中的修改,即使没有提交,对其它事务也都是可见的。事务可以读取未提交的数据,这也就是脏读。在实际一般很少使用。
READ COMMITTED 一个事务从开始知道提交之前,所做的任何修改对其他事物都是不可见的。
REPEATABLE READ 解决了脏读的问题。改几倍保证了在同一个事务中多次读取同样记录的结果是一直的。但是理论上,可重复读隔离级别还是无法解决另一个幻读的问题。
该级别是 Mysql 默认的事务隔离级别。
SERIALIZABLE 是最高的格列界别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE 会再读取的每一行数据上都加锁,所以可能导致大量的超时 和锁征用的问题。实际应用中也很少使用到这个隔离级别。
以上的五个事务隔离级别都是在Connection接口中定义的静态常量,使用setTransactionIsolation(int level) 方法可以设置事务隔离级别。
如:con.setTransactionIsolation(Connection.REPEATABLE_READ)。
注意:事务的隔离级别受数据库的限制,不同的数据库支持的的隔离级别不一定相同。