当前位置: 首页 > 知识库问答 >
问题:

如何在@Transactional SpringBootTest测试用例中测试spring批处理作业?

罗寒
2023-03-14

我今天好像赢不了...

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

让我详细说明...

我试图为我的Spring Batch reporting流程提供一个简单的Spring Boot Integration测试,该测试从Dangled web of 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批处理作业时,我希望从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批处理作业不能从事务的上下文中启动!尽管我通过ResourceLesTransActionManagerMapJobRepositoryFactoryBean使用内存中的作业管理器,但由于这只是我编写的一个短期计划脚本······

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 Partioner的autowired 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;
            }
        };
    }
}

共有1个答案

申屠瀚海
2023-03-14

发生此错误是因为您的代码已经在Spring Batch驱动的事务中执行。因此在事务范围内运行作业是不正确的。但是,如果您仍然希望禁用由作业存储库完成的事务验证,可以将validateTransactionState设置为false,请参见AbstractJobRepositoryFactoryBean#SetValidateTransactionState。

也就是说,在事务中运行作业不是修复org.hibernate.lazyInitializationException的方法。属性spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true的存在是有原因的,如果它适合您,我相信它是一种比在事务中运行整个作业更好的方法(顺便说一句,如果必须为此使用事务,我会将其范围缩小到最小(例如步骤),而不是整个作业)。

 类似资料:
  • 背景: 我目前正在开发一个应用程序,该应用程序使用Spring Batch读取一组文件并将文件的内容缓存在内存中。缓存的数据然后由划分为不同类的业务逻辑层使用,每个类通过使用缓存中的数据执行特定的业务功能。 要求: 我被分配了对业务逻辑层进行单元测试的任务。由于业务逻辑层假设数据将在缓存中可用,所以在测试业务逻辑类之前,我需要一种在单元测试中触发Spring批处理作业的方法。 问题是: 我正在考虑

  • 我不确定我是否能清楚地解释我的情况。请建议您对spring-batch作业测试E2E流是否有其他更好的意见,以及您是否可以对上述方法提供任何清晰的说明,这将是有帮助的。

  • 我有一个使用的小型spring批处理作业。当我从命令行启动时,该作业运行良好,但当我要测试组件时,我得到一个。我不明白为什么这个工作能很好地工作,但是我不能根据文档测试它。 我只有一个简单的类,带有基本的注释。 以下是我的作业配置类: 下面是我的测试类: 问题出现在第192行的类中: 下面是来自测试执行的stacktrace: 2019-06-12 17:17:32.845-info-[main]

  • 我正在寻找一些关于测试Spring批处理步骤和步骤执行的一般性意见和建议。 我的基本步骤是从api读入数据,处理实体对象,然后写入数据库。我已经测试了快乐之路,这一步成功地完成了。我现在想做的是在处理器阶段数据丢失时测试异常处理。我可以单独测试processor类,但我更愿意测试整个步骤,以确保在步骤/作业级别正确反映流程故障。 我已经阅读了spring批量测试指南,如果我是诚实的,我对它有点迷茫

  • 我遵循了官方的Spring批处理指南(https://Spring.io/guides/gs/batch-processing/)并成功地完成了该示例。对于同样的工作,我正在尝试创建一个端到端的集成测试。特别是我只使用测试配置。在我的测试中,我定义了所有需要的bean。因此这应该是运行作业所需的唯一配置。这给了很大的灵活性。创建ApplicationContext时测试失败。它抱怨找不到数据源。当

  • 问题内容: 在TDD(测试驱动开发)开发过程中,如何处理测试数据?假设有一个场景,解析日志文件以获取所需的列。对于强大的测试,我该如何准备测试数据?对我来说,将此类文件定位到测试类文件是否正确? 问题答案: 例如,Maven对用于处理测试数据的文件夹结构使用约定: 如果您使用maven进行构建,则需要将测试资源放置在正确的文件夹中,如果您使用其他内容进行构建,则您可能希望使用此结构,因为它不仅仅是