Skip to content

Instantly share code, notes, and snippets.

@yusufcakal
Last active June 20, 2020 10:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yusufcakal/d7c8b52f47389fd0763bc7b199277d52 to your computer and use it in GitHub Desktop.
Save yusufcakal/d7c8b52f47389fd0763bc7b199277d52 to your computer and use it in GitHub Desktop.
Using TransactionalReadOnly annotation to change data source in runtime for Spring
@Configuration
@EnableTransactionManagement
public class DatabaseConfiguration {
@Bean
@Primary
@Profile("!test")
@DependsOn({"primaryDataSource", "replicaDataSource"})
public DataSource dataSource(@Qualifier("primaryDataSource") HikariDataSource primaryDataSource,
@Qualifier("replicaDataSource") HikariDataSource replicaDataSource) {
final RoutingDataSource routingDataSource = new RoutingDataSource();
final Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(RoutingDataSource.Route.PRIMARY, primaryDataSource);
targetDataSources.put(RoutingDataSource.Route.REPLICA, replicaDataSource);
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(primaryDataSource);
return routingDataSource;
}
@Profile("!test")
@Bean(destroyMethod = "close")
public HikariDataSource primaryDataSource() {
HikariConfig hikariConfig = buildHikariConfig("primary-hikari-pool", dataSourceProperties.getUrl());
return new HikariDataSource(hikariConfig);
}
@Profile("!test")
@Bean(destroyMethod = "close")
public HikariDataSource replicaDataSource() {
HikariConfig hikariConfig = buildHikariConfig("replica-hikari-pool", dataSourceProperties.getReplicaUrl());
return new HikariDataSource(hikariConfig);
}
private HikariConfig buildHikariConfig(String poolName, String url) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setPoolName(poolName);
hikariConfig.setDataSourceClassName(dataSourceProperties.getDriverClassName());
hikariConfig.addDataSourceProperty("url", url);
hikariConfig.addDataSourceProperty("user", dataSourceProperties.getUsername());
hikariConfig.addDataSourceProperty("password", dataSourceProperties.getPassword());
hikariConfig.setMaximumPoolSize(dataSourceProperties.getMaxPoolSize());
hikariConfig.setMinimumIdle(dataSourceProperties.getMinIdle());
hikariConfig.setLeakDetectionThreshold(dataSourceProperties.getLeakThreshold());
hikariConfig.setConnectionTimeout(10000);
hikariConfig.setMaxLifetime(360000);
hikariConfig.setIdleTimeout(80000);
return hikariConfig;
}
}
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnProperty("read.replica.enabled")
public class ReadOnlyRouteInterceptor {
@Around("@annotation(TransactionalReadOnly)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
RoutingDataSource.setReplicaRoute();
return proceedingJoinPoint.proceed();
} finally {
RoutingDataSource.clearReplicaRoute();
}
}
}
public class RoutingDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<Route> ctx = new ThreadLocal<>();
public static void clearReplicaRoute() {
ctx.remove();
}
public static void setReplicaRoute() {
ctx.set(Route.REPLICA);
}
@Override
protected Object determineCurrentLookupKey() {
return ctx.get();
}
public enum Route {
PRIMARY, REPLICA
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public @interface TransactionalReadOnly {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment