在 Spring 框架中,事务(Transaction)管理是保障数据一致性和完整性的重要机制。Spring 支持两种主要的事务实现方式:
✅ 一、编程式事务(Programmatic Transaction)
手动控制事务的开启、提交与回滚。更灵活但更冗长,推荐用于底层框架或需要动态控制事务粒度的地方。
✳️ 实现方式 1:使用 TransactionTemplate
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private UserRepository userRepository;
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
transactionTemplate.executeWithoutResult(status -> {
try {
userRepository.decreaseBalance(fromId, amount);
userRepository.increaseBalance(toId, amount);
} catch (Exception e) {
status.setRollbackOnly(); // 手动回滚
throw e;
}
});
}
}
✳️ 实现方式 2:使用 PlatformTransactionManager
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
public void updateUser() {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// your db operations
transactionManager.commit(status);
} catch (RuntimeException ex) {
transactionManager.rollback(status);
throw ex;
}
}
}
✅ 二、声明式事务(Declarative Transaction)
基于注解或 XML 配置,由 Spring AOP 自动管理事务。推荐方式,更简洁、更易维护。
✳️ 注解方式(最常用)
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
// 如果出现异常,事务自动回滚
}
}
常用注解参数:
属性 | 说明 |
---|---|
propagation | 事务的传播行为,默认 REQUIRED |
isolation | 事务隔离级别,默认 DEFAULT |
timeout | 超时时间(秒) |
readOnly | 是否只读,优化性能 |
rollbackFor | 指定哪些异常触发回滚(如:Exception.class ) |
noRollbackFor | 指定哪些异常不触发回滚 |
示例:
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
rollbackFor = {SQLException.class}
)
✳️ XML 配置方式(老项目中可能见到)
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* com.example.service..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>
</aop:config>
✅ 三、编程式 vs 声明式事务 对比
对比项 | 编程式事务 | 声明式事务 |
---|---|---|
控制粒度 | 细,灵活控制事务边界 | 粒度固定,统一处理事务 |
实现复杂度 | 代码多、冗余 | 使用简单、维护性强 |
推荐场景 | 动态事务、框架底层逻辑 | 大多数业务服务逻辑推荐使用 |
✅ 四、使用声明式事务的注意事项
- 方法必须是
public
且被 Spring 容器管理(即由容器代理)才能生效 - 默认只对 运行时异常(
RuntimeException
)回滚 - 事务方法间内部调用不会生效(需使用代理或注入自身)
好的!下面我帮你写一个完整的 Spring Boot + MyBatis + 声明式事务示例项目,展示事务的标准用法、异常回滚和事务传播特性。示例中包含:
- 两个 Service 方法,模拟转账业务
- 声明式事务注解
@Transactional
- 事务异常回滚演示
- 事务传播测试
Spring Boot + MyBatis 事务示例
1. 项目依赖(pom.xml
)
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Starter JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 数据库表(MySQL)
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(50),
balance DECIMAL(10,2)
);
INSERT INTO account (user_name, balance) VALUES ('Alice', 1000.00), ('Bob', 1000.00);
3. 实体类 Account.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private Integer id;
private String userName;
private BigDecimal balance;
}
4. Mapper 接口 AccountMapper.java
@Mapper
public interface AccountMapper {
Account findByUserName(String userName);
void updateBalance(@Param("userName") String userName, @Param("amount") BigDecimal amount);
}
5. Mapper XML AccountMapper.xml
<mapper namespace="com.example.mapper.AccountMapper">
<select id="findByUserName" resultType="com.example.domain.Account">
SELECT * FROM account WHERE user_name = #{userName}
</select>
<update id="updateBalance">
UPDATE account SET balance = balance + #{amount} WHERE user_name = #{userName}
</update>
</mapper>
6. Service 层 AccountService.java
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
/**
* 转账操作,声明式事务控制
*/
@Transactional(rollbackFor = Exception.class)
public void transfer(String fromUser, String toUser, BigDecimal amount) {
// 扣款
accountMapper.updateBalance(fromUser, amount.negate());
// 模拟异常,测试事务回滚
if (amount.compareTo(BigDecimal.valueOf(500)) > 0) {
throw new RuntimeException("转账金额过大,模拟异常回滚");
}
// 收款
accountMapper.updateBalance(toUser, amount);
}
/**
* 测试事务传播,外部方法不加事务,调用内部加事务方法
*/
public void testPropagation(String fromUser, String toUser, BigDecimal amount) {
saveWithoutTransaction();
saveWithTransaction(fromUser, toUser, amount);
}
@Transactional(propagation = Propagation.REQUIRED)
public void saveWithTransaction(String fromUser, String toUser, BigDecimal amount) {
accountMapper.updateBalance(fromUser, amount.negate());
accountMapper.updateBalance(toUser, amount);
}
public void saveWithoutTransaction() {
// 一般操作,不开启事务
System.out.println("无事务操作");
}
}
7. Controller 层 AccountController.java
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
@PostMapping("/transfer")
public String transfer(@RequestParam String from, @RequestParam String to, @RequestParam BigDecimal amount) {
try {
accountService.transfer(from, to, amount);
return "转账成功";
} catch (Exception e) {
return "转账失败,原因:" + e.getMessage();
}
}
@PostMapping("/testPropagation")
public String testPropagation(@RequestParam String from, @RequestParam String to, @RequestParam BigDecimal amount) {
try {
accountService.testPropagation(from, to, amount);
return "事务传播测试成功";
} catch (Exception e) {
return "事务传播测试失败:" + e.getMessage();
}
}
}
8. 说明
@Transactional
注解控制事务,异常自动回滚rollbackFor = Exception.class
表示所有异常都会回滚,不限于 RuntimeException- 测试转账金额大于 500 时抛异常,演示事务回滚效果
- 事务传播示例展示外部无事务,内部有事务的方法调用
好的!下面是完整的 Spring Boot + MyBatis + 事务管理 示例,包括:
application.yml
配置- 编程式事务示例
- 声明式事务示例(回顾)
- 简单测试用例示例
1. Spring Boot 配置文件(src/main/resources/application.yml
)
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis 配置(可选)
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.domain
# 开启事务管理(Spring Boot 默认开启)
spring:
main:
allow-bean-definition-overriding: true
2. 编程式事务示例(基于 PlatformTransactionManager
)
@Service
public class ProgrammaticTransactionService {
private final PlatformTransactionManager transactionManager;
private final AccountMapper accountMapper;
public ProgrammaticTransactionService(PlatformTransactionManager transactionManager, AccountMapper accountMapper) {
this.transactionManager = transactionManager;
this.accountMapper = accountMapper;
}
public void transferMoney(String fromUser, String toUser, BigDecimal amount) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
accountMapper.updateBalance(fromUser, amount.negate());
// 模拟异常
if (amount.compareTo(BigDecimal.valueOf(500)) > 0) {
throw new RuntimeException("金额过大,回滚事务");
}
accountMapper.updateBalance(toUser, amount);
transactionManager.commit(status);
} catch (RuntimeException e) {
transactionManager.rollback(status);
throw e;
}
}
}
3. 声明式事务示例(回顾)
@Service
public class DeclarativeTransactionService {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(String fromUser, String toUser, BigDecimal amount) {
accountMapper.updateBalance(fromUser, amount.negate());
if (amount.compareTo(BigDecimal.valueOf(500)) > 0) {
throw new RuntimeException("金额过大,回滚事务");
}
accountMapper.updateBalance(toUser, amount);
}
}
4. 简单测试用例示例(JUnit 5)
@SpringBootTest
public class TransactionTest {
@Autowired
private DeclarativeTransactionService declarativeTransactionService;
@Autowired
private ProgrammaticTransactionService programmaticTransactionService;
@Autowired
private AccountMapper accountMapper;
@Test
void testDeclarativeTransactionRollback() {
BigDecimal amount = BigDecimal.valueOf(600);
Assertions.assertThrows(RuntimeException.class, () -> {
declarativeTransactionService.transfer("Alice", "Bob", amount);
});
// 验证余额未变(假设初始为1000)
Account alice = accountMapper.findByUserName("Alice");
Account bob = accountMapper.findByUserName("Bob");
Assertions.assertEquals(BigDecimal.valueOf(1000), alice.getBalance());
Assertions.assertEquals(BigDecimal.valueOf(1000), bob.getBalance());
}
@Test
void testProgrammaticTransactionRollback() {
BigDecimal amount = BigDecimal.valueOf(600);
Assertions.assertThrows(RuntimeException.class, () -> {
programmaticTransactionService.transferMoney("Alice", "Bob", amount);
});
// 验证余额未变
Account alice = accountMapper.findByUserName("Alice");
Account bob = accountMapper.findByUserName("Bob");
Assertions.assertEquals(BigDecimal.valueOf(1000), alice.getBalance());
Assertions.assertEquals(BigDecimal.valueOf(1000), bob.getBalance());
}
}
5. 说明
- 编程式事务通过
PlatformTransactionManager
手动开启提交或回滚,适合复杂场景 - 声明式事务通过
@Transactional
注解声明,代码简洁,自动管理事务边界 - 测试用例验证转账超额时事务回滚,余额不变
发表回复