如何在@Transactional SpringBootTest 测试用例中测试 spring 批处理作业?
How to test spring batch job within @Transactional SpringBootTest test case?
我今天好像赢不了...
- 有没有一种方法可以从 Spock SpringBootTest 集成测试中的
OneToMany
关系中读取数据,而无需将测试注释为 @Transactional
或添加不切实际的 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
?
- 或者,有没有办法从
@Transactional
测试用例中启动 Spring-批处理作业?
让我详细说明...
我正在尝试为我的 Spring 批处理报告流程获取一个简单的 Spring 启动集成测试,它从 DB2 tables 的混乱网络中读取并生成一系列感兴趣的系统的更改消息。我正在使用 Groovy Spock 测试框架和一个 H2 内存数据库,其中填充了我的 DB2 tables 数据的代表性片段。
在测试开始时,我尝试使用给定 Table 中的每个实体在驱动我的消息传递的更改跟踪 table 中生成条目。
setup:
List allExistingTestPeople = peopleRepository.findAll()
Collections.shuffle(allExistingTestPeople)
allExistingTestPeople?.each { Person person ->
Nickname nicknames = person.nicknames
nicknames?.each { Nickname nickname ->
changeTrackingRepository.save(new Change(personId: person.id, nicknameId: nickname.id, status: NEW))
}
}
将这些作为我的 DB2 域 类:
@Entity
@Table(name = "T_PERSON")
public class Person {
@Id
@Column(name = "P_ID")
private Integer id;
@Column(name = "P_NME")
private String name;
@OneToMany(targetEntity = Nickname.class, mappedBy = "person")
private List<Nickname> nicknames;
}
@Entity
@Table(name = "T_NICKNAME")
public class Nickname{
@EmbeddedId
private PersonNicknamePK id;
@Column(name = "N_NME")
private String nickname;
@ManyToOne(optional = false, targetEntity = Person.class)
@JoinColumn(name = "P_ID", referencedColumnName="P_ID", insertable = false, updatable = false)
private Person person;
}
@Embeddable
public class PersonNicknamePK implements Serializable {
@Column(name="P_ID")
private int personId;
@Column(name="N_ID")
private short nicknameId;
}
但是我收到了这个 LazyInitializationException,即使我正在从测试用例上下文中的 OneToMany
关系中读取...
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.my.package.db2.model.Person.nicknames, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:161)
at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:350)
我在网上看到了用 @Transactional
注释来注释我的测试用例的建议,这肯定让我更进一步,让我可以从这个 OneToMany
关系中读取。但是,当我随后尝试启动 Spring 批处理作业时,我想从我的 when
子句中进行测试:
@Transactional
def "Happy path test to validate I can generate a report of changes"() {
setup:
//... See above
when:
service.launchBatchJob()
then:
//... Messages are generated
}
我遇到一个异常,Spring 批处理作业无法从事务上下文中启动!即使我通过 ResourcelessTransactionManager
和 MapJobRepositoryFactoryBean
使用内存中的作业管理器,因为这只是我正在编写的一个短暂的预定脚本...
java.lang.IllegalStateException: Existing transaction detected in JobRepository. Please fix this and try again (e.g. remove @Transactional annotations from client).
at org.springframework.batch.core.repository.support.AbstractJobRepositoryFactoryBean.invoke(AbstractJobRepositoryFactoryBean.java:177)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy125.createJobExecution(Unknown Source)
at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:134)
at com.my.package.service.MyService.launchBatchJob(MyService.java:30)
到目前为止,唯一可行的方法是我放弃 @Transactional
注释,而是将 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
添加到我的 application-test.properties
文件中。但是,这似乎不是一个好主意,因为它不现实——如果我添加这个,那么即使我的代码中存在由于延迟初始化异常而导致的错误,我也永远不会在测试中看到它.
对不起小说,希望有人能给我指明正确的方向:(
编辑:
这也是我的内存中 Spring-批处理配置,我在其中尝试关闭交易验证。不幸的是,虽然这让我更进一步,但 Spring Batch partioner 的自动装配 EntityManager 突然无法 运行 在 H2 数据库中查询。
@Configuration
@EnableBatchProcessing
public class InMemoryBatchManagementConfig {
@Bean
public ResourcelessTransactionManager resourceslessTransactionManager() {
ResourcelessTransactionManager resourcelessTransactionManager = new ResourcelessTransactionManager();
resourcelessTransactionManager.setNestedTransactionAllowed(true);
resourcelessTransactionManager.setValidateExistingTransaction(false);
return resourcelessTransactionManager;
}
@Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactory(ResourcelessTransactionManager txManager)
throws Exception {
MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean(txManager);
factory.setValidateTransactionState(false);
factory.afterPropertiesSet();
return factory;
}
@Bean
public JobRepository jobRepository(MapJobRepositoryFactoryBean factory) throws Exception {
return factory.getObject();
}
@Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) throws Exception {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(jobRepository);
launcher.afterPropertiesSet();
return launcher;
}
@Bean
public JobExplorer jobExplorer(MapJobRepositoryFactoryBean factory) {
return new SimpleJobExplorer(factory.getJobInstanceDao(), factory.getJobExecutionDao(),
factory.getStepExecutionDao(), factory.getExecutionContextDao());
}
@Bean
public BatchConfigurer batchConfigurer(MapJobRepositoryFactoryBean mapJobRepositoryFactory,
ResourcelessTransactionManager resourceslessTransactionManager,
SimpleJobLauncher jobLauncher,
JobExplorer jobExplorer) {
return new BatchConfigurer() {
@Override
public JobRepository getJobRepository() throws Exception {
return mapJobRepositoryFactory.getObject();
}
@Override
public PlatformTransactionManager getTransactionManager() throws Exception {
return resourceslessTransactionManager;
}
@Override
public JobLauncher getJobLauncher() throws Exception {
return jobLauncher;
}
@Override
public JobExplorer getJobExplorer() throws Exception {
return jobExplorer;
}
};
}
}
发生此错误是因为您的代码已经在 Spring Batch 驱动的事务中执行。所以运行事务范围内的作业不正确。但是,如果您仍想禁用作业存储库完成的事务验证,可以将 validateTransactionState
设置为 false,请参阅 AbstractJobRepositoryFactoryBean#setValidateTransactionState。
也就是说,运行 事务中的作业不是修复 org.hibernate.LazyInitializationException
的方法。 属性 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
的存在是有原因的,如果它对你有用,我相信这是比 运行 交易中整个工作更好的方法(顺便说一句,如果我有为此使用事务,我会将其范围缩小到最小(例如步骤)而不是整个作业。
您可以使用 TransactionTemplate
到 运行 以编程方式进行交易,只有交易中的 "setup"(而不是 @Transactional
中的所有内容)。不幸的是,这种方式将提交事务,您将需要进行一些手动清理。
它可以像任何其他 bean 一样自动装配:
@Autowired
private TransactionTemplate transactionTemplate;
...它是这样使用的:
transactionTemplate.execute((transactionStatus) -> {
// ...setup...
return null; // alternatively you can return some data out of the callback
});
我今天好像赢不了...
- 有没有一种方法可以从 Spock SpringBootTest 集成测试中的
OneToMany
关系中读取数据,而无需将测试注释为@Transactional
或添加不切实际的spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
? - 或者,有没有办法从
@Transactional
测试用例中启动 Spring-批处理作业?
让我详细说明...
我正在尝试为我的 Spring 批处理报告流程获取一个简单的 Spring 启动集成测试,它从 DB2 tables 的混乱网络中读取并生成一系列感兴趣的系统的更改消息。我正在使用 Groovy Spock 测试框架和一个 H2 内存数据库,其中填充了我的 DB2 tables 数据的代表性片段。
在测试开始时,我尝试使用给定 Table 中的每个实体在驱动我的消息传递的更改跟踪 table 中生成条目。
setup:
List allExistingTestPeople = peopleRepository.findAll()
Collections.shuffle(allExistingTestPeople)
allExistingTestPeople?.each { Person person ->
Nickname nicknames = person.nicknames
nicknames?.each { Nickname nickname ->
changeTrackingRepository.save(new Change(personId: person.id, nicknameId: nickname.id, status: NEW))
}
}
将这些作为我的 DB2 域 类:
@Entity
@Table(name = "T_PERSON")
public class Person {
@Id
@Column(name = "P_ID")
private Integer id;
@Column(name = "P_NME")
private String name;
@OneToMany(targetEntity = Nickname.class, mappedBy = "person")
private List<Nickname> nicknames;
}
@Entity
@Table(name = "T_NICKNAME")
public class Nickname{
@EmbeddedId
private PersonNicknamePK id;
@Column(name = "N_NME")
private String nickname;
@ManyToOne(optional = false, targetEntity = Person.class)
@JoinColumn(name = "P_ID", referencedColumnName="P_ID", insertable = false, updatable = false)
private Person person;
}
@Embeddable
public class PersonNicknamePK implements Serializable {
@Column(name="P_ID")
private int personId;
@Column(name="N_ID")
private short nicknameId;
}
但是我收到了这个 LazyInitializationException,即使我正在从测试用例上下文中的 OneToMany
关系中读取...
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.my.package.db2.model.Person.nicknames, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:161)
at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:350)
我在网上看到了用 @Transactional
注释来注释我的测试用例的建议,这肯定让我更进一步,让我可以从这个 OneToMany
关系中读取。但是,当我随后尝试启动 Spring 批处理作业时,我想从我的 when
子句中进行测试:
@Transactional
def "Happy path test to validate I can generate a report of changes"() {
setup:
//... See above
when:
service.launchBatchJob()
then:
//... Messages are generated
}
我遇到一个异常,Spring 批处理作业无法从事务上下文中启动!即使我通过 ResourcelessTransactionManager
和 MapJobRepositoryFactoryBean
使用内存中的作业管理器,因为这只是我正在编写的一个短暂的预定脚本...
java.lang.IllegalStateException: Existing transaction detected in JobRepository. Please fix this and try again (e.g. remove @Transactional annotations from client).
at org.springframework.batch.core.repository.support.AbstractJobRepositoryFactoryBean.invoke(AbstractJobRepositoryFactoryBean.java:177)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy125.createJobExecution(Unknown Source)
at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:134)
at com.my.package.service.MyService.launchBatchJob(MyService.java:30)
到目前为止,唯一可行的方法是我放弃 @Transactional
注释,而是将 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
添加到我的 application-test.properties
文件中。但是,这似乎不是一个好主意,因为它不现实——如果我添加这个,那么即使我的代码中存在由于延迟初始化异常而导致的错误,我也永远不会在测试中看到它.
对不起小说,希望有人能给我指明正确的方向:(
编辑:
这也是我的内存中 Spring-批处理配置,我在其中尝试关闭交易验证。不幸的是,虽然这让我更进一步,但 Spring Batch partioner 的自动装配 EntityManager 突然无法 运行 在 H2 数据库中查询。
@Configuration
@EnableBatchProcessing
public class InMemoryBatchManagementConfig {
@Bean
public ResourcelessTransactionManager resourceslessTransactionManager() {
ResourcelessTransactionManager resourcelessTransactionManager = new ResourcelessTransactionManager();
resourcelessTransactionManager.setNestedTransactionAllowed(true);
resourcelessTransactionManager.setValidateExistingTransaction(false);
return resourcelessTransactionManager;
}
@Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactory(ResourcelessTransactionManager txManager)
throws Exception {
MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean(txManager);
factory.setValidateTransactionState(false);
factory.afterPropertiesSet();
return factory;
}
@Bean
public JobRepository jobRepository(MapJobRepositoryFactoryBean factory) throws Exception {
return factory.getObject();
}
@Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) throws Exception {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(jobRepository);
launcher.afterPropertiesSet();
return launcher;
}
@Bean
public JobExplorer jobExplorer(MapJobRepositoryFactoryBean factory) {
return new SimpleJobExplorer(factory.getJobInstanceDao(), factory.getJobExecutionDao(),
factory.getStepExecutionDao(), factory.getExecutionContextDao());
}
@Bean
public BatchConfigurer batchConfigurer(MapJobRepositoryFactoryBean mapJobRepositoryFactory,
ResourcelessTransactionManager resourceslessTransactionManager,
SimpleJobLauncher jobLauncher,
JobExplorer jobExplorer) {
return new BatchConfigurer() {
@Override
public JobRepository getJobRepository() throws Exception {
return mapJobRepositoryFactory.getObject();
}
@Override
public PlatformTransactionManager getTransactionManager() throws Exception {
return resourceslessTransactionManager;
}
@Override
public JobLauncher getJobLauncher() throws Exception {
return jobLauncher;
}
@Override
public JobExplorer getJobExplorer() throws Exception {
return jobExplorer;
}
};
}
}
发生此错误是因为您的代码已经在 Spring Batch 驱动的事务中执行。所以运行事务范围内的作业不正确。但是,如果您仍想禁用作业存储库完成的事务验证,可以将 validateTransactionState
设置为 false,请参阅 AbstractJobRepositoryFactoryBean#setValidateTransactionState。
也就是说,运行 事务中的作业不是修复 org.hibernate.LazyInitializationException
的方法。 属性 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
的存在是有原因的,如果它对你有用,我相信这是比 运行 交易中整个工作更好的方法(顺便说一句,如果我有为此使用事务,我会将其范围缩小到最小(例如步骤)而不是整个作业。
您可以使用 TransactionTemplate
到 运行 以编程方式进行交易,只有交易中的 "setup"(而不是 @Transactional
中的所有内容)。不幸的是,这种方式将提交事务,您将需要进行一些手动清理。
它可以像任何其他 bean 一样自动装配:
@Autowired
private TransactionTemplate transactionTemplate;
...它是这样使用的:
transactionTemplate.execute((transactionStatus) -> {
// ...setup...
return null; // alternatively you can return some data out of the callback
});