您现在的位置是:首页 >技术杂谈 >Spring 事务网站首页技术杂谈
Spring 事务
事务:
在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。 多条DML要么同时成功,要么同时失败,叫做事务Transaction
DML 代表数据操作语言(Data Manipulation Language)
事务的四个处理过程:
第一步:开启事务 (start transaction)
第二步:执行核心业务代码
第三步:提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)
第四步:回滚事务(如果核心业务处理过程中出现异常)(rollback transaction)
事务的四个特性:
原子性(Atomicity):原子性指的是事务是一个不可分割的操作单元,要么全部执行成功,要么全部回滚到初始状态,不会出现部分执行的情况。如果事务的所有操作都成功完成,则称为提交(commit),如果任何一个操作失败,则称为回滚(rollback),所有操作都会被撤销。
一致性(Consistency):一致性指的是事务在执行前和执行后,数据库的状态必须保持一致。事务的执行不能破坏数据库的完整性约束,如主键约束、外键约束等。事务的操作应该使数据库从一个一致性状态转换到另一个一致性状态。
隔离性(Isolation):隔离性指的是事务的执行应该与其他并发执行的事务相互隔离,每个事务的操作应该对其他事务是隐私的,不会互相干扰。隔离性保证了并发事务的独立性和一致性。
持久性(Durability):持久性指的是一旦事务提交成功,它对数据库的修改应该是永久的,即使在系统故障或断电情况下,修改的数据也应该能够被恢复到提交后的状态。持久性通过将事务的操作记录到物理存储介质(如磁盘)来实现。
以银行账户转账为例:
数据库:
#创建数据库并设置字符集
create database myspring character set utf8mb4;
创建表
CREATE TABLE `account`(
#账户
`actno` VARCHAR(255),
#余额
`balance` INT,
PRIMARY KEY(`actno`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
select * from `account`
插入数据
insert into `account`
(`actno`,`balance`)
VALUES
("001",25000000),
("002",12000000)
select * from `account`
pojo Plain Old Java Object"(简单老式 Java 对象)
package com.bank.dao.com.bank.pojo;
public class Account {
private String actno;
private Double balance;
@Override
public String toString() {
return "Account{" +
"actno='" + actno + ''' +
", balance=" + balance +
'}';
}
public Account() {
}
public Account(String actno, Double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
}
dao 接口
package com.bank.dao;
import com.bank.pojo.Account;
public interface AccountDao {
/**
* 根据账号查询余额
* @param actno
* @return
*/
Account selectByActno(String actno);
/**
* 更新账户
* @param act
* @return
*/
int update(Account act);
}
dao实现类
package com.bank.dao.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
//JdbcTemplate 由spring提供用以简化jdbc
@Resource(name = "jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Override
public Account selectByActno(String actno) {
String sql = "select actno, balance from account where actno = ?";
Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
return account;
}
@Override
public int update(Account act) {
String sql = "update account set balance = ? where actno = ?";
int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());
return count;
}
}
service 接口
package com.bank.service;
public interface AccountService {
/**
* 转账
* @param fromActno
* @param toActno
* @param money
*/
void transfer(String fromActno, String toActno, double money);
}
service 实现类
package com.bank.service.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
/**
* @program: spring_learn
* @description: implement AccountService
* @author: Mr.Wang
* @create: 2023-06-03 21:30
**/
@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("转账失败,请联系银行");
}
}
}
配置类
package com;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
/**
* @program: spring_learn
* @description: configuration class for spring
* @author: Mr.Wang
* @create: 2023-06-03 21:35
**/
@Configuration
@ComponentScan
public class config {
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/myspring");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean("jdbcTemplate")
public JdbcTemplate getJdbcTemplate(){
return new JdbcTemplate(getDataSource());
}
}
test:
package com;
import com.bank.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @program: spring_learn
* @description: test bank
* @author: Mr.Wang
* @create: 2023-06-03 22:06
**/
public class BankTest {
@Test
public void test01(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(config.class);
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transfer("001","002",50000);
}
}
result
模拟异常:
package com.bank.service.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
/**
* @program: spring_learn
* @description: implement AccountService
* @author: Mr.Wang
* @create: 2023-06-03 21:30
**/
@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("转账失败,请联系银行");
}
}
}
发生异常
int count = accountDao.update(fromAct);执行了 count += accountDao.update(toAct);没有执行
001 balance 减少了50000,002没有变
消失了50000
改进:
Spring事务管理API
Spring对事务的管理底层实现方式是基于AOP实现的。
PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:
- DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
- JtaTransactionManager:支持分布式事务管理。
如果要在Spring6中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务。(Spring内置写好了,可以直接用。)
配置文件 spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.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/myspring"/>
<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注解
package com.bank.service.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @program: spring_learn
* @description: implement AccountService
* @author: Mr.Wang
* @create: 2023-06-03 21:30
**/
@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("转账失败,请联系银行");
}
}
}
data
test
package com;
import com.bank.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @program: spring_learn
* @description: test bank
* @author: Mr.Wang
* @create: 2023-06-03 22:06
**/
public class BankTest {
@Test
public void test01(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transfer("001","002",50000);
}
}
result:
数据没有改变
事务属性:
1.事务传播行为:
事务传播行为是指在一个方法中发生的事务对于其他方法调用的影响方式。当一个方法调用另一个方法时,事务传播行为定义了如何处理被调用方法中的事务。
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
在 Spring 框架中,事务传播行为由 `Propagation` 枚举定义,并可以通过 `@Transactional` 注解或编程方式进行配置。
下面是几种常见的事务传播行为:
1. `REQUIRED`(默认):如果当前没有事务,则创建一个新的事务。如果存在当前事务,则加入到当前事务中。
2. `REQUIRES_NEW`:无论当前是否存在事务,都创建一个新的事务。如果存在当前事务,则将其挂起。
3. `MANDATORY`:必须在一个已存在的事务中执行,否则抛出异常。
4. `NESTED`:如果当前没有事务,则创建一个新的事务。如果存在当前事务,则在当前事务的嵌套事务中执行。嵌套事务是独立提交或回滚的,但是如果外部事务回滚,则会回滚所有嵌套事务。
5. `SUPPORTS`:如果当前存在事务,则加入到当前事务中;如果当前没有事务,则以非事务方式执行。
6. `NOT_SUPPORTED`:以非事务方式执行,并挂起当前事务(如果存在)。
7. `NEVER`:以非事务方式执行,如果当前存在事务,则抛出异常。
以下是常见的七个事务传播行为及其示例:
1. REQUIRED(默认):
- 方法A使用 REQUIRED 事务传播行为,方法B也使用 REQUIRED 事务传播行为,并且方法B被方法A调用。
- 如果方法A已经在一个事务中,则方法B将加入到该事务中执行。
- 如果方法A不在事务中,则方法B将创建一个新的事务并执行。2. REQUIRES_NEW:
- 方法A使用 REQUIRED 事务传播行为,方法B使用 REQUIRES_NEW 事务传播行为,并且方法B被方法A调用。
- 方法A和方法B都将在各自独立的事务中执行。
- 如果方法A已经在一个事务中,则方法A的事务会被挂起,方法B会启动一个新的事务。
- 如果方法A不在事务中,则方法B会创建一个新的事务。3. SUPPORTS:
- 方法A使用 REQUIRED 事务传播行为,方法B使用 SUPPORTS 事务传播行为,并且方法B被方法A调用。
- 如果方法A已经在一个事务中,则方法B将在该事务中执行。
- 如果方法A不在事务中,则方法B将以非事务方式执行。4. NOT_SUPPORTED:
- 方法A使用 REQUIRED 事务传播行为,方法B使用 NOT_SUPPORTED 事务传播行为,并且方法B被方法A调用。
- 方法B将以非事务方式执行,不管方法A是否在事务中。
- 如果方法A在事务中,则方法A的事务会被挂起。5. MANDATORY:
- 方法A使用 REQUIRED 事务传播行为,方法B使用 MANDATORY 事务传播行为,并且方法B被方法A调用。
- 方法B必须在一个已存在的事务中执行,否则将抛出异常。
- 如果方法A在事务中,则方法B将加入到该事务中执行。
- 如果方法A不在事务中,则方法B将抛出异常。6. NEVER:
- 方法A使用 REQUIRED 事务传播行为,方法B使用 NEVER 事务传播行为,并且方法B被方法A调用。
- 方法B将以非事务方式执行,不管方法A是否在事务中。
- 如果方法A在事务中,则方法B将抛出异常。7. NESTED:
- 方法A使用 REQUIRED 事务传播行为,方法B使用 NESTED 事务传播行为,并且方法B被方法A调用。
- 方法B将在方法A的嵌套事务中执行。
- 如果方法A已经在一个事务中,则方法B将在该事务的嵌套事务中执行。
- 如果方法A不在事务中,则方法B将创建一个新的事务。这些例子展示了不同事务传播行为的行为和影响,通过合理选择事务传播行为,可以确保事务的正确隔离和管理,
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
-->
<root level="DEBUG">
<appender-ref ref="myspringlog"/>
</root>
</loggers>
<appenders>
<!--输出日志信息到控制台-->
<console name="myspringlog" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
</console>
</appenders>
</configuration>
依赖
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.19.0</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j2-impl</artifactId> <version>2.19.0</version> </dependency>
data:
使用
@Transactional(propagation = Propagation.REQUIRED)
package com.bank.service.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @program: spring_learn
* @description: implement AccountService
* @author: Mr.Wang
* @create: 2023-06-03 21:30
**/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Override
@Transactional(propagation = Propagation.REQUIRED)
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("转账失败,请联系银行");
}
}
}
package com.bank.service.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @program: spring_learn
* @description:
* @author: Mr.Wang
* @create: 2023-06-04 17:45
**/
@Service
public class AccountService2 {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Resource
private AccountService accountService;
@Transactional(propagation = Propagation.REQUIRED)
public void save(Account act) {
accountService.transfer("001","002",5000);
accountDao.update(act);
}
}
test:
package com;
import com.bank.pojo.Account;
import com.bank.service.impl.AccountService2;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @program: spring_learn
* @description: test bank
* @author: Mr.Wang
* @create: 2023-06-03 22:06
**/
public class BankTest {
@Test
public void test01(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AccountService2 accountService2 = applicationContext.getBean("accountService2", AccountService2.class);
Account act = new Account();
act.setActno("001");
act.setBalance(50000.0);
accountService2.save(act);
}
}
result:
2023-06-04 18:38:01 469 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.bank.service.impl.AccountService2.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2023-06-04 18:38:01 544 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
2023-06-04 18:38:01 799 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@5a101b1c] for JDBC transaction
2023-06-04 18:38:01 799 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5a101b1c] to manual commit
2023-06-04 18:38:01 799 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Participating in existing transaction
2023-06-04 18:38:01 844 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL query
2023-06-04 18:38:01 846 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [select actno, balance from account where actno = ?]
2023-06-04 18:38:01 862 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'actno' to property 'actno' of type 'java.lang.String'
2023-06-04 18:38:01 862 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'balance' to property 'balance' of type 'java.lang.Double'
2023-06-04 18:38:01 877 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL query
2023-06-04 18:38:01 877 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [select actno, balance from account where actno = ?]
2023-06-04 18:38:01 877 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'actno' to property 'actno' of type 'java.lang.String'
2023-06-04 18:38:01 880 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'balance' to property 'balance' of type 'java.lang.Double'
2023-06-04 18:38:01 880 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
2023-06-04 18:38:01 882 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [update account set balance = ? where actno = ?]
2023-06-04 18:38:01 887 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
2023-06-04 18:38:01 887 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [update account set balance = ? where actno = ?]
2023-06-04 18:38:01 887 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
2023-06-04 18:38:01 887 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [update account set balance = ? where actno = ?]
2023-06-04 18:38:01 893 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction commit
2023-06-04 18:38:01 893 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@5a101b1c]
2023-06-04 18:38:01 893 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5a101b1c] after transactionProcess finished with exit code 0
使用
@Transactional(propagation = Propagation.REQUIRED)
对save方法开启一个事务A,在save中调用 transfer 方法,transfer 方法与save方法 中的
accountDao.update(act);操作
合并共同组成事务A 操作内容
模拟异常
package com.bank.service.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @program: spring_learn
* @description: implement AccountService
* @author: Mr.Wang
* @create: 2023-06-03 21:30
**/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Override
@Transactional(propagation = Propagation.REQUIRED)
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("转账失败,请联系银行");
}
}
}
retest:
2023-06-04 18:41:37 741 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.bank.service.impl.AccountService2.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2023-06-04 18:41:37 789 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
2023-06-04 18:41:38 077 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@29f0802c] for JDBC transaction
2023-06-04 18:41:38 077 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@29f0802c] to manual commit
2023-06-04 18:41:38 083 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Participating in existing transaction
2023-06-04 18:41:38 137 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL query
2023-06-04 18:41:38 137 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [select actno, balance from account where actno = ?]
2023-06-04 18:41:38 169 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'actno' to property 'actno' of type 'java.lang.String'
2023-06-04 18:41:38 169 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'balance' to property 'balance' of type 'java.lang.Double'
2023-06-04 18:41:38 177 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL query
2023-06-04 18:41:38 177 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [select actno, balance from account where actno = ?]
2023-06-04 18:41:38 181 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'actno' to property 'actno' of type 'java.lang.String'
2023-06-04 18:41:38 181 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'balance' to property 'balance' of type 'java.lang.Double'
2023-06-04 18:41:38 181 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
2023-06-04 18:41:38 183 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [update account set balance = ? where actno = ?]
2023-06-04 18:41:38 183 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Participating transaction failed - marking existing transaction as rollback-only
2023-06-04 18:41:38 183 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Setting JDBC transaction [com.mysql.cj.jdbc.ConnectionImpl@29f0802c] rollback-only
2023-06-04 18:41:38 183 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
2023-06-04 18:41:38 183 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@29f0802c]
2023-06-04 18:41:38 200 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@29f0802c] after transaction
data没有变化
使用
@Transactional(propagation = Propagation.REQUIRED)
对save方法开启一个事务A,在save中调用 transfer 方法,transfer 方法与save方法 中的
accountDao.update(act);操作
合并共同组成事务A 操作内容
当调用 transfer 方法 出现异常时,将会回滚
accountDao.update(act); 与 调用 transfer 方法 共同组成同一个事务,
所以 2 个操作都会被回滚
当使用
@Transactional(propagation = Propagation.REQUIRES_NEW)
package com.bank.service.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @program: spring_learn
* @description: implement AccountService
* @author: Mr.Wang
* @create: 2023-06-03 21:30
**/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
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("转账失败,请联系银行");
}
}
}
package com.bank.service.impl;
import com.bank.dao.AccountDao;
import com.bank.pojo.Account;
import com.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @program: spring_learn
* @description:
* @author: Mr.Wang
* @create: 2023-06-04 17:45
**/
@Service
public class AccountService2 {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Resource
private AccountService accountService;
@Transactional(propagation = Propagation.REQUIRED)
public void save(Account act) {
try {
accountService.transfer("001", "002", 5000);
}catch(Exception e){
}
accountDao.update(act);
}
}
初始data
retest:
2023-06-04 19:04:03 007 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.bank.service.impl.AccountService2.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2023-06-04 19:04:03 062 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
2023-06-04 19:04:03 326 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@29f0802c] for JDBC transaction
2023-06-04 19:04:03 326 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@29f0802c] to manual commit
2023-06-04 19:04:03 339 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Suspending current transaction, creating new transaction with name [com.bank.service.impl.AccountServiceImpl.transfer]
2023-06-04 19:04:03 349 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@5eccd3b9] for JDBC transaction
2023-06-04 19:04:03 349 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5eccd3b9] to manual commit
2023-06-04 19:04:03 405 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL query
2023-06-04 19:04:03 405 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [select actno, balance from account where actno = ?]
2023-06-04 19:04:03 437 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'actno' to property 'actno' of type 'java.lang.String'
2023-06-04 19:04:03 437 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'balance' to property 'balance' of type 'java.lang.Double'
2023-06-04 19:04:03 437 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL query
2023-06-04 19:04:03 437 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [select actno, balance from account where actno = ?]
2023-06-04 19:04:03 437 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'actno' to property 'actno' of type 'java.lang.String'
2023-06-04 19:04:03 437 [main] DEBUG org.springframework.jdbc.core.BeanPropertyRowMapper - Mapping column 'balance' to property 'balance' of type 'java.lang.Double'
2023-06-04 19:04:03 437 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
2023-06-04 19:04:03 437 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [update account set balance = ? where actno = ?]
2023-06-04 19:04:03 457 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
2023-06-04 19:04:03 457 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@5eccd3b9]
2023-06-04 19:04:03 460 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5eccd3b9] after transaction
2023-06-04 19:04:03 460 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Resuming suspended transaction after completion of inner transaction
2023-06-04 19:04:03 460 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
2023-06-04 19:04:03 460 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [update account set balance = ? where actno = ?]
2023-06-04 19:04:03 460 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction commit
2023-06-04 19:04:03 460 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@29f0802c]
2023-06-04 19:04:03 468 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@29f0802c] after transactionProcess finished with exit code 0
使用
@Transactional(propagation = Propagation.REQUIRES_NEW)
对save方法开启一个事务A,在save中调用 transfer 方法,时会开启事务B
2个事务相互独立
当调用 transfer 方法 出现异常时,将会回滚 transfer 方法 所作的操作(回滚事务B)
但事务A 中accountDao.update(act);操作 并不会一起回滚
只会 回滚事务B所做的操作,不属于事务B而属于事务A的操作并不会一起回滚