java - 如何在 @Transactional SpringBootTest 测试用例中测试 Spring Batch 作业?

标签 java spring-data spring-batch spock lazy-initialization

我今天似乎赢不了......

  1. 有没有一种方法可以在 Spock SpringBootTest 集成测试中读取 OneToMany 关系,而无需将测试注释为 @Transactional 或添加不切实际的 spring. jpa.properties.hibernate.enable_lazy_load_no_trans=true
  2. 或者,有没有办法从 @Transactional 测试用例中启动 Spring-Batch 作业?

让我详细说明一下...

我正在尝试为我的 Spring Batch 报告流程进行一个简单的 Spring Boot 集成测试,该测试从困惑的 DB2 表网络中读取数据,并为感兴趣的系统生成一系列更改消息。我正在使用 Groovy Spock 测试框架和一个 H2 内存数据库,其中填充了 DB2 表数据的代表性部分。

在测试开始时,我尝试使用给定表中的每个实体在驱动我的消息传递的更改跟踪表中生成条目。

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 Batch 作业时,我想从我的 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 Batch 作业!即使我通过 ResourcelessTransactionManagerMapJobRepositoryFactoryBean 使用内存作业管理器,因为这只是我正在编写的一个短暂的计划脚本...

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$1.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-Batch 配置,我在其中尝试关闭事务验证。不幸的是,虽然这让我更进一步,Spring Batch 分区程序的 Autowiring 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 的存在是有原因的,如果它适合您,我相信这是比在事务中运行整个作业更好的方法(并且顺便说一句,如果我必须为此使用事务,我会将其范围缩小到最小(例如步骤),而不是整个作业)。

关于java - 如何在 @Transactional SpringBootTest 测试用例中测试 Spring Batch 作业?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60610021/

相关文章:

java - 使用 servlet 的套接字服务器

带括号语法的 Java 函数定义

postgresql - PSQLException : The column name clazz_ was not found in this ResultSet

Spring Security/MVC/JPA --> 不支持请求方法 'POST'

java - 一对多的连接列为 null hibernate

java - Spring 批处理 |读取计数 = 过滤 + 写入?

java - 重新加载后台进程的配置文件

java - 避免在 Spring 中创建 session

java - 不使用 XML 动态创建 Spring Batch 作业

java.lang.NoClassDefFoundError : org/springframework/core/ResolvableTypeProvider- Spring Batch