学习的文章
小姐姐非要问我:spring编程式事务是啥? (qq.com)
阿里3面:Spring声明式事务连环炮,让我措手不及。。 (qq.com)
带你读懂Spring 事务——事务的传播机制 - 知乎 (zhihu.com)
spring 事务失效的 12 种场景_事务什么时候失效_hanjq_code的博客-CSDN博客
什么是事务
-
事务是并发操作的单位
-
是用户定义的操作序列
-
事务如果成功,则会提交
-
事务如果失败,则会回滚
事务的四大特性
-
原子性
- 事务中操作,要么不做,要么都做
-
持久性
- 一个事务一旦提交,它对数据库的改变是永久的
-
一致性
-
事务让数据库从一个一致性状态转移到另一个一致性状态
-
比如
-
事务前,A有50,B有50,总共有100
-
事务中,A送给B20
-
事务后,A有30,B有70,总共还是有100
-
-
-
隔离性
-
一个事务的执行不能被其他事务所干扰
-
分成不同的等级
-
事务并发访问导致的数据问题
脏读
读到了修改但还没有提交的数据
-
A事务修改了某条记录的字段c,但还没有提交
-
B事务在此时读取了字段c
-
A事务发生了回滚,字段c恢复了修改前的状态
-
但B事务持有的还是修改后的状态
不可重复读
某个事务在执行过程中,两次读同一个条记录,但结果不一样
-
A事务读取了记录c,值为10
-
B事务修改了记录c,值为0
-
A事务再次读取记录c,值为0
幻读
某个事务在执行过程中,前后两次读取记录,数据总量不一样
-
A事务统计了表c中的记录总数,结果为10
-
B事务删除了表c中的5条记录
-
A事务再次统计了表c中的记录总数,结果为5
和不可重复读的区别在于,幻读针对的是记录条数,不可重复读针对的是记录内容
事务的隔离级别
针对事务并发访问时出现的问题,设置了四种事务的隔离级别
读未提交
可以的读取还没有提交的数据
没有限制,三种问题都有可能发生
读已提交
只能读取已经提交了的数据
不会发生脏读,但是会发生不可重复读和幻读
可重复读
一个事务前后读取的同一条记录的结果必须一致
不会发生脏读和不可重复读,但会发生幻读
mysql中默认的事务隔离级别
串行化
所有的事务必须依次执行
不会发生脏读、不可重读读和幻读
效率比较低
Spring事务的使用方法
Spring分为两种控制事务的方法
-
编程式事务
-
方法1:通过PlatformTransactionManager控制事务
-
方法2:通过TransactionTemplate控制事务
-
-
声明式事务
- 常用
编程式事务的使用
使用PlatformTransactionManager
@Test
public void test1() throws Exception {
//定义一个数据源
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root123");
dataSource.setInitialSize(5);
//定义一个JdbcTemplate,用来方便执行数据库增删改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//3.开启事务:调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus)对象
TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
//4.执行业务操作,下面就执行2个插入操作
try {
System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
//5.提交事务:platformTransactionManager.commit
platformTransactionManager.commit(transactionStatus);
} catch (Exception e) {
//6.回滚事务:platformTransactionManager.rollback
platformTransactionManager.rollback(transactionStatus);
}
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
使用TransactionTemplate
@Test
public void test1() throws Exception {
//定义一个数据源
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root123");
dataSource.setInitialSize(5);
//定义一个JdbcTemplate,用来方便执行数据库增删改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(10);//如:设置超时时间10s
//3.创建TransactionTemplate对象
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);
/**
* 4.通过TransactionTemplate提供的方法执行业务操作
* 主要有2个方法:
* (1).executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
* (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
* 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。
* 那么什么时候事务会回滚,有2种方式:
* (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
* (2)execute方法或者executeWithoutResult方法内部抛出异常
* 什么时候事务会提交?
* 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
*/
transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
@Override
public void accept(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");
}
});
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
声明式事务的使用
-
在配置类上使用@EnableTransactionManagement
- Springboot可以加在启动类上
-
定义事务管理器
-
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
-
springboot中有默认的事务管理器
-
-
在需要事务的目标上加上@Transaction注解
-
作用位置
-
注意:@Transaction只对public方法有效
-
@Transacion放在接口上,接口的所有实现类中所有的public方法都自动加上事务
-
@Transaction放在类上,当前类以及其下无限级子类中的public方法都被加上事务
-
@Transaction放在public方法上,方法被加上事务
-
-
属性
-
"transactionManager"或"value"
-
指定事务管理器的bean对象
-
为空的话,默认按类型获取
-
-
“propagation”
-
指定事务的传播类型
-
默认为REQUIRED
-
-
“rollbackFor”
- 自定义回滚异常
-
-
事务的传播类型
-
REQUIRED
-
如果当前有事务,则加入当前事务
-
如果当前没有事务,则自己新建一个事务
-
-
SUPPORTS
-
如果当前有事务,则加入当前事务
-
如果当前没有事务,则以非事务方式执行
-
-
MANDATORY
-
如果当前有事务,则加入当前事务
-
如果当前没有事务,则抛出异常
-
-
REQUIERES_NEW
-
如果当前有事务,则将该事务挂起,另外新创建一个事务
-
如果当前没有事务,则新创建一个事务
-
-
NOT_SUPPORTED
-
如果当前有事务,则将该事务挂起,以非事务方式执行
-
如果当前没有事务,则以非事务方式执行
-
-
NEVER
-
如果当前有事务,则抛出异常
-
如果当前没有事务,以非事务方式执行
-
-
NESTED
-
如果当前有事务,则在当前事务中嵌套一个事务执行
-
如果当前没有事务,则新建一个事务执行
-
事务失效或回滚异常的12种情况
事务失效
-
访问权限问题
- 事务只能对public的方法方法生效
-
方法用final或static修饰
-
spring事务是使用动态代理的方式实现的
-
如果加了final或public方法,则方法无法被代理
-
-
方法内部调用
-
@Service public class UserService { @Autowired private UserMapper userMapper; public void add(UserModel userModel) { userMapper.insertUser(userModel); updateStatus(userModel); } @Transactional public void updateStatus(UserModel userModel) { doSameThing(); } }
-
在add方法中是通过this来调用updateStatus方法的,没有通过代理
-
解决方法:
-
1.注入自己
-
@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(); } }
-
-
2.通过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(); } }
-
-
-
-
未被spring管理
-
忘了给类添加注释了
-
没有被放到IOC容器中
-
-
多线程调用
-
spring事务是通过数据库连接来实现的
-
在多线程中,每一个线程用的都是不同的数据库连接
-
-
表不支持事务
- MyISAM引擎的表不支持事务
-
未开启事务
-
springboot需要在启动类上加上@EnableTransactionManagement的注解
-
传统spring项目需要在applicationContext.xml中进行相关的配置
-
事务回滚异常
-
使用了错误的传播特性
-
手动捕获了异常
-
如果想要spring事务能够正常回滚,必须抛出它能够处理的异常
-
使用try/catch将异常捕获,将导致事务不会回滚
-
-
抛得异常类型不正确
- spring事务,默认情况下只会回滚RuntimeException或Error
-
自定义回滚异常不匹配
- 比如定义了BusinessException,但抛出的是SqlException
-
嵌套事务回滚范围多了
-
public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); roleService.doOtherThing(); } } @Service public class RoleService { @Transactional(propagation = Propagation.NESTED) public void doOtherThing() { System.out.println("保存role表数据"); } }
-
roleService.doOtherThing()
如果发生回滚,它抛出的异常没有被处理,会继续往上级抛 -
add()在捕获到向上抛的异常后也会发生回滚
-
应该手动进行捕获
-
@Slf4j @Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); try { roleService.doOtherThing(); } catch (Exception e) { log.error(e.getMessage(), e); } } }
-
-