Как протестировать пакетное задание в тестовом кейсе @Transactional SpringBootTest? - PullRequest
0 голосов
/ 10 марта 2020

Я просто не могу выиграть сегодня ...

  1. Есть ли способ прочитать из отношения OneToMany в интеграционном тесте Spock SpringBootTest, не помечая его как @Transactional или добавить нереалистичные c spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true?
  2. ИЛИ, есть ли способ запустить Spring-Batch Job из @Transactional теста?

Позвольте мне уточнить ...

I Я пытаюсь заставить простой тест Spring Boot Integration работать для моего процесса создания отчетов Spring Batch, который читает из запутанной паутины таблиц 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 Job, я хочу протестировать его из условия 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 Job не может быть запущен из контекста транзакции! Несмотря на то, что я использую диспетчер заданий в памяти через 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$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. НО, это не очень хорошая идея, потому что это нереально c - если я добавлю это, то даже если в моем коде есть ошибка из-за исключения lazy-initialization-исключение, я бы никогда этого не увидел в тестах.

Извините за роман, надеясь, что кто-то может указать мне правильное направление: (


РЕДАКТИРОВАТЬ:

Также вот моя весна в памяти - Пакетная конфигурация, в которой я попытался отключить проверку транзакции. К сожалению, хотя это немного продвинуло меня, автоматически подключенный EntityManager разделителя Spring Batch неожиданно не запускает запросы в базе данных 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;
            }
        };
    }
}

Ответы [ 2 ]

1 голос
/ 10 марта 2020

Эта ошибка возникает из-за того, что ваш код будет уже выполнен в транзакции, управляемой Spring Batch. Поэтому выполнение задания в рамках транзакции не является правильным. Однако, если вы все еще хотите отключить проверку транзакции, выполняемую репозиторием задания, вы можете установить для validateTransactionState значение false, см. AbstractJobRepositoryFactoryBean # setValidateTransactionState .

Тем не менее, запуск задания в транзакции это не способ исправить org.hibernate.LazyInitializationException. Свойство spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true существует по какой-то причине, и если оно работает для вас, я считаю, что это лучший подход, чем выполнение всего задания в транзакции (и, кстати, если бы мне пришлось использовать транзакцию для этого, я бы сузил его объем до минимума (например, шаг), а не всей работы).

0 голосов
/ 10 марта 2020

Вы можете выполнять транзакции программно, используя TransactionTemplate, чтобы выполнить только «настройку» внутри транзакции (вместо того, чтобы иметь все в @Transactional). К сожалению, таким образом транзакция будет зафиксирована, и вам потребуется выполнить некоторую ручную очистку.

Она может быть автоматически подключена как любой другой компонент:

    @Autowired
    private TransactionTemplate transactionTemplate;

... и используется таким образом :

        transactionTemplate.execute((transactionStatus) -> {
            // ...setup...
            return null; // alternatively you can return some data out of the callback
        });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...