Spring AOP拦截同注解嵌套方法调用:多数据源路由失效问题
问题原因分析
- Spring AOP代理机制限制:Spring的
@Transactional和自定义AOP切面都基于动态代理实现。当primary()方法内部直接调用replica()时,调用的是原始对象方法而非代理对象方法,因此DataSourceRouteInterceptor的切面逻辑不会触发,无法切换数据源。 - 事务上下文继承:
primary()标注的@Transactional(readOnly=false)会开启主库事务上下文,默认事务传播行为(REQUIRED)会让replica()加入当前主库事务,不会重新选择数据源。
解决方案
步骤1:开启代理对象暴露
在Spring配置类上添加注解,允许通过AopContext获取当前代理对象:
@Configuration @EnableAspectJAutoProxy(exposeProxy = true) public class AppConfig { // 其他配置项 }
步骤2:修改内部调用方式为代理对象调用
调整MyService的primary()方法,通过代理对象调用replica()以触发切面逻辑:
@Service public class MyService { @Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate; @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) public List<String> replica() { String sqlQuery = "SELECT QUERY"; return namedParameterJdbcTemplate.query(sqlQuery, (rs, rowNum) -> rs.getString(1)); } @Transactional(readOnly = false) public List<String> primary() { String sqlQuery = "INSERT QUERY"; namedParameterJdbcTemplate.update(sqlQuery, params); // 通过代理对象调用,触发切面与新事务 MyService proxyService = (MyService) AopContext.currentProxy(); return proxyService.replica(); } }
步骤3:调整事务传播行为
将replica()的事务传播行为改为REQUIRES_NEW,确保调用时开启独立的只读事务,强制切换到副本库数据源。若使用默认的REQUIRED,会继承主库事务上下文,无法实现数据源切换。
可选:优化路由线程安全性
确保RoutingDataSource的路由状态存储在ThreadLocal中,避免多线程下状态混乱:
public class RoutingDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> ROUTE_KEY = new ThreadLocal<>(); public static void setReplicaRoute() { ROUTE_KEY.set("replica"); } public static void clearReplicaRoute() { ROUTE_KEY.remove(); } @Override protected Object determineCurrentLookupKey() { return ROUTE_KEY.get(); } }
验证效果
修改完成后调用primary()方法时:
- 先执行主库插入操作,事务上下文绑定主库;
- 通过代理调用
replica()时,触发切面设置副本库路由,并开启独立只读事务; - 查询操作路由到副本库,执行完成后清理路由状态,回到主库事务上下文。
内容的提问来源于stack exchange,提问作者Harsh Kanakhara
相关产品推荐
相关产品推荐

