Appearance
声明式事务管理
声明式事务管理是Spring框架提供的一种非侵入式的事务管理方式,它允许开发者通过注解或XML配置来定义事务边界和属性,而不需要在代码中显式编写事务管理逻辑。
声明式事务的优势
相比于编程式事务管理,声明式事务具有以下优势:
- 代码简洁: 无需编写重复的事务管理代码
- 关注点分离: 业务逻辑与事务管理分离
- 配置灵活: 可以在不修改代码的情况下调整事务属性
- 一致性: 为应用提供统一的事务管理方式
配置声明式事务
Spring提供了两种配置声明式事务的方式:
- 基于注解的配置: 使用
@Transactional
注解 - 基于XML的配置: 使用AOP配置事务通知和切点
基于注解的声明式事务
1. 启用事务注解
使用@EnableTransactionManagement
注解启用事务注解支持:
java
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public DataSource dataSource() {
// 配置数据源
return new HikariDataSource();
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
或者在XML配置中启用:
xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<!-- 数据源配置 -->
</bean>
</beans>
2. 使用@Transactional注解
@Transactional
注解可以应用在类级别或方法级别,方法级别的配置会覆盖类级别的配置。
java
@Service
@Transactional // 类级别注解,应用于所有公共方法
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 继承类级别的事务配置
@Override
public User createUser(User user) {
return userRepository.save(user);
}
// 覆盖类级别的事务配置
@Override
@Transactional(readOnly = true)
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
// 自定义事务配置
@Override
@Transactional(timeout = 60, rollbackFor = {DataAccessException.class, ServiceException.class})
public void updateUserStatus(Long userId, UserStatus status) {
User user = findById(userId);
user.setStatus(status);
userRepository.save(user);
}
// 禁用事务
@Override
@Transactional(propagation = Propagation.NEVER)
public List<User> getAllUserNames() {
return userRepository.findAllUserNames();
}
}
3. @Transactional注解的属性
@Transactional
注解提供了多种事务属性的配置:
java
@Transactional(
transactionManager = "transactionManager", // 事务管理器
propagation = Propagation.REQUIRED, // 事务传播行为
isolation = Isolation.DEFAULT, // 事务隔离级别
timeout = 30, // 事务超时时间(秒)
readOnly = false, // 是否只读事务
rollbackFor = {Exception.class}, // 触发回滚的异常类
noRollbackFor = {UserNotFoundException.class} // 不触发回滚的异常类
)
基于XML的声明式事务
对于某些不便使用注解的场景,Spring也支持通过XML配置声明式事务:
xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 配置事务属性 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- AOP配置 -->
<aop:config>
<aop:pointcut id="serviceOperation" expression="execution(* com.example.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
</aop:config>
</beans>
事务属性详解
1. 事务传播行为
事务传播行为定义了当前方法被另一个事务方法调用时,应该如何处理事务:
传播行为 | 描述 |
---|---|
REQUIRED (默认) | 如果存在事务,则加入;否则创建新事务 |
SUPPORTS | 如果存在事务,则加入;否则以非事务方式执行 |
MANDATORY | 如果存在事务,则加入;否则抛出异常 |
REQUIRES_NEW | 创建新事务,如果存在事务,则挂起当前事务 |
NOT_SUPPORTED | 以非事务方式执行,如果存在事务,则挂起当前事务 |
NEVER | 以非事务方式执行,如果存在事务,则抛出异常 |
NESTED | 如果存在事务,则创建嵌套事务;否则创建新事务 |
2. 事务隔离级别
隔离级别定义了一个事务的结果对其他并发事务的可见性:
隔离级别 | 描述 |
---|---|
DEFAULT | 使用底层数据库的默认隔离级别 |
READ_UNCOMMITTED | 允许读取未提交的数据变更(脏读) |
READ_COMMITTED | 防止脏读,允许不可重复读和幻读 |
REPEATABLE_READ | 防止脏读和不可重复读,允许幻读 |
SERIALIZABLE | 防止脏读、不可重复读和幻读,但可能导致性能下降 |
3. 事务超时时间
定义了事务在强制回滚前可以运行的最大时间(秒):
java
@Transactional(timeout = 30) // 30秒超时
4. 只读事务
标记事务为只读可以让数据库优化性能:
java
@Transactional(readOnly = true)
5. 回滚规则
定义哪些异常会导致事务回滚,哪些不会:
java
@Transactional(
rollbackFor = {CustomException.class, SQLException.class},
noRollbackFor = {InvalidInputException.class}
)
声明式事务的原理
Spring声明式事务基于AOP实现,主要过程如下:
- Spring识别带有
@Transactional
注解的类和方法 - 在运行时,Spring创建这些类的代理对象
- 当代理对象的方法被调用时:
- 开始事务
- 调用目标方法
- 如果方法正常完成,则提交事务
- 如果方法抛出异常,则根据配置决定是否回滚事务
常见问题与解决方案
1. 事务不生效的原因
- 非public方法:
@Transactional
只对public方法有效 - 自调用问题: 在同一个类中调用带有
@Transactional
的方法不会触发事务 - 异常被捕获: 如果方法内部捕获了异常但没有重新抛出,事务不会回滚
- 不正确的异常类型: 默认情况下,只有RuntimeException和Error会触发回滚
2. 自调用问题解决方案
java
@Service
public class UserService {
@Autowired
private ApplicationContext context;
public void methodA() {
// 获取代理对象,避免自调用问题
UserService proxy = context.getBean(UserService.class);
proxy.methodB();
}
@Transactional
public void methodB() {
// 事务方法实现
}
}
3. 确保异常正确传播
java
@Transactional
public void updateUser(User user) {
try {
userRepository.save(user);
emailService.sendNotification(user);
} catch (EmailServiceException e) {
// 转换为运行时异常,确保事务回滚
throw new ServiceException("Failed to send notification", e);
}
}
最佳实践
- 在服务层使用事务: 将事务控制在服务层,而不是数据访问层或控制器层
- 合理设置传播行为: 根据业务需求选择合适的传播行为
- 精确配置回滚规则: 明确定义哪些异常应该导致回滚
- 注意事务边界: 避免在一个事务中执行过多操作,特别是耗时的操作
- 避免在事务方法中调用远程服务: 这可能导致长时间运行的事务
- 不要过度使用REQUIRES_NEW: 它会创建新的事务,可能导致性能问题
- 合理使用只读事务: 对于查询操作,使用只读事务可以提高性能
- 测试事务行为: 确保在各种异常情况下,事务行为符合预期
总结
声明式事务管理是Spring框架的强大特性,它大大简化了事务管理的复杂性,使开发者可以专注于业务逻辑而不是事务控制细节。通过合理配置@Transactional
注解或XML配置,可以灵活控制事务的行为,满足各种业务场景的需求。
在实际应用中,声明式事务是处理大多数事务需求的首选方式,只有在需要非常精细的事务控制时,才考虑使用编程式事务管理。