使用RepositoryItemWriter搭配AbstractRoutingDataSource时实体未持久化问题
核心问题根因
- 单个Spring Batch Chunk的所有操作(读、处理、写)默认包裹在同一个事务中,
JpaTransactionManager在事务启动阶段就会从数据源获取连接并绑定到当前线程事务上下文,事务存续期间不会再重新获取连接,你在beforeRead/afterRead中修改动态路由的上下文标识,不会改变已经绑定的连接,数据源切换完全不生效。 - 你读写共用同一个
dataTypeRepository,对应的EntityManager已和事务绑定的连接关联,最终读、写操作实际都走了事务初始化时拿到的默认数据源(你配置的targetDataSource),相当于把目标库的数据读出来再原样写回目标库,所以Job执行计数正常,但源库数据没有同步到目标库。
可行解决方案
方案1:拆分独立持久化单元(推荐)
放弃动态路由数据源的实现,分别为源库、目标库配置独立的JPA持久化单元,完全隔离读写的数据源上下文:
- 保留你定义的
sourceDataSource、targetDataSource两个独立数据源,删除路由数据源配置 - 配置两套独立的JPA上下文:
// 源库JPA配置 @Configuration @EnableJpaRepositories( basePackages = "com.xxx.repository.source", entityManagerFactoryRef = "sourceEntityManagerFactory", transactionManagerRef = "sourceTransactionManager" ) public class SourceJpaConfig { @Bean public LocalContainerEntityManagerFactoryBean sourceEntityManagerFactory(DataSource sourceDataSource) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(sourceDataSource); em.setPackagesToScan("com.xxx.entity"); HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); return em; } @Bean public PlatformTransactionManager sourceTransactionManager(EntityManagerFactory sourceEntityManagerFactory) { return new JpaTransactionManager(sourceEntityManagerFactory); } } // 目标库JPA配置 @Configuration @EnableJpaRepositories( basePackages = "com.xxx.repository.target", entityManagerFactoryRef = "targetEntityManagerFactory", transactionManagerRef = "targetTransactionManager" ) public class TargetJpaConfig { @Bean public LocalContainerEntityManagerFactoryBean targetEntityManagerFactory(DataSource targetDataSource) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(targetDataSource); em.setPackagesToScan("com.xxx.entity"); HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); return em; } @Bean public PlatformTransactionManager targetTransactionManager(EntityManagerFactory targetEntityManagerFactory) { return new JpaTransactionManager(targetEntityManagerFactory); } }
- 分别在对应包下创建源库、目标库的
DataTypeRepository,Reader注入源库的Repository,Writer注入目标库的Repository - Step配置时指定
targetTransactionManager作为事务管理器,源库的读操作不会参与目标库的事务,读写完全隔离
方案2:使用JDBC读写实现
如果不需要JPA的实体管理能力,直接用JDBC实现读写,显式指定数据源:
// 源库读,直接指定sourceDataSource @Bean @StepScope public JdbcCursorItemReader<DataType> dataTypeReader(DataSource sourceDataSource) { return new JdbcCursorItemReaderBuilder<DataType>() .name("dataTypeReader") .dataSource(sourceDataSource) .sql("select id, xxx from data_type") .rowMapper(new BeanPropertyRowMapper<>(DataType.class)) .build(); } // 目标库写,直接指定targetDataSource @Bean @StepScope public JdbcBatchItemWriter<DataType> dataTypeWriter(DataSource targetDataSource) { return new JdbcBatchItemWriterBuilder<DataType>() .dataSource(targetDataSource) .sql("insert into data_type (id, xxx) values (:id, :xxx)") .beanMapped() .build(); }
注意事项
- 如果需要保证跨库读写的事务一致性,需要引入分布式事务管理器(如Atomikos)实现XA事务
- 如果源库数据量大,建议给Reader的查询语句添加排序条件,避免分页读丢数
内容的提问来源于stack exchange,提问作者Michal
相关产品推荐
相关产品推荐

