使用spring @Transactional和AbstractRoutingDataSource在只读数据库和读写数据库之间切换

问题描述

我需要使用带有Spring的@Transactional批注的只读和读写数据源。我有以下设置:

public class DataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getEnvironment();
    }
}

@Aspect
public class DataSourceSwitch {
    @Around("@annotation(transactional)")
    public Object proceed(ProceedingJoinPoint pjp,Transactional transactional) throws Throwable {
        DatabaseEnvironment newEnv = transactional.readOnly() ?
                DatabaseEnvironment.READ_ONLY : DatabaseEnvironment.READ_WRITE;
        DatabaseContextHolder.setEnvironment(newEnv);
        return pjp.proceed();
    }
}

@Configuration
@EnableTransactionManagement
@EnableAspectJAutoproxy
public class JpaConfig {
    @Bean
    @Primary
    public DataSource getDataSource() throws Exception {
        DataSourceRouter router = new DataSourceRouter();
        Map<Object,Object> dataSources = new HashMap<>();
        dataSources.put(DatabaseEnvironment.READ_ONLY,<readOnlyDb>);
        dataSources.put(DatabaseEnvironment.READ_WRITE,<readWriteDb>);
        router.setTargetDataSources(dataSources);
        return router;
    }
}

DatabaseContextHolder将应使用的DB保存到ThreadLocal。带有@Transactional批注的方法具有相应设置的readOnly标志。

我面临的问题是,路由器的方法determineCurrentLookupKey仅在调用一个@Transactional方法时被调用一次,而在随后的事务方法中不会再次被调用不同的数据源。因此,例如,如果调用的第一个方法需要只读数据源,而后来的另一个方法需要读写数据源,则数据库上下文确实会更改,但是determineCurrentLookupKey不会再被调用,因此它将尝试写入只读数据库,并且失败。每次调用事务方法时都不应该调用determineCurrentLookupKey方法吗?

方法之间不能相互调用,也不能从同一类调用。将propagation = Propagation.REQUIRES_NEW添加@Transactional批注也无济于事。使其运作的最佳方法是什么?

解决方法

解决方案是将其添加到application.yaml

spring:
  jpa:
    open-in-view: false