Spring事务管理
一个转账的问题
pom
参考:https://www.cnblogs.com/uncleyong/p/17026215.html
创建表
添加数据
实体类
逆向生成实体类,并添加无参构造、带参构造、toString
package com.qzcsbj.bean; public class Account { private long id; private String username; private double money; public Account() { } public Account(String username, double money) { this.username = username; this.money = money; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", username='" + username + '\'' + ", money=" + money + '}'; } }
dao层(mapper层)
mapper接口
package com.qzcsbj.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.qzcsbj.bean.Account; /** * @公众号 : 全栈测试笔记 * @博客 : www.cnblogs.com/uncleyong * @微信 : ren168632201 * @描述 : <> */ public interface AccountMapper extends BaseMapper<Account> { }
service层
service接口
package com.qzcsbj.service; import com.baomidou.mybatisplus.extension.service.IService; import com.qzcsbj.bean.Account; /** * @公众号 : 全栈测试笔记 * @博客 : www.cnblogs.com/uncleyong * @微信 : ren168632201 * @描述 : <> */ public interface AccountService extends IService<Account> { // 这里只是演示,假设username是唯一的 public int transferMoney(String fromName,String toName,double money); }
service实现类
package com.qzcsbj.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.qzcsbj.bean.Account; import com.qzcsbj.mapper.AccountMapper; import com.qzcsbj.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService { @Autowired AccountMapper accountMapper; public int transferMoney(String fromName, String toName, double money) { // 账户A给账户B转钱,这里只是演示,假设账户A的钱≥要转出的钱 // 账户A Account accountA = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", fromName)); accountA.setMoney(accountA.getMoney()-money); int countA = accountMapper.updateById(accountA); // int countA = accountMapper.update(accountA,new QueryWrapper<Account>().eq("username",fromName)); // 账户B Account accountB = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", toName)); accountB.setMoney(accountB.getMoney()+money); int countB = accountMapper.updateById(accountB); // int countB = accountMapper.update(accountB,new QueryWrapper<Account>().eq("username",toName)); return countA+countB; } }
测试类
package com.qzcsbj.test; import com.qzcsbj.service.AccountService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @公众号 : 全栈测试笔记 * @博客 : www.cnblogs.com/uncleyong * @微信 : ren168632201 * @描述 : <> */ @RunWith(SpringJUnit4ClassRunner.class) // 表示Spring和JUnit整合测试 @ContextConfiguration("classpath:applicationContext.xml") public class TestTransaction { @Autowired AccountService accountService; @Test public void testTransferMoney(){ String fromName = "jack"; String toName = "tom"; double money = 10.0; int i = accountService.transferMoney(fromName, toName, money); if (i==2){ System.out.println(fromName + "给"+ toName + "转账" + money + "元成功"); } else { System.out.println(fromName + "给"+ toName + "转账" + money + "元成功"); } } }
结果
表中数据正确
模拟异常
修改service实现类,模拟异常
package com.qzcsbj.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.qzcsbj.bean.Account; import com.qzcsbj.mapper.AccountMapper; import com.qzcsbj.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService { @Autowired AccountMapper accountMapper; public int transferMioney(String fromName, String toName, double money) { // 账户A给账户B转钱,这里只是演示,假设账户A的钱≥要转出的钱 // 账户A Account accountA = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", fromName)); accountA.setMoney(accountA.getMoney()-money); int countA = accountMapper.updateById(accountA); // int countA = accountMapper.update(accountA,new QueryWrapper<Account>().eq("username",fromName)); // 模拟异常 int i=1/0; // 账户B Account accountB = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", toName)); accountB.setMoney(accountB.getMoney()+money); int countB = accountMapper.updateById(accountB); // int countB = accountMapper.update(accountB,new QueryWrapper<Account>().eq("username",toName)); return countA+countB; } }
测试结果
运行出异常了,jack的账户扣了钱(mybatis底层是jdbc,默认自动提交事务,所以转出这个操作会成功入库),tom的账户没变
所以,transferMoney这个业务方法需要在事务中运行才可以。
事务(Transaction)简介
事务是一系列的动作,是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态。
在企业级应用程序开发中,事务管理是必不可少的技术,用来确保数据的完整性和一致性。
Spring事务管理的核心接口
org.springframework.transaction下三个接口是Spring中事务的顶级接口:平台事务管理器、事务定义接口、事务状态接口
PlatformTransactionManager接口中:
三个方法的作用:
获取TransactionStatus对象,从而管理事务
提交事务
事务过程执行异常,回滚事务
TransactionDefinition接口中:
上面五个方法:
Spring定义了如下七中传播行为,这里以A业务和B业务之间如何传播事务为例说明(A业务调用B业务): 1、PROPAGATION_REQUIRED :required , 必须。默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。 2、PROPAGATION_SUPPORTS:supports ,支持。A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。 3、PROPAGATION_MANDATORY:mandatory ,强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。 4、PROPAGATION_REQUIRES_NEW :requires_new,必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。 5、PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。 6、PROPAGATION_NEVER :never,从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行。 7、PROPAGATION_NESTED :nested ,嵌套。如果A当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
Spring事务管理中,定义了如下的隔离级别: 1、ISOLATION_DEFAULT:使用后端数据库默认的隔离级别(不同的数据隔离级别不同) 2、ISOLATION_READ_UNCOMMITTED:【读未提交】最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 3、ISOLATION_READ_COMMITTED:【读已提交】允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 4、ISOLATION_REPEATABLE_READ:【可重复读】对同一字段的多次读取结果都是一致的,可以阻止脏读和不可重复读,但幻读仍有可能发生 5、ISOLATION_SERIALIZABLE:【串行化】最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,性能差
TransactionStatus这个接口中:
6个方法的作用:
是否是新的事务
是否有保存点
设置回滚
是否回滚
刷新
是否完成
这个接口描述的是控制事务执行和查询事务状态的方法。
mybatis底层是jdbc,所以需要配置DataSourceTransactionManager这个类
配置声明式事务:基于aop的xml配置
配置applicationContext.xml(基于这里<https://www.cnblogs.com/uncleyong/p/17026215.html>的xml添加如下内容)
下面只配置了一个方法需要织入事务
<tx:advice id="adviser" transaction-manager="transactionManager"> <!--配置哪些方法需要配代理织入事务--> <tx:attributes> <tx:method name="transferMoney"/> </tx:attributes> </tx:advice>
配置AOP切入点
<aop:config> <aop:pointcut id="pointcut" expression="execution(* com.qzcsbj.service.impl.*.*(..))"/> <aop:advisor advice-ref="adviser" pointcut-ref="pointcut"/> </aop:config>
测试报错
数据库回滚了,说明事务起作用了
其它配置
propagation:事务的传播性,非必须,默认值REQUIRED
isolation:隔离级别,非必须,mysql填可重复读REPEATABLE_READ;如果不配置就是默认isolation="DEFAULT"
timeout="-1",事务超时时间,单位是秒,非必须;默认值是-1,使用数据库底层的超时时间,如果底层数据库事务系统没有设置超时值,那么就是none,表示没有超时限制,也就是不超时;
read-only:只读,非必须,默认值是false,查询设置true,非查询设置false
no-rollback-for:非必须,示不被触发进行回滚的 Exception(s)
rollback-for:非必须,表示将被触发进行回滚的 Exception(s)
配置声明式事务:基于aop的注解配置
applicationContext.xml配置(基于这里<https://www.cnblogs.com/uncleyong/p/17026215.html>的xml添加如下内容)
<!--配置MyBatis的事务平台管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="ds"/> </bean> <!--开启注解管理事务--> <tx:annotation-driven transaction-manager="transactionManager"/>
可以在方法上加注解
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout = -1,readOnly = false,rollbackFor ={Exception.class}) public int transferMoney(String fromName, String toName, double money) { // 账户A给账户B转钱,这里只是演示,假设账户A的钱≥要转出的钱 // 账户A Account accountA = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", fromName)); accountA.setMoney(accountA.getMoney()-money); int countA = accountMapper.updateById(accountA); // int countA = accountMapper.update(accountA,new QueryWrapper<Account>().eq("username",fromName)); // 模拟异常 int i=1/0; // 账户B Account accountB = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", toName)); accountB.setMoney(accountB.getMoney()+money); int countB = accountMapper.updateById(accountB); // int countB = accountMapper.update(accountB,new QueryWrapper<Account>().eq("username",toName)); return countA+countB; }
测试异常
自动回滚了
也可以在类上加注解,表示全局,而方法上加注解表示局部,如果全局和局部都有注解,那么方法上的注解优先。
原文会持续更新,原文地址:https://www.cnblogs.com/uncleyong/p/17035170.html
__EOF__
关于博主:擅长性能、全链路、自动化、企业级自动化持续集成(DevTestOps)、测开等
面试必备:项目实战(性能、自动化)、简历笔试,https://www.cnblogs.com/uncleyong/p/15777706.html
测试提升:从测试小白到高级测试修炼之路,https://www.cnblogs.com/uncleyong/p/10530261.html
欢迎分享:如果您觉得文章对您有帮助,欢迎转载、分享,也可以点击文章右下角【推荐】一下!