1. 引言
在开发Web应用程序时,经常会遇到需要对数据库进行事务管理的情况。Spring 框架提供了两种事务管理的方式:声明式事务和编程式事务。本文将介绍这两种事务管理方式的源码、区别、优缺点、适用场景以及在实际项目中的应用。
2. 声明式事务
声明式事务是通过使用 Spring AOP (Aspect Oriented Programming) 技术来实现的。在声明式事务中,我们只需要在方法或类上添加 @Transactional 注解,Spring 框架会自动为我们处理事务的开始、提交和回滚。
2.1 源码解析
Spring 框架中声明式事务的源码主要包括以下几个核心组件:
2.1.1 TransactionalAspectSupport
TransactionalAspectSupport 是声明式事务的核心类,负责将 @Transactional 注解解析成事务的配置信息,并进行事务的管理和控制。它是一个抽象类,具体的事务管理功能由继承它的子类提供。
2.1.2 AnnotationTransactionAttributeSource
AnnotationTransactionAttributeSource 是用于解析 @Transactional 注解的类,它会解析注解中的配置信息,并生成对应的事务属性对象 TransactionAttribute。
2.1.3 PlatformTransactionManager
PlatformTransactionManager 是一个接口,定义了事务管理器的标准接口。具体的事务管理器可以根据具体的数据库类型或需求来选择,例如 DataSourceTransactionManager、JpaTransactionManager 等。
2.2 区别与优缺点
2.2.1 区别
- 声明式事务是通过注解来配置事务的属性,使得代码更加简洁、清晰,且易于维护。
- 在声明式事务中,事务的控制是基于方法调用的,即当一个带有 @Transactional 注解的方法被调用时,Spring 框架会自动管理事务的开始、提交和回滚。
2.2.2 优点
- 代码简洁:使用注解方式配置事务,避免了手动在代码中编写事务管理的相关代码。
- 易于维护:将事务的配置与业务逻辑分离,便于修改和调整事务的属性。
- 提高可读性:通过在方法或类上添加 @Transactional 注解,显式地表明该方法或类需要进行事务管理。
2.2.3 缺点
- 对基于接口的代理支持不友好。声明式事务是通过 Spring AOP 实现的,而基于接口的代理需要使用 JDK 动态代理。由于 JDK 动态代理只针对接口进行代理,无法对类进行代理,因此无法应用于基于接口的代理场景。
2.3 适用场景
声明式事务适用于以下场景:
- 对业务方法进行事务管理,能够提高代码的可读性和维护性。
- 需要对多个数据库操作进行事务管理的情况。
2.4 实战
2.4.1 添加依赖
在 pom.xml 文件中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.4.2 配置数据源
在 application.properties 文件中配置数据库的连接信息:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
2.4.3 编写示例代码
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users")
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@PostMapping("/user")
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
}
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> getAllUsers() {
return userRepository.findAll();
}
public User createUser(User user) {
return userRepository.save(user);
}
}
public interface UserRepository extends JpaRepository<User, Long> {
}
上述代码中,我们在 UserService 类上添加了 @Transactional 注解,表示该类中的所有方法都需要进行事务管理。getUser 和 createUser 方法将被自动管理事务的开始、提交和回滚。
3. 编程式事务
编程式事务是通过编写代码的方式来实现事务的管理。使用编程式事务需要手动在代码中编写事务的开始、提交和回滚等操作。
3.1 源码解析
编程式事务主要依赖于以下两个接口:
3.1.1 PlatformTransactionManager
PlatformTransactionManager 接口定义了事务管理器的标准接口,具体的事务管理器需要实现该接口。
3.1.2 TransactionDefinition
TransactionDefinition 接口定义了事务的属性,包括隔离级别、传播行为和超时时间等。
3.2 区别与优缺点
3.2.1 区别
- 编程式事务需要手动在代码中编写事务相关的操作,代码中包含了明确的事务开始、提交和回滚等操作。
- 编程式事务比声明式事务更加灵活,可以根据需要手动控制事务的开始、提交和回滚。
3.2.2 优点
- 更灵活:可以根据需要手动控制事务的开始、提交和回滚。
- 更细粒度的控制:可以根据需要指定事务的隔离级别、传播行为和超时时间等。
- 支持基于接口的代理:编程式事务可以应用于基于接口的代理场景。
3.2.3 缺点
- 代码冗余:编程式事务需要手动在代码中编写事务相关的操作,代码会更加冗长,维护性较差。
- 可读性差:代码中包含了明确的事务开始、提交和回滚等操作,可读性较差。
3.3 适用场景
编程式事务适用于以下场景:
- 需要更灵活和细粒度控制事务的开始、提交和回滚的情况。
- 基于接口的代理场景。
3.4 实战
3.4.1 添加依赖
在 pom.xml 文件中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.4.2 配置数据源
在 application.properties 文件中配置数据库的连接信息:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
3.4.3 编写示例代码
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users")
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@PostMapping("/user")
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PlatformTransactionManager transactionManager;
public List<User> getAllUsers() {
TransactionDefinition txDef = new DefaultTransactionDefinition();
TransactionStatus txStatus = transactionManager.getTransaction(txDef);
try {
List<User> users = userRepository.findAll();
transactionManager.commit(txStatus);
return users;
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}
}
public User createUser(User user) {
TransactionDefinition txDef = new DefaultTransactionDefinition();
TransactionStatus txStatus = transactionManager.getTransaction(txDef);
try {
User savedUser = userRepository.save(user);
transactionManager.commit(txStatus);
return savedUser;
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}
}
}
public interface UserRepository extends JpaRepository<User, Long> {
}
上述代码中,我们通过调用 transactionManager
的方法来手动控制事务的开始、提交和回滚等操作。
4. 总结
声明式事务 | 编程式事务 | |
---|---|---|
源码解析 | 通过 Spring AOP 实现 | 通过编写代码手动控制事务的开始、提交和回滚等操作 |
区别 | 配置简单、代码清晰,易于维护 | 配置复杂,代码冗长,可读性差 |
优点 | 代码简洁、易维护 | 更灵活、细粒度控制事务的行为 |
缺点 | 对基于接口的代理支持不友好 | 代码冗长,可读性差 |
适用场景 | 对业务方法进行事务管理,需提高代码的可读性和维护性 | 需要更灵活、细粒度控制事务的情况,基于接口的代理场景 |
实战示例 | 在 Service 类或方法上添加 @Transactional 注解 | 在代码中通过调用事务管理器的方法手动控制事务的开始、提交和回滚等操作 |
无论选择声明式事务还是编程式事务,对于简单的事务需求,可以直接使用声明式事务,它能够提供简洁、清晰的代码结构。而对于更复杂、灵活的事务需求,编程式事务提供了更多的细粒度控制,在这种情况下,编程式事务是一个更好的选择。
本文来自极简博客,作者:柔情似水,转载请注明原文链接:Spring/Spring Boot中的声明式事务和编程式事务