Я просто не могу выиграть сегодня ...
- Есть ли способ прочитать из отношения
OneToMany
в интеграционном тесте Spock SpringBootTest, не помечая его как @Transactional
или добавить нереалистичные c spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
? - ИЛИ, есть ли способ запустить 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;
}
};
}
}