一个转账的问题
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