Spring事务管理的失效和Proxy类型的DataSource

Spring事务管理与Proxy DataSource问题解析
当Spring的TransactionManager使用Proxy类型的DataSource时,事务管理失效。原因是事务开始和进行中,ThreadLocal中存在两份不同的DataSource Proxy,导致事务无法正常进行。解决方案是通过将真实的DataSource实例提供给TransactionManager,或者通过封装的IDataSourceHandler服务暴露,避免直接暴露Proxy。

Spring事务管理的失效和Proxy类型的DataSource

 

       在服务框架中,我们由于需要将DataSource作为第三方服务暴露给其他模块(此处是十分不推荐的,因为如果作为服务那么首先就要求该服务没有状态),因此就采用JdkProxy来实现虚拟DataSource暴露给其他模块以及第三方。

 

环境:

       采用ASF(基于SCA服务框架的应用服务框架)暴露DataSource作为第三方服务,其他模块的Ibatis SqlMapClientSpringTransactionManager都注入了这个Proxy类型的DataSource

 

问题:

       发现Spring注入DataSourceProxy以后,使用是正常的,但是事务却失效了,JDBC事务失效。

 

分析:

       跟踪察看了spring的代码,发现原来spring中的TransactionManager在事务启动和结束的时候,都是首先将DataSourceConnectionHolderThreadLocal中保存,然后在事务过程中获取的都是保存过的Connection,出错也是会滚该Connection

       但是由于当前使用的是ProxyDataSource,虽然每一次Call的时候都是调用同一个DataSource,但是在事务开始的时候,获取的是注入到TransactionManager中的DataSource Proxy,在事务进行过程中,使用的是新的Proxy,在ThreadLocalMap中存在了两份DataSource Proxy的信息也就是有两份ConnectionHolder,这导致在事务开始的时候,将connectionautocommitfalse,然后放入到第一个DataSource Proxy作为KeyEntry中,无法被后面事务进行所获取,事务进行的时候默认将会通过DataSource产生一个新的Connection同时autocommit失效,因此产生了事务失效的问题。

 

Spring的事务处理流程图:

1 Spring 事务处理流程图

 

解决方法:

       其实只需要让Spring TransactionManager能够将DataSource真实实例获得并作为Entry Key保存到ThreadLocal中即可,那么就有两种方式,一种将就是将分开的配置放在一起,那么这里看TransactionManager应该是外部不需要配置的,可以和DataSource配置在一个spring中,那么就不需要用Proxy的方式,另一种不得不用Proxy的方式,例如对于IbatisSqlMapClient。处理方法如下:

 

2 DataSource 解决方法类图

 

       不再将DataSource作为服务暴露给第三方,而是将内部封装DataSourceIDataSourceHandler来作为服务暴露,同时调用方通过工厂类直接将IDataSourceHandler服务注入,就可以达到在调用方这边获取到的是DataSource的实例,但是需要注意的就是,由于ASF服务框架是动态随机加载各个组件,可能在服务被调用的时候尚未发布,因此需要在注入的时候设置为lazy-inittrue

 

结论:

       Proxy被运用约来广泛,但是也会造成一些再运用Proxy代理的实例作为框架业务逻辑的情况,必须弄清楚情况然后选择如何调用和组装,同时作为服务暴露给第三方,尽量选择无状态的接口服务。

 
### Spring 事务管理失效的常见场景及原因分析 Spring 框架通过声明式事务管理简化了事务处理,但在实际开发中,由于对事务机制理解不充分或使用不当,可能导致事务失效。以下是一些常见的事务失效场景及其原因解决方法。 --- #### 1. 自调用导致事务失效 当一个类中的非事务方法调用该类中带有 `@Transactional` 注解的事务方法时,事务不会生效。这是因为 Spring 的事务代理机制是基于 AOP 的,默认情况下,只有外部调用才会经过代理对象,而类内部的方法调用会绕过代理,导致事务失效。 **解决方法:** - 使用 `AopContext.currentProxy()` 获取当前代理对象,再调用事务方法。 - 通过 `ApplicationContext` 获取当前 Bean 的代理对象,再进行调用(推荐)。 ```java @Service public class OrderService { @Autowired private ApplicationContext context; public void placeOrder() { OrderService proxy = context.getBean(OrderService.class); proxy.createOrder(); // 通过代理调用事务方法 } @Transactional public void createOrder() { // 事务逻辑 } } ``` --- #### 2. 异常未正确抛出或未声明回滚异常类型 默认情况下,Spring 只会在遇到 `RuntimeException` 或其子类时触发事务回滚。如果方法中捕获了异常但未重新抛出,或者抛出的是受检异常(checked exception),事务将不会回滚。 **解决方法:** - 不要手动捕获异常,或在捕获后重新抛出运行时异常。 - 使用 `@Transaction` 注解时指定 `rollbackFor` 属性,明确哪些异常应触发回滚。 ```java @Transactional(rollbackFor = Exception.class) public void processPayment() throws Exception { try { // 业务逻辑 } catch (Exception e) { throw new RuntimeException(e); // 或直接抛出受检异常 } } ``` --- #### 3. 方法访问权限不是 public Spring 的事务代理仅对 `public` 方法生效。如果事务方法的访问权限为 `private`、`protected` 或包级私有,则事务不会生效。 **解决方法:** - 将事务方法的访问权限改为 `public`。 ```java @Transactional public void updateInventory() { // 正确:事务生效 } ``` --- #### 4. 事务传播行为配置错误 事务传播行为决定了事务方法在被调用时如何参与当前事务。如果传播行为配置不当,可能导致事务无法合并或新建事务失败。 **常见传播行为:** - `REQUIRED`:如果当前存在事务,则加入;否则新建一个事务(默认)。 - `REQUIRES_NEW`:总是新建事务,并挂起当前事务(如有)。 - `SUPPORTS`:支持当前事务,不存在则以非事务方式执行。 - `NOT_SUPPORTED`:以非事务方式执行,挂起当前事务(如有)。 **解决方法:** - 根据业务需求合理设置 `@Transactional(propagation = Propagation.XXX)`。 ```java @Transactional(propagation = Propagation.REQUIRES_NEW) public void logTransaction() { // 总是新建事务 } ``` --- #### 5. 未正确配置事务管理器或未启用事务注解 如果没有在配置文件中定义事务管理器(如 `PlatformTransactionManager`),或者未启用 `@Transactional` 注解支持,事务将完全失效。 **解决方法:** - 确保配置了事务管理器(如 `DataSourceTransactionManager`)。 - 在配置类或 XML 文件中启用事务注解。 ```java @Configuration @EnableTransactionManagement public class AppConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } } ``` --- #### 6. 方法未被 Spring 管理(未被注入为 Bean) 如果事务方法所在的类没有被 Spring 容器管理(即未使用 `@Service`、`@Component` 或 `@Repository` 注解),则事务不会生效。 **解决方法:** - 确保事务方法所在的类被 Spring 容器管理。 ```java @Service public class PaymentService { @Transactional public void chargeCustomer() { // 事务逻辑 } } ``` --- #### 7. 使用 final 方法或 final 类 如果事务方法被声明为 `final`,或者事务类本身是 `final` 类型Spring 将无法为其创建代理,从而导致事务失效。 **解决方法:** - 避免将事务方法或类声明为 `final`。 --- #### 8. 多线程环境下事务失效 在多线程环境中,事务通常绑定在当前线程上(通过 `ThreadLocal`)。如果在事务方法中启动新线程执行数据库操作,新线程将无法继承事务上下文。 **解决方法:** - 避免在事务方法中开启新线程执行事务性操作。 - 或者手动传递事务上下文(不推荐,实现复杂)。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值