Spring事务简介
--------------------------------------------------------------------------------------------------------------------------------
测试案例
以银行转账为例,一次转账操作伴随着一个账户的扣钱和另一个账户的加钱,这个流程是一个原子操作,要么同时发生,一气呵成,要么都不做。
AccountServiceImpl实现AccountService接口,里面只有一个转账方法
public interface AccountService {
public void transfer(int out,int in);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public void transfer(int out, int in) {
accountMapper.outMoney(out);
accountMapper.inMoney(in);
}
}
AccountMapper以及其映射文件
@Repository
public interface AccountMapper {
public void inMoney(@Param("id") int id);
public void outMoney(@Param("id") int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.test.mapper.AccountMapper">
<update id="inMoney">
update account set money=money+100 where id=#{id}
</update>
<update id="outMoney">
update account set money=money-100 where id=#{id}
</update>
</mapper>
account数据库
测试代码及结果
@RunWith(SpringJUnit4ClassRunner.class)//加载类运行器
@ContextConfiguration(classes = SpringConfig.class)//加载上下文配置
public class TestTransition {
@Autowired
private AccountService accountService;
@Test
public void test01(){
accountService.transfer(1,2);
}
}
用1/0模拟程序运行时出现异常的情况
由于没有开启事务,出现了莫名其妙少了100块钱的情况,说明这个事务不是连续的。
开启Spring事务
1.在业务层接口添加Spring业务管理
public interface AccountService {
@Transactional
public void transfer(int out,int in);
}
2.设置Spring的事务管理器
package com.example.test.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.example.test.dao.StudentDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
public class JdbcConfig {
//返回DataSource的实例化对象
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/testspring?serverTimezone=Asia/Shanghai");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
@Bean
//Spring事务管理器
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
return dstm;
}
}
3.加载事务驱动
@Configuration
@ComponentScan("com.example.test")//包扫描
@PropertySource("db.properties")//加载properties文件
@Import({JdbcConfig.class,MybatisConfig.class})//加载第三方bean
@EnableAspectJAutoProxy //开启AOP
@EnableTransactionManagement//开启Spring事务
public class SpringConfig {
}
测试
由于出现了异常,Spring帮我们回滚了outMoney操作
Spring事务角色
在这里,transfer方法是事务管理员,inMoney和outMoney方法是事务协调员
程序运行时,两个事务协调员加入事务管理员开启的事务T,成为一个事务,他们要么一起完成,要么都不做。
事务相关配置
对于rollbackFor,值得注意的是,Spring只对两种类型进行回滚,一种是error,一种是运行时异常(非受检异常),所以对于受检异常,我们需要手动回滚。
下面是一些常见的受检异常
例
事务传播行为
要实现的需求
也就是无论转账成功与否,我们都希望对log(日志表)进行写,记录下这次转账操作的发生。
如果没有事务传播,那么log事务会和intMoney,outMoney一样,加入到事务管理员transfer创建的事务T中,与他们同进退。
开启事务传播
开启事务传播,可以表明事务协调员对事务管理员所携带的事务的态度
在这里intMoney或outMoney发生的回滚,不会影响到log,因为log单独开了个事务T2
propagation配置类型
怎么看这个表:
SUPPORTS:管理员开启事务T,协调员就加入;管理员没有开事务,协调员也不开。