spring boot 上的主从配置,@Transactional(readOnly = true) 未按预期工作

发布于 2025-01-12 20:16:31 字数 5034 浏览 5 评论 0原文

所以我有一个主从数据源设置如下:

@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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

樱花落人离去 2025-01-19 20:16:31

简而言之,此问题的解决方案是将 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 to application.properties and in case Hikari is used also setting setAutoCommit(false).

More details can be found here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文