本地事务是指在单一数据库系统内执行的一组操作,这些操作要么全部完成,要么全部不执行,是一个不可分割的工作单元。本地事务具有ACID特性:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JdbcTransactionExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/test";
private static final String USER = "root";
private static final String PASS = "root";
public static void main(String[] args) {
Connection conn = null;
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try {
// 1. 获取数据库连接
conn = DriverManager.getConnection(DB_URL, USER, PASS);
// 2. 关闭自动提交
conn.setAutoCommit(false);
// 3. 执行SQL操作
String sql1 = "INSERT INTO users (name, email) VALUES (?, ?)";
stmt1 = conn.prepareStatement(sql1);
stmt1.setString(1, "John Doe");
stmt1.setString(2, "john.doe@example.com");
stmt1.executeUpdate();
// 模拟异常
// int a = 0/0;
String sql2 = "UPDATE accounts SET balance = balance - 100 WHERE user_id = ?";
stmt2 = conn.prepareStatement(sql2);
stmt2.setInt(1, 1);
stmt2.executeUpdate();
// 4. 提交事务
conn.commit();
System.out.println("Transaction committed successfully.");
} catch (SQLException e) {
if (conn != null) {
try {
// 5. 回滚事务
conn.rollback();
System.out.println("Transaction rolled back.");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 6. 关闭连接
try {
if (stmt1 != null) stmt1.close();
if (stmt2 != null) stmt2.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
编程式事务管理是指在代码中显式地控制事务的开始、提交和回滚。这种方式提供了更大的灵活性,但也会增加代码的复杂性。
TransactionTemplate是Spring提供的一个类,用于简化编程式事务管理。它封装了事务管理的逻辑,使得开发者可以更方便地控制事务。
首先,需要配置一个TransactionTemplate bean。通常在Spring Boot项目中,可以通过DataSourceTransactionManager来配置。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
// 设置事务隔离级别
// transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// 设置事务只读
// transactionTemplate.setReadOnly(true);
// 设置传播行为
// transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 设置超时时间为30秒
// transactionTemplate.setTimeout(30);
return transactionTemplate;
}
}
注意: 在Spring
Boot中,PlatformTransactionManager、TransactionTemplate通常会被JPA、JDBC、MyBatis、Hibernate等常见框架自动配置和注入,所以通常不需要手动配置PlatformTransactionManager、TransactionTemplate。
在需要事务管理的方法中,注入TransactionTemplate并使用它来控制事务。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private AccountRepository accountRepository;
@Autowired
private TransactionTemplate transactionTemplate;
public void registerUser(User user) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 1. 保存用户信息
userRepository.save(user);
// 设置保存点
// Object savepoint = status.createSavepoint();
// ... 其他操作
// 操作失败,回滚到保存点
// status.rollbackToSavepoint(savepoint);
// 2. 更新账户余额
Account account = accountRepository.getById(user.getId());
account.setBalance(account.getBalance() - 100);
accountRepository.updateById(account);
// 释放保存点
// status.releaseSavepoint(savepoint);
} catch (Exception e) {
// 3. 设置事务回滚
status.setRollbackOnly();
e.printStackTrace();
}
}
});
}
}
除了TransactionTemplate,还可以直接使用PlatformTransactionManager来管理事务。
与前面的配置相同,确保已经配置了PlatformTransactionManager。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
注意: 在Spring
Boot中,PlatformTransactionManager通常会被JPA、JDBC、MyBatis、Hibernate等常见框架自动配置和注入,所以通常不需要手动配置PlatformTransactionManager。
在需要事务管理的方法中,注入PlatformTransactionManager并使用它来控制事务。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private AccountRepository accountRepository;
@Autowired
private PlatformTransactionManager transactionManager;
public void registerUser(User user) {
// 1. 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 设置传播行为
// def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 设置隔离级别
// def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// 设置是否只读
// def.setReadOnly(false);
// 2. 获取事务状态
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 3. 保存用户信息
userRepository.save(user);
// 创建保存点
// Object savepoint = status.createSavepoint();
// 回滚到保存点
// status.rollbackToSavepoint(savepoint);
// 4. 更新账户余额
Account account = accountRepository.getById(user.getId());
account.setBalance(account.getBalance() - 100);
accountRepository.updateById(account);
// 释放保存点
// status.releaseSavepoint(savepoint);
// 5. 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 6. 回滚事务
transactionManager.rollback(status);
e.printStackTrace();
}
}
}
特性/功能 | TransactionTemplate | PlatformTransactionManager |
---|---|---|
用途 | 提供模板化的编程方式来管理事务,简化事务管理代码。 | 定义事务管理的基本接口,提供事务管理的核心功能。 |
编程模型 | 编程式事务管理。 | 可以用于编程式和声明式事务管理。 |
使用方式 | 直接在代码中使用 TransactionTemplate 来管理事务。 |
通常通过 TransactionTemplate 或 @Transactional 注解使用。 |
配置属性 | 可以设置隔离级别、传播行为、超时时间、只读属性等。 | 通过 TransactionTemplate 配置,也可以通过其他方式配置。 |
回滚规则 | 通过 TransactionCallback 手动处理异常来控制回滚。 |
通过 TransactionAttribute 配置回滚规则。 |
保存点 | 支持保存点(savepoints),可以部分回滚事务。 | 支持保存点(savepoints),可以部分回滚事务。 |
事务状态管理 | 使用 TransactionStatus 对象来管理事务状态。 |
使用 TransactionStatus 对象来管理事务状态。 |
依赖 | 依赖于 PlatformTransactionManager 。 |
是事务管理的核心接口,TransactionTemplate 依赖于它。 |
灵活性 | 提供更高的灵活性,适合复杂的事务逻辑。 | 提供基本的事务管理功能,适合大多数情况。 |
Spring框架提供了强大的声明式事务管理,简化了事务处理代码。通过使用@Transactional注解,可以方便地定义方法级别的事务边界。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private AccountRepository accountRepository;
@Transactional
public void registerUser(User user) {
// 1. 保存用户信息
userRepository.save(user);
// 2. 更新账户余额
Account account = accountRepository.getById(user.getId());
account.setBalance(account.getBalance() - 100);
accountRepository.updateById(account);
// 如果抛出运行时异常,事务将回滚
}
}
常用的 @Transactional 参数及其说明:
该部分转载于 一口气怼完12种@Transactional的失效场景
声明式事务是基于 AOP(面向切面编程)代理机制来拦截方法调用,并在方法执行前后进行事务管理。Spring 生成代理的方式主要有两种:JDK
动态代理和CGLIB 代理。
失效原因:
解决方案:
失效原因:
解决方案:
失效原因:
解决方案:
AspectJ 解决事务失效:
- 引入依赖
<dependency> <groupId>org.aspectjgroupId> <artifactId>aspectjweaverartifactId> dependency>
- 暴露代理对象
在启动类上添加注解@EnableAspectJAutoProxy(exposeProxy=true),暴露代理对象:@Configuration @EnableAspectJAutoProxy(exposeProxy = true) public class AppConfig { }
- 使用代理对象
内部调用失效示例:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class TransactionalService { @Autowired private AnotherService anotherService; @Transactional public void methodA() { // 一些数据库操作 System.out.println("Method A is running"); methodB(); // 内部方法调用 } @Transactional public void methodB() { // 一些数据库操作 System.out.println("Method B is running"); } }
使用代理对象调用示例:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class TransactionalService { @Autowired private AnotherService anotherService; @Transactional public void methodA() { // 一些数据库操作 System.out.println("Method A is running"); ((TransactionalService) AopContext.currentProxy()).methodB(); // 使用AopContext获取代理对象 } @Transactional public void methodB() { // 一些数据库操作 System.out.println("Method B is running"); } }
失效原因:
解决方案:
这类失效场景主要聚焦在框架本身在解析@Transactional时的内部支持。如果使用的场景本身就是框架不支持的,那事务也是无法生效的。
失效原因:
解决方案:
在 TransactionAspectSupport.prepareTransactionInfo 方法中
txInfo.bindToThread() 无论是否创建了新的事务,都会将TransactionInfo对象绑定到当前线程。这是为了确保事务信息栈的完整性,即使没有创建新事务。
某些NoSQL数据库或特定版本的关系型数据库,比如Mysql的Myisam存储引擎是不支持事务的。Spring
的声明式事务管理将无法正常工作,因为这些数据库缺乏必要的事务特性(如ACID属性)。
在MVC项目中还需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。
失效原因:
解决方案:
失效原因:
解决方案:
失效原因:
解决方案:
预期场景:
失效原因:
解决方案: