Spring Boot JPA 事务中动态切换数据源

2019-07-18  本文已影响0人  马周易

网上通过 RoutingDataSource + ThreadLocal + AOP 实现动态切换数据源的文章很多,但是一旦加上@Transactional就无法切换了。原因是事务提交时才会调用AbstractRoutingDataSource的determineCurrentLookupKey方法, 获取当前数据源。而在事务中就算切换多次数据源,只会使用事务提交时的当前数据源。因此,要在事务中切换数据源,必须使用@Transactional(propagation = Propagation.REQUIRES_NEW),开启新的事务。关键代码如下:

public class RoutingDataSourceContext implements AutoCloseable {

    private static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();

    public static String getDataSourceRoutingKey() {
        return threadLocalDataSourceKey.get();
    }

    public RoutingDataSourceContext(String key) {
        threadLocalDataSourceKey.set(key);
    }

    @Override
    public void close() throws Exception {
        threadLocalDataSourceKey.remove();
    }

}
@Slf4j
public class RoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String key = RoutingDataSourceContext.getDataSourceRoutingKey();
        log.debug("查询当前数据源名称为{}", key);
        return key;
    }

}
@Configuration
@ConfigurationProperties("ba.datasource")
public class DataSourceConfig {

    @Getter
    @Setter
    private DataSourceProperties master;

    @Getter
    @Setter
    private Map<String, DataSourceProperties> slaves = new HashMap<>();

    @Bean
    DataSource dataSource() {
        RoutingDataSource dataSource = new RoutingDataSource();

        dataSource.setDefaultTargetDataSource(master.initializeDataSourceBuilder().build());

        Map<Object, Object> targetDataSources = new HashMap<>();
        slaves.forEach((key, slave) -> {
            targetDataSources.put(key, slave.initializeDataSourceBuilder().build());
        });
        dataSource.setTargetDataSources(targetDataSources);

        return dataSource;
    }

}
ba:
  datasource:
    master:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://localhost:5432/master
      username: postgres
      password: 123456
    slaves:
      slave1:
        driver-class-name: org.postgresql.Driver
        url: jdbc:postgresql://localhost:5432/slave1
        username: postgres
        password: 123456
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RoutingWith {

    String value();

}
@Slf4j
@Aspect
@Component
@Order(-1)
public class RoutingAspect {

    @Around("@annotation(routingWith)")
    public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWith routingWith) throws Throwable {
        String key = routingWith.value();
        log.debug("切换数据源为{}", key);
        try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {
            return joinPoint.proceed();
        }
    }

}
@Component
public class UserManager {

    @Autowired
    private UserDAO userDAO;

    @Autowired(required = false)
    private UserManager userManager;

    @Transactional
    public void addUser() {
        userManager.test1();

        userManager.test2();
    }

    public void test1() {
        UserDVO user = new UserDVO();
        user.setUsername("Jack Ma");
        user.setPassword("123456");
        userDAO.save(user);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @RoutingWith("slave1")
    public void test2() {
        UserDVO user = new UserDVO();
        user.setUsername("Zhouyi Ma");
        user.setPassword("654321");
        userDAO.save(user);
    }

}
上一篇 下一篇

猜你喜欢

热点阅读