Spring 在 select 期间批处理 + 休眠写入
Spring batch + hibernate write during select
前面的信息。我只是 select 编辑了我认为必要的代码片段,它们位于不同的文件中,所以不要怀疑它看起来是否有点混乱。
我正在 Spring批处理 Reader 作业中读取平面文件。
我编写了一个从 FieldSetMapper 调用的 ProductValueMapper,它将列映射到 Hibernate 模型。该映射器还检查产品是否已存在于数据库中,如果存在,则使用数据库中的实体,否则将创建一个新实体。
@Component
@StepScope
public class ProductValueMapper {
@Autowired
private IProductDao productDao;
@Autowired
private IFactory<Product> productFactory;
private Product fetch(String[] criteria) {
//... try to fetch product using different criteria, or create a new one using the factory ...
return product;
}
Product map(String[] criteria) {
Product product = fetch(criteria);
//... map some stuff ...
return product;
}
}
DAO 通过
自动装配实体管理器
@PersistenceContext
private EntityManager manager;
并标记为@Transactional
之后我有了一个处理器,它除了日志记录什么都不做。
然后我写入默认的 jpaItemWriter 是这样创建的:
@Configuration
@Import(DatabaseConfiguration.class)
public class HibernateConfiguration extends DefaultBatchConfigurer {
@Autowired
@Qualifier("oracleDataSource")
private DataSource dataSource;
@Bean(name = "jpaEntitiyManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceUnitName("hibernatePersistenceUnit");
em.setPackagesToScan("com.somepackage");
em.setDataSource(dataSource);
em.setJpaProperties(hibernateProperties());
HibernateJpaVendorAdapter vendor = new HibernateJpaVendorAdapter();
vendor.setGenerateDdl(false);
vendor.setShowSql(true);
em.setJpaVendorAdapter(vendor);
return em;
}
@Bean
public Properties hibernateProperties() {
Properties prop = new Properties();
prop.setProperty("hibernate.hbm2ddl.auto", "validate");
prop.setProperty("hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
prop.setProperty("hibernate.globally_quoted_identifiers", "false");
prop.setProperty("hibernate.show_sql", "true");
return prop;
}
@Override
public PlatformTransactionManager getTransactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
@Configuration
@EnableBatchProcessing(modular = true)
@ComponentScan({"com.somepackage"})
@Import({HibernateConfiguration.class, DatabaseConfiguration.class})
public class BatchConfiguration {
@Autowired
public EntityManagerFactory emf;
@Bean
public JpaItemWriter<ProductEntity> jpaItemWriter() {
JpaItemWriter<ProductEntity> itemWriter = new JpaItemWriter<>();
itemWriter.setEntityManagerFactory(emf);
return itemWriter;
}
//... rest of the setup for the job
}
程序按预期工作,除了块大小 > 1 和一个项目在批处理期间发生更改,我遇到了休眠在以下项目的 select 期间执行更新语句的问题。
我知道我可以通过在处理器中调用 flush 和 save 或将块大小减小到 1 来解决这个问题,但不知何故,这两种解决方案对我来说都是错误的。
不应该每个项目都有一个交易保持打开状态,然后在调用作者时应该一个一个地提交这些交易吗?还是我误解了Spring Batch.
中transactionHandling的原理
* 编辑 1 *
问题是,当将块大小设置为 1 时,程序的行为符合预期:更新发生在写入阶段。
2016-09-05 11:20:40.828 INFO 11084 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:20:40.828 INFO 11084 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Prduct1
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, .....
2016-09-05 11:20:40.832 INFO 11084 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somepackage.ProductEntity@8e654f7
2016-09-05 11:20:40.832 INFO 11084 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - beforeWrite
Hibernate: update PIME.PRODUCT set AVAILABILITYDATE=?, ....
2016-09-05 11:20:40.836 INFO 11084 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - afterWrite
2016-09-05 11:20:40.887 INFO 11084 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:20:40.887 INFO 11084 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product2
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:20:40.891 INFO 11084 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somepackage.ProductEntity@2c7fb24c
2016-09-05 11:20:40.891 INFO 11084 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - beforeWrite
2016-09-05 11:20:40.891 INFO 11084 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - afterWrite
但是当块大小增加时,写入以 select 语句的形式发生,因为写入不是在处理产品结束时发生,而是在块中发生:
2016-09-05 11:09:36.240 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:09:36.240 INFO 12408 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product1
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:09:36.244 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somemodule.ProductEntity@6f28a07e
2016-09-05 11:09:36.244 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:09:36.244 INFO 12408 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product2
Hibernate: update PIME.PRODUCT set AVAILABILITYDATE=?, ....
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:09:36.250 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somemodule.ProductEntity@71852f76
2016-09-05 11:09:36.250 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:09:36.250 INFO 12408 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product3
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:09:36.253 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somemodule.ProductEntity@76ac8c3d
2016-09-05 11:09:36.253 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:09:36.253 INFO 12408 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product4
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:09:36.256 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somemodule.ProductEntity@6a0d47e8
2016-09-05 11:09:36.256 INFO 12408 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - beforeWrite
2016-09-05 11:09:36.257 INFO 12408 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - afterWrite
我们需要使用 Entry 而不是 Entity。在这种情况下,对您来说最好的做法是
- 从 Reader 开始,您从数据库查询并将其存储为条目 (Pojo) 而不是实体。
- 从处理器,您处理(更改)条目
- 在 Writer 中,您通过条目中的 ID 更新数据库。 (或者你可以使用 Dozer 从 Entity 映射到 Pojo)
否则,Spring将执行如下操作:
- Reader,您获取 A 并将其存储为活动实体 A。
- Processor,你直接在A实体上改
- 另一个 reader,您获取 B,然后 Spring 将更新 A,因为他们直接在 A 实体上背叛了更改。
注意:如果您不希望它发生,您可以将@ReadOnly 与@Transactional
一起使用
谢谢,
义亚
前面的信息。我只是 select 编辑了我认为必要的代码片段,它们位于不同的文件中,所以不要怀疑它看起来是否有点混乱。
我正在 Spring批处理 Reader 作业中读取平面文件。 我编写了一个从 FieldSetMapper 调用的 ProductValueMapper,它将列映射到 Hibernate 模型。该映射器还检查产品是否已存在于数据库中,如果存在,则使用数据库中的实体,否则将创建一个新实体。
@Component
@StepScope
public class ProductValueMapper {
@Autowired
private IProductDao productDao;
@Autowired
private IFactory<Product> productFactory;
private Product fetch(String[] criteria) {
//... try to fetch product using different criteria, or create a new one using the factory ...
return product;
}
Product map(String[] criteria) {
Product product = fetch(criteria);
//... map some stuff ...
return product;
}
}
DAO 通过
自动装配实体管理器@PersistenceContext
private EntityManager manager;
并标记为@Transactional
之后我有了一个处理器,它除了日志记录什么都不做。
然后我写入默认的 jpaItemWriter 是这样创建的:
@Configuration
@Import(DatabaseConfiguration.class)
public class HibernateConfiguration extends DefaultBatchConfigurer {
@Autowired
@Qualifier("oracleDataSource")
private DataSource dataSource;
@Bean(name = "jpaEntitiyManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceUnitName("hibernatePersistenceUnit");
em.setPackagesToScan("com.somepackage");
em.setDataSource(dataSource);
em.setJpaProperties(hibernateProperties());
HibernateJpaVendorAdapter vendor = new HibernateJpaVendorAdapter();
vendor.setGenerateDdl(false);
vendor.setShowSql(true);
em.setJpaVendorAdapter(vendor);
return em;
}
@Bean
public Properties hibernateProperties() {
Properties prop = new Properties();
prop.setProperty("hibernate.hbm2ddl.auto", "validate");
prop.setProperty("hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
prop.setProperty("hibernate.globally_quoted_identifiers", "false");
prop.setProperty("hibernate.show_sql", "true");
return prop;
}
@Override
public PlatformTransactionManager getTransactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
@Configuration
@EnableBatchProcessing(modular = true)
@ComponentScan({"com.somepackage"})
@Import({HibernateConfiguration.class, DatabaseConfiguration.class})
public class BatchConfiguration {
@Autowired
public EntityManagerFactory emf;
@Bean
public JpaItemWriter<ProductEntity> jpaItemWriter() {
JpaItemWriter<ProductEntity> itemWriter = new JpaItemWriter<>();
itemWriter.setEntityManagerFactory(emf);
return itemWriter;
}
//... rest of the setup for the job
}
程序按预期工作,除了块大小 > 1 和一个项目在批处理期间发生更改,我遇到了休眠在以下项目的 select 期间执行更新语句的问题。
我知道我可以通过在处理器中调用 flush 和 save 或将块大小减小到 1 来解决这个问题,但不知何故,这两种解决方案对我来说都是错误的。 不应该每个项目都有一个交易保持打开状态,然后在调用作者时应该一个一个地提交这些交易吗?还是我误解了Spring Batch.
中transactionHandling的原理* 编辑 1 *
问题是,当将块大小设置为 1 时,程序的行为符合预期:更新发生在写入阶段。
2016-09-05 11:20:40.828 INFO 11084 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:20:40.828 INFO 11084 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Prduct1
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, .....
2016-09-05 11:20:40.832 INFO 11084 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somepackage.ProductEntity@8e654f7
2016-09-05 11:20:40.832 INFO 11084 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - beforeWrite
Hibernate: update PIME.PRODUCT set AVAILABILITYDATE=?, ....
2016-09-05 11:20:40.836 INFO 11084 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - afterWrite
2016-09-05 11:20:40.887 INFO 11084 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:20:40.887 INFO 11084 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product2
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:20:40.891 INFO 11084 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somepackage.ProductEntity@2c7fb24c
2016-09-05 11:20:40.891 INFO 11084 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - beforeWrite
2016-09-05 11:20:40.891 INFO 11084 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - afterWrite
但是当块大小增加时,写入以 select 语句的形式发生,因为写入不是在处理产品结束时发生,而是在块中发生:
2016-09-05 11:09:36.240 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:09:36.240 INFO 12408 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product1
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:09:36.244 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somemodule.ProductEntity@6f28a07e
2016-09-05 11:09:36.244 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:09:36.244 INFO 12408 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product2
Hibernate: update PIME.PRODUCT set AVAILABILITYDATE=?, ....
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:09:36.250 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somemodule.ProductEntity@71852f76
2016-09-05 11:09:36.250 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:09:36.250 INFO 12408 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product3
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:09:36.253 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somemodule.ProductEntity@76ac8c3d
2016-09-05 11:09:36.253 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - beforeRead
2016-09-05 11:09:36.253 INFO 12408 --- [ main] n.e.p.i.r.map.GenericProductMapper : Processing product: Product4
Hibernate: select productent0_.PRODUCTSN as PRODUCTSN1_25_, ....
2016-09-05 11:09:36.256 INFO 12408 --- [ main] n.e.p.i.logging.LogItemReadListener : ItemReadListener - afterRead: com.somemodule.ProductEntity@6a0d47e8
2016-09-05 11:09:36.256 INFO 12408 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - beforeWrite
2016-09-05 11:09:36.257 INFO 12408 --- [ main] n.e.p.i.logging.LogItemWriterListener : ItemWriteListener - afterWrite
我们需要使用 Entry 而不是 Entity。在这种情况下,对您来说最好的做法是
- 从 Reader 开始,您从数据库查询并将其存储为条目 (Pojo) 而不是实体。
- 从处理器,您处理(更改)条目
- 在 Writer 中,您通过条目中的 ID 更新数据库。 (或者你可以使用 Dozer 从 Entity 映射到 Pojo)
否则,Spring将执行如下操作:
- Reader,您获取 A 并将其存储为活动实体 A。
- Processor,你直接在A实体上改
- 另一个 reader,您获取 B,然后 Spring 将更新 A,因为他们直接在 A 实体上背叛了更改。
注意:如果您不希望它发生,您可以将@ReadOnly 与@Transactional
一起使用谢谢, 义亚