Spring事务-两种开启事务管理的方式
- 1、前期准备
- 2、基于注解的声明式事务管理
- 3、基于编程式的事务管理
- 4、声明式事务失效的情况
例子:假设有一个银行转账的业务,其中涉及到从一个账户转钱到另一个账户。在这个业务中,我们需要保证要么两个账户都成功更新,要么都不更新,以避免出现数据不一致的情况。以下是基于注解的声明式事务管理和编程式事务管理的示例:
1、前期准备
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.5.4</version>
</dependency>
首先是实体类 Account
:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@TableName("account")
public class Account {
@Id
private Long id; //银行账户的唯一标识符
private String accountNumber; //银行账户的账号,用于唯一标识一个账户
private double balance; //银行账户的余额,表示账户当前的可用资金数量
// getters and setters
}
然后是 AccountRepository
:
import org.springframework.data.jpa.repository.JpaRepository;
@Mapper
public interface AccountRepository extends JpaRepository<Account, Long> {
Account findByAccountNumber(String accountNumber);
}
然后是控制器类 BankController
:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BankController {
@Autowired
private BankService bankService;
@PostMapping("/transfer")
public String transfer(@RequestBody TransferRequest request) {
bankService.transfer(request.getFromAccount(), request.getToAccount(), request.getAmount());
return "Transfer successful";
}
}
接下来是请求体类 TransferRequest
:
public class TransferRequest {
private String fromAccount;
private String toAccount;
private double amount;
// getters and setters
}
2、基于注解的声明式事务管理
这种方式使用注解来定义事务,通过在需要进行事务管理的方法上添加相应的注解来标识事务的边界和属性。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BankService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transfer(String fromAccount, String toAccount, double amount) {
Account from = accountRepository.findByAccountNumber(fromAccount);
Account to = accountRepository.findByAccountNumber(toAccount);
from.setBalance(from.getBalance() - amount);
to.setBalance(to.getBalance() + amount);
accountRepository.save(from);
int i = 1/0;
accountRepository.save(to);
}
}
报错,事务回滚
在上面的示例中,@Transactional
注解被用于标记transfer
方法。这表示transfer
方法将被Spring框架管理事务。如果该方法执行过程中发生异常,Spring会回滚所有的数据库操作,以保证数据的一致性。
3、基于编程式的事务管理
编程式事务管理是一种通过编程方式手动控制事务的管理过程。与声明式事务管理相比,它不依赖于特定的注解或配置,而是在代码中显式地编写事务管理逻辑。在编程式事务管理中,开发人员需要手动管理事务的开始、提交、回滚等过程。
编程式事务管理的主要原理包括以下几个方面:
-
事务定义(Transaction Definition): 在编程式事务管理中,首先需要定义事务的属性,包括事务的传播行为、隔离级别、超时时间等。这些定义将决定事务的行为。
-
事务管理器(Transaction Manager): 事务管理器负责实际管理事务,包括事务的开始、提交、回滚等操作。在编程式事务管理中,通常需要手动获取事务管理器,并调用其方法来管理事务。
-
事务的控制: 在编程式事务管理中,开发人员需要显式地控制事务的开始、提交、回滚等过程。这通常通过调用事务管理器的方法来实现,如获取事务、提交事务、回滚事务等。
-
异常处理: 在事务管理过程中,可能会出现各种异常情况。开发人员需要适当地处理这些异常,例如在捕获到异常时执行事务的回滚操作,以保证数据的一致性。
-
事务边界: 在编程式事务管理中,需要明确定义事务的边界,即事务开始和结束的位置。通常事务的边界由业务逻辑决定,在业务逻辑的开始处开启事务,在结束处提交或回滚事务。
首先需要定义事务管理器 Bean: 在 Spring Boot 应用程序的配置类中,使用 @Bean 注解定义一个名为 transactionManager 的 DataSourceTransactionManager Bean。确保该 Bean 使用了正确的数据源。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
service
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BankService {
@Autowired
private AccountRepository accountRepository;
@Autowired
private DataSourceTransactionManager transactionManager;
public void transfer(String fromAccount, String toAccount, double amount) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("transaction-1");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
Account from = accountRepository.findByAccountNumber(fromAccount);
Account to = accountRepository.findByAccountNumber(toAccount);
from.setBalance(from.getBalance() - amount);
to.setBalance(to.getBalance() + amount);
accountRepository.save(from);
accountRepository.save(to);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
在这个示例中,我们直接使用了DataSourceTransactionManager
来手动管理事务。我们首先定义了一个事务的定义(DefaultTransactionDefinition
),然后使用该定义来开启一个事务。如果执行过程中发生异常,我们手动回滚事务;如果一切正常,则手动提交事务。
4、声明式事务失效的情况
- @Transactional 应用在非 public 修饰的方法上
- @Transactional 注解属性 propagation 设置错误
- @Transactional 注解属性 rollbackFor 设置错误
- 同一个类中方法调用,导致@Transactional失效
- 异常被catch捕获导致@Transactional失效
- 数据库引擎不支持事务
笔者有空再针对这几种情况进行说明