事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
Spring提供了一个接口PlatformTransactionManager用来处理事务
public interface PlatformTransactionManager{
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager接口最基本的事务管理实现类DataSourceTransactionManager
public class DataSourceTransactionManager {
……
}
案例学习: 模拟银行账户间转账业务
需求:实现任意两个账户间转账操作
需求微缩:A账户减钱,B账户加钱
分析:
①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
②:业务层提供转账操作(transfer),调用减钱与加钱的操作
③:提供2个账号和操作金额执行转账操作
④:基于Spring整合MyBatis环境搭建上述操作
结果分析:
①:程序正常执行时,账户金额A减B加,没有问题
②:账户金额A减执行完成后, 程序出现异常后,转账失败,但是异常之前账户金额A减操作成功,异常之后账户金额B加操作失败,这是有非常大的安全隐患的
实现步骤:
首先创建一个数据库, 有id, name, money三个字段, 以及两条数据
create table tb_account(
id int primary key auto_increment,
name varchar(10),
money varchar(10)
)
insert into tb_account (name, money)
values ("chenyq", 1000), ("zhangsan", 1000);
基于整合Mybatis和Junit环境(不会的可以看前面文章), 创建一个service层接口AccountService
public interface AccountService {
/**
* @param out 转出对象
* @param in 转入对象
* @param money 转出金额
*/
void transfer(String out, String in, double money);
}
创建dao层接口AccountDao, 并定义一个加钱和减钱的方法
public interface AccountDao {
@Update("update tb_account set money = money + #{money} where name = #{name};")
void inMoney(@Param("name") String name, @Param("money") String money);
@Update("update tb_account set money = money - #{money} where name = #{name};")
void outMoney(@Param("name") String name, @Param("money") String money);
}
service层AccountService接口的AccountServiceImpl实现类调用数据层方法
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out, String in, double money) {
accountDao.outMoney(out, money); // 账户减钱
accountDao.inMoney(in, money); // 账户加钱
}
}
测试类中进行测试, 可以看到数据库中是修改成功了的
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer("chenyq", "zhangsan", 100);
}
}
下面我们在AccountServiceImpl实现类中模拟chenyq账户转钱出去之后, 程序出现异常
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out, String in, double money) {
accountDao.outMoney(out, money); // 账户减钱
int i = 1 / 0; // 模拟程序出现异常
accountDao.inMoney(in, money); // 账户加钱
}
}
重新执行测试类, 我们可以发现虽然程序报错, 但是数据库中chenyq账户的money又减少了100
我们需要开启Spring事务, 让加钱和减钱的操作同时成功或失败, 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务, Spring开启事务步骤如下
步骤一: 使用@Transactional注解开启任务, 不过我们一般不是在实现类的方法中开启事务, 而是在接口的方法中开启, 降低耦合度
public interface AccountService {
/**
* @param out 转出对象
* @param in 转入对象
* @param money 转出金额
*/
@Transactional // 开启事务
void transfer(String out, String in, double money);
}
步骤二: 在JdbcConfig独立配置文件中, 定义设置一个事务管理器, 并将这个事务管理器交给Spring管理
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
步骤三: 在Spring核心配置文件中, 通过EnableTransactionManagement注解开启Spring对开启事务的注解支持(简单来说, 告知Spring是使用了注解开启事务的)
@Configuration
@ComponentScan("com.chenyq")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
到这事务也就开启完成了, 开启事务中的方法同成功同失败, 不会出现因为异常一个业务成功一个业务失败的情况
事务有如下相关配置:
属性 | 作用 | 示例 |
---|---|---|
readOnly | 设置是否为只读事务 | readOnly=true 只读事务 |
timeout | 设置事务超时时间 | timeout = -1(永不超时) |
rollbackFor | 设置事务回滚异常(class) | rollbackFor = {NullPointException.class} |
rollbackForClassName | 设置事务回滚异常(String) | 同上格式为字符串 |
noRollbackFor | 设置事务不回滚异常(class) | noRollbackFor = {NullPointException.class} |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
propagation | 设置事务传播行为 | …… |
使用示例:
@Transactional(readOnly = true, timeout = -1)
void transfer(String out, String in, double money);
有些异常是不参与事务回滚的, 我们需要rollbackFor设置这些异常参与事务回滚
@Transactional(rollbackFor = {NullPointException.class})
void transfer(String out, String in, double money);
如果我们在一个开启事务了的方法1中调用另一个方法2, 我们想要方法1事务回滚时, 方法2不回滚, 就需要设置事务多播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
事务传播行为还有其他属性
传播属性 | 事务管理员 | 事务协调员 |
---|---|---|
REQUIRED(默认) | 开启T | 加入T |
无 | 新建T2 | |
REQUIRES_NEW | 开启T | 新建T2 |
无 | 新建T2 | |
SUPPORTS | 开启T | 加入T |
无 | 无 | |
NOT_SUPPORTED | 开启T | 无 |
无 | 无 | |
MANDATORY | 开启T | 加入T |
无 | ERROR | |
NEVER | 开启T | ERROR |
无 | 无 | |
NESTED | 设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交由客户响应提交/回滚 |