My Setup is the following:
I Have a Spring Boot 2.1.1 Application. For Connection Pooling Hikari is used.
I would like to write one service which routes the READs to a READER database and the WRITES to a Master database in one transaction/method.
So i want the following to get to work:
@Transactional
public test(Test test){
Optional<Res> result = myRepo.findByAttr(test.getTest()); //do that on reader database
validateIfResultExists(result);
myRepo.save(test);//do this on master database
}
For that i wrote a custom DbContextHolder:
public class DbContextHolder {
private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();
public static void setDbType(DbType dbType) {
if(dbType == null){
throw new NullPointerException();
}
contextHolder.set(dbType);
}
public static DbType getDbType() {
if(contextHolder.get() == null){
DbContextHolder.setDbType(DbType.MASTER);
}
return (DbType) contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
I also have a custom RoutingDataSource:
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
And a DataSource for configuring database with hikari:
@Configuration
public class DataSourceConfig {
private static final String PRIMARY_DATASOURCE_PREFIX = "spring.primary.datasource";
private static final String REPLICA_DATASOURCE_PREFIX = "spring.replica.datasource";
@Autowired
private Environment environment;
@Value("${spring.datasource.hikari.connectionTimeout}")
private Long CONNECTION_TIMEOUT;
@Value("${spring.datasource.hikari.idleTimeout}")
private Long IDLE_TIMEOUT;
@Value("${spring.datasource.hikari.maxLifetime}")
private Long MAX_LIFETIME_TIMEOUT;
@Value("${spring.datasource.hikari.minimumIdle}")
private int MINIMUM_IDLE;
@Value("${spring.datasource.hikari.maximumPoolSize}")
private int MAX_POOL_SIZE;
@Resource
@Bean
@Primary
public DataSource dataSource() {
final RoutingDataSource routingDataSource = new RoutingDataSource();
final DataSource primaryDataSource = buildDataSource("PrimaryHikariPool", PRIMARY_DATASOURCE_PREFIX);
final DataSource replicaDataSource = buildDataSource("ReplicaHikariPool", REPLICA_DATASOURCE_PREFIX);
final Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DbType.MASTER, primaryDataSource);
targetDataSources.put(DbType.REPLICA, replicaDataSource);
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(primaryDataSource);
return routingDataSource;
}
private DataSource buildDataSource(String poolName, String dataSourcePrefix) {
final HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setConnectionTimeout(CONNECTION_TIMEOUT);
hikariConfig.setIdleTimeout(IDLE_TIMEOUT);
hikariConfig.setMaxLifetime(MAX_LIFETIME_TIMEOUT);
hikariConfig.setMinimumIdle(MINIMUM_IDLE);
hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE);
hikariConfig.setPoolName(poolName);
hikariConfig.setJdbcUrl(environment.getProperty(String.format("%s.url", dataSourcePrefix)));
hikariConfig.setUsername(environment.getProperty(String.format("%s.username", dataSourcePrefix)));
hikariConfig.setPassword(environment.getProperty(String.format("%s.password", dataSourcePrefix)));
hikariConfig.setDriverClassName(environment.getProperty(String.format("%s.driver", dataSourcePrefix)));
return new HikariDataSource(hikariConfig);
}
}
But that doesn't seem to work. If i log the current DbType everythint looks gread, but it doesn't work since i get the following error:
ERROR: cannot execute INSERT in a read-only transaction
For making the transaction distributed, you can add the aspect around your transaction (@Around("@annotation(transactional)"))
and by checking the type of transaction, you can apply which replica has to be called.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.