spring boot 上的主从配置,@Transactional(readOnly = true) 未按预期工作
所以我有一个主从数据源设置如下:
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.myservice.notificationservice.repositories.happyoffer",
entityManagerFactoryRef = "happyofferEntityManagerFactory",
transactionManagerRef= "happyofferTransactionManager"
)
public class HappyofferDataSourceConfig {
@Value("${spring.entity.scan.packages}")
private String packageToScan;
@Bean
@Primary
@ConfigurationProperties("spring.happyoffer.datasource.master")
public DataSourceProperties happyOfferMasterDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("spring.happyoffer.datasource.master.configuration")
public DataSource masterDataSource() {
return happyOfferMasterDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("spring.happyoffer.datasource.slave")
public DataSourceProperties happyOfferSlaveDataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties("spring.happyoffer.datasource.slave.configuration")
public DataSource slaveDataSource() {
return happyOfferSlaveDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@Primary
public DataSource routingDataSource() {
Map<Object, Object> targetDataSources = new LinkedHashMap<>();
RoutingDataSourceConfiguration routingDataSourceConfiguration = new RoutingDataSourceConfiguration();
DataSource master = this.masterDataSource();
targetDataSources.put(DataSourceTypes.MASTER, master);
DataSource slave = this.slaveDataSource();
targetDataSources.put(DataSourceTypes.SLAVE, slave);
routingDataSourceConfiguration.setTargetDataSources(targetDataSources);
routingDataSourceConfiguration.setDefaultTargetDataSource(master);
return routingDataSourceConfiguration;
}
@Bean
public DataSource dataSource() {
return new LazyConnectionDataSourceProxy(routingDataSource());
}
@Bean(name = "happyofferEntityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean happyofferEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(routingDataSource())
.packages(new String[]{packageToScan})
.build();
}
@Bean
@Primary
public PlatformTransactionManager happyofferTransactionManager(final @Qualifier("happyofferEntityManagerFactory") LocalContainerEntityManagerFactoryBean happyofferEntityManagerFactory) {
return new JpaTransactionManager(happyofferEntityManagerFactory.getObject());
}
@Primary
@Bean(name = "readerJdbcTemplate")
public NamedParameterJdbcTemplate getReaderJdbcTemplate() {
return new NamedParameterJdbcTemplate(slaveDataSource());
}
@Bean(name = "writerJdbcTemplate")
public NamedParameterJdbcTemplate getWriterJdbcTemplate() {
return new NamedParameterJdbcTemplate(masterDataSource());
}
}
路由配置定义为:
public class RoutingDataSourceConfiguration extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if(isReadOnly) {
return DataSourceTypes.SLAVE;
} else {
return DataSourceTypes.MASTER;
}
}
}
我还有一个存储库类定义为:
@Repository
@Transactional(readOnly = true)
public interface DeviceInfoRepository extends JpaRepository<DeviceInfo,Integer> {
List<DeviceInfo> findAllByUserIdIn(List<Integer> userIds);
}
我只是在这样的服务中调用上述函数:
@Service
@Slf4j
public class NotificationShooterServiceImpl implements NotificationShooterService {
@Autowired
DeviceInfoRepository deviceInfoRepository;
@Override
public NotificationShooterResponse shoot(List<Integer> userIds) throws Exception {
List<DeviceInfo> deviceInfoList = deviceInfoRepository.findAllByUserIdIn(userIds);
log.info("Size : " + deviceInfoList.size());
..........
..........
..........
NotificationShooterResponse notificationShooterResponse = new NotificationShooterResponse();
notificationShooterResponse.setCountOfUniqueUserIds(deviceInfoList.size());
return notificationShooterResponse;
}
现在,正如我添加的那样>@Transactional(readOnly = true),我希望查询将被路由到SLAVE db。然而,我每次都看到它会成为大师。 我对此进行了调试,发现此事务的 readOnly 属性未设置为 true,即 在上面显示的文件 RoutingDataSourceConfiguration
中, boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
读取 false
。
So I have a master-slave data source setup as follows :
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.myservice.notificationservice.repositories.happyoffer",
entityManagerFactoryRef = "happyofferEntityManagerFactory",
transactionManagerRef= "happyofferTransactionManager"
)
public class HappyofferDataSourceConfig {
@Value("${spring.entity.scan.packages}")
private String packageToScan;
@Bean
@Primary
@ConfigurationProperties("spring.happyoffer.datasource.master")
public DataSourceProperties happyOfferMasterDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("spring.happyoffer.datasource.master.configuration")
public DataSource masterDataSource() {
return happyOfferMasterDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("spring.happyoffer.datasource.slave")
public DataSourceProperties happyOfferSlaveDataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties("spring.happyoffer.datasource.slave.configuration")
public DataSource slaveDataSource() {
return happyOfferSlaveDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
@Bean
@Primary
public DataSource routingDataSource() {
Map<Object, Object> targetDataSources = new LinkedHashMap<>();
RoutingDataSourceConfiguration routingDataSourceConfiguration = new RoutingDataSourceConfiguration();
DataSource master = this.masterDataSource();
targetDataSources.put(DataSourceTypes.MASTER, master);
DataSource slave = this.slaveDataSource();
targetDataSources.put(DataSourceTypes.SLAVE, slave);
routingDataSourceConfiguration.setTargetDataSources(targetDataSources);
routingDataSourceConfiguration.setDefaultTargetDataSource(master);
return routingDataSourceConfiguration;
}
@Bean
public DataSource dataSource() {
return new LazyConnectionDataSourceProxy(routingDataSource());
}
@Bean(name = "happyofferEntityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean happyofferEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(routingDataSource())
.packages(new String[]{packageToScan})
.build();
}
@Bean
@Primary
public PlatformTransactionManager happyofferTransactionManager(final @Qualifier("happyofferEntityManagerFactory") LocalContainerEntityManagerFactoryBean happyofferEntityManagerFactory) {
return new JpaTransactionManager(happyofferEntityManagerFactory.getObject());
}
@Primary
@Bean(name = "readerJdbcTemplate")
public NamedParameterJdbcTemplate getReaderJdbcTemplate() {
return new NamedParameterJdbcTemplate(slaveDataSource());
}
@Bean(name = "writerJdbcTemplate")
public NamedParameterJdbcTemplate getWriterJdbcTemplate() {
return new NamedParameterJdbcTemplate(masterDataSource());
}
}
Routing configuration is defined as :
public class RoutingDataSourceConfiguration extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if(isReadOnly) {
return DataSourceTypes.SLAVE;
} else {
return DataSourceTypes.MASTER;
}
}
}
I also have a repository class defined as :
@Repository
@Transactional(readOnly = true)
public interface DeviceInfoRepository extends JpaRepository<DeviceInfo,Integer> {
List<DeviceInfo> findAllByUserIdIn(List<Integer> userIds);
}
I'm simply calling the above function in a service like this :
@Service
@Slf4j
public class NotificationShooterServiceImpl implements NotificationShooterService {
@Autowired
DeviceInfoRepository deviceInfoRepository;
@Override
public NotificationShooterResponse shoot(List<Integer> userIds) throws Exception {
List<DeviceInfo> deviceInfoList = deviceInfoRepository.findAllByUserIdIn(userIds);
log.info("Size : " + deviceInfoList.size());
..........
..........
..........
NotificationShooterResponse notificationShooterResponse = new NotificationShooterResponse();
notificationShooterResponse.setCountOfUniqueUserIds(deviceInfoList.size());
return notificationShooterResponse;
}
Now, as I have added @Transactional(readOnly = true)
, I expect that the query will get routed to SLAVE db. However, I'm seeing it going to MASTER each time.
I debugged this and found that the readOnly attribute is not set to true for this transaction i.e.
in the file RoutingDataSourceConfiguration
shown above,boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
reads false
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
简而言之,此问题的解决方案是将
hibernate.connection.provider_disables_autocommit=true
添加到application.properties
中,如果使用 Hikari,还设置setAutoCommit(false)< /代码>。
更多详细信息可以在此处找到
In short the solution for this problem is adding
hibernate.connection.provider_disables_autocommit=true
toapplication.properties
and in case Hikari is used also settingsetAutoCommit(false)
.More details can be found here