需求: 实现act-001账户向act-002账户转账10000,要求两个账户的余额一个减成功一个加成功,即执行的两条update语句必须同时成功或失败
实现步骤
第一步: 引入项目所需要的依赖
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.powernodegroupId>
<artifactId>spring6-013-tx-bankartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<repositories>
<repository>
<id>repository.spring.milestoneid>
<name>Spring Milestone Repositoryname>
<url>https://repo.spring.io/milestoneurl>
repository>
repositories>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>6.0.0-M2version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>6.0.0-M2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.13version>
dependency>
<dependency>
<groupId>jakarta.annotationgroupId>
<artifactId>jakarta.annotation-apiartifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
dependencies>
<properties>
<maven.compiler.source>17maven.compiler.source>
<maven.compiler.target>17maven.compiler.target>
properties>
project>
第二步: 准备t_act
表并向表中插入两条账户记录act-001和act-002
第三步: 编写t_act
表对应的实体类
@Date
public class Account {
private String actno;
private Double balance;
}
第四步: 编写Dao(持久层)
中的AccountDao
接口及其实现类AccountDaoImpl
专门负责t_act
表的CRUD操作, 没有任何业务逻辑代码
public interface AccountDao {
// 根据账号查询余额
Account selectByActno(String actno);
// 更新账户信息
int update(Account act);
}
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Resource(name = "jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Override
public Account selectByActno(String actno) {
// 根据账号查询账户的信息,并把查询结果封装到对应的实体类中
String sql = "select actno, balance from t_act where actno = ?";
Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
return account;
}
@Override
public int update(Account act) {
// 根据账号更新账户的余额
String sql = "update t_act set balance = ? where actno = ?";
int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());
return count;
}
}
第五步: 编写Service(业务层)
中的AccountService
及其实现类AccountServiceImpl
专门负责关于账户的业务逻辑处理,如事务控制的相关代码
public interface AccountService {
// 转账方法
void transfer(String fromActno, String toActno, double money);
}
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
// 因为在这个方法中要完成所有的转账业务,所以需要控制事务
@Override
public void transfer(String fromActno, String toActno, double money) {
// 查询账户余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new RuntimeException("账户余额不足");
}
// 如果余额充足开始转账
Account toAct = accountDao.selectByActno(toActno);
// 将内存中两个对象的余额先修改
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
// 将数据库账户的余额更新
int count = accountDao.update(fromAct);
count += accountDao.update(toAct);
if (count != 2) {
throw new RuntimeException("转账失败,请联系银行");
}
}
}
第六步: 编写spring
的配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.powernode.bank"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
beans>
第七步: 编写测试程序,模拟表示/控制层
处理用户的需求,后台调用对应业务层完成业务
public class BankTest {
@Test
public void testTransfer(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
try {
accountService.transfer("act-001", "act-002", 10000);
System.out.println("转账成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
第八步: 模拟转账异常,如果在更新两个账户余额的操作中出现了异常,此时就会出现前者的余额减了但后者的余额没有加上
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Override
public void transfer(String fromActno, String toActno, double money) {
// 查询账户余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new RuntimeException("账户余额不足");
}
// 余额充足,开始转账
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
int count = accountDao.update(fromAct);
// 模拟异常
String s = null;
s.toString();
count += accountDao.update(toAct);
if (count != 2) {
throw new RuntimeException("转账失败,请联系银行");
}
}
}
编程式事务
编程式事务(了解)
,自己在业务方法中手写控制事务的代码
@Override
public void transfer(String fromActno, String toActno, double money) {
// 第一步开启事务
// 第二步执行核心业务逻辑
// 第三步如果执行核心业务流程中没有异常则提交事务
// 第四步如果执行核心业务流程中有异常则回滚事务
}
声明式事务
声明式事务(常用)
,基于注解方式
或XML配置方式
实现事务的控制
Spring事务管理的底层是基于AOP实现的,所以Spring专门针对事务开发了一套APIPlatformTransactionManager(事务管理器的核心接口)
并且有两个实现类
实现类 | 描述 |
---|---|
DataSourceTransactionManager | 支持JdbcTemplate、MyBatis、Hibernate 等事务管理 |
JtaTransactionManager | 支持分布式事务管理 |
第一步: 在spring配置文件
中引入tx
的命名空间及其约束文件,spring-jdbc
依赖中关联了事务相关的依赖
第二步:在spring配置文件中配置事务管理器
,如果在Spring6中使用JdbcTemplate
就要使用DataSourceTransactionManager
事务管理器来管理事务
Connection连接
连接对象开启关闭事务,所以需要给事务管理器配置数据源
第三步: 在spring配置文件中开启事务注解驱动器
告诉Spring框架采用注解的方式控制事务
@Transactional
注解所标识的方法就是连接点(可以织入切面的位置)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.powernode.bank"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
beans>
第四步: 在Service层中的业务类上或业务方法上添加@Transactional
注解,表示当前类/方法开启了事务,业务方法中批量的DML操作可以保证同时成功或失败
@Service("accountService")
@Transactional// 开启事务
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Override
public void transfer(String fromActno, String toActno, double money) {
// 查询账户余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new RuntimeException("账户余额不足");
}
// 余额充足,开始转账
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
int count = accountDao.update(fromAct);
// 模拟异常
String s = null;
s.toString();
count += accountDao.update(toAct);
if (count != 2) {
throw new RuntimeException("转账失败,请联系银行");
}
}
}