Spring/Spring Boot中的声明式事务和编程式事务

柔情似水 2024-06-09 ⋅ 26 阅读

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 注解在代码中通过调用事务管理器的方法手动控制事务的开始、提交和回滚等操作

无论选择声明式事务还是编程式事务,对于简单的事务需求,可以直接使用声明式事务,它能够提供简洁、清晰的代码结构。而对于更复杂、灵活的事务需求,编程式事务提供了更多的细粒度控制,在这种情况下,编程式事务是一个更好的选择。


全部评论: 0

    我有话说: