1
votes

Le référentiel de démarrage Spring n'est pas enregistré dans la base de données s'il est appelé à partir d'un travail planifié

J'ai une application Spring Boot dans laquelle je dois planifier un travail pour lire des fichiers d'un répertoire spécifique et stocker les données dans la base de données.

J'ai utilisé Spring batch pour gérer la partie fichiers car le nombre de fichiers est très important.

L'application a un composant nommé PraserStarer qui a une méthode nommée startParsing . Cette méthode est annotée avec l'annotation @scheduled .

Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3552) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1444) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1440) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350) ~[spring-orm-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at com.sun.proxy.$Proxy87.flush(Unknown Source) ~[na:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:308) ~[spring-orm-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at com.sun.proxy.$Proxy87.flush(Unknown Source) ~[na:na]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:533) ~[spring-data-jpa-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:504) ~[spring-data-jpa-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359) ~[spring-data-commons-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200) ~[spring-data-commons-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644) ~[spring-data-commons-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608) ~[spring-data-commons-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595) ~[spring-data-commons-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595) ~[spring-data-commons-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) ~[spring-data-commons-2.1.4.RELEASE.jar:2.1.4.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    ... 66 common frames omitted

J'ai une interface de référentiel NewsRepositry injectée dans l'écrivain du lot de printemps première étape.

L'application dispose d'un contrôleur simple pour appeler manuellement la méthode startParsing . Lors de l'appel de la méthode startParsing à partir du contrôleur, tout fonctionne correctement. Le travail de lot de printemps démarre normalement, lit les fichiers, écrit les données dans la base de données et archive les fichiers.

Lorsque la méthode startParsing est appelée à partir du cadre de planification, le spring le travail par lots démarre normalement et lit les fichiers mais rien n'est stocké dans la base de données.

Je soupçonne que le problème ici est qu'il existe deux contextes différents, un pour la partie planification et un autre pour le reste de l'application.

Pour une raison quelconque, il n'y a pas de gestionnaire de transactions dans le contexte de planification qui ne fait rien aller à la base de données.

1- Ma suspicion est-elle correcte?

2- Si oui, comment puis-je forcer le gestionnaire de transactions à être chargé dans l'autre contexte?

EDIT

Le code de la classe de démarrage de l'analyseur est ci-dessous

@Transactional
    public void write(List<? extends GdeltRecord> items) throws Exception {
        for (GdeltRecord gdeltRecord : items) {
            dataRepo.saveAndFlush(gdeltRecord);
        }
//      dataRepo.saveAll(items);
 }

Le code de l'auditeur de tâche est ci-dessous p >

@Component
public class Writer implements ItemWriter<DataRecord>{

    @Autowired
    private DataRepository dataRepo;

    @Override
    public void write(List<? extends DataRecord> items) throws Exception {
        dataRepo.saveAll(items);
    }

}

Ce qui reste est le rédacteur et il est ci-dessous

@Component
public class ParserJobListener extends JobExecutionListenerSupport {

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    private Resource[] resources;

    @Value("${app.archive_directory}")
    private String archiveDirectory;

    @Autowired
    private Writer writer;

    public MultiResourceItemReader<DataRecord> multiResourceItemReader() {
        MultiResourceItemReader<DataRecord> resourceItemReader = new MultiResourceItemReader<DataRecord>();
        resourceItemReader.setResources(resources);
        resourceItemReader.setDelegate(new Reader());
        return resourceItemReader;
    }

    public Step archivingStep() {
        FileArchivingTask archivingTask = new FileArchivingTask(resources, archiveDirectory);
        return stepBuilderFactory.get("Archiving step").tasklet(archivingTask).build();
    }

    public Step processingStep() {
        return stepBuilderFactory.get("Process news file").<DataRecord, DataRecord>chunk(1000)
                .reader(multiResourceItemReader()).writer(writer).build();
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
        if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
            System.out.println("Job finished")
        }
    }

    public void setResources(Resource[] resources) {
        this.resources = resources;
    }

}

Édition 2

J'ai changé le méthode d'écriture de writer pour enregistrer et vider chaque élément individuellement comme suit

@Component
public class ParserStarter {

    @Autowired
    JobLauncher jobLauncher;

    @Value("${app.data_directory}")
    private String dataDir;

    @Autowired
    private ParserJobListener jobListener;

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    public Resource[] getResources() throws IOException {
        // return array of file resource to be processed
    }

//  @Scheduled(fixedDelay = 60 * 1000)
    public void startParsing() throws Exception {
        String jobName = System.currentTimeMillis() + " New Parser Job";

        JobParameters jobParameters = new JobParametersBuilder().addString("source", jobName).toJobParameters();

        jobLauncher.run(getParsingJob(), jobParameters);
    }

    @Bean(name="getParsingJob")
    private Job getParsingJob() throws IOException {

        jobListener.setResources(getResources());

        Step processingStep = jobListener.processingStep();

        Step archivingStep = jobListener.archivingStep();

        Job job = jobBuilderFactory.get("Store News").incrementer(new RunIdIncrementer())
                .listener(jobListener).start(processingStep).next(archivingStep).build();

        return job;
    }
}

Cette fois, l'application lance une exception TransactionRequiredException: aucune transaction n'est en cours .

Voici la trace complète de la pile de l'exception

@scheduled(fixedDelay = 60 * 1000)
public startParsing(){
    // start spring batch job
}


7 commentaires

Votre configuration est incorrecte, je retirerais les définitions d'étape de l'auditeur et les placerais à côté de la définition de travail dans une classe séparée (une configuration de travail typique peut être trouvée dans le guide de démarrage ). Concernant les transactions, pouvez-vous partager la configuration de votre gestionnaire de transactions?


Je ne pense pas que le problème réside dans les étapes. J'ai essayé ce que vous avez suggéré et la situation est la même. Concernant la configuration du gestionnaire de transactions, je n'ai fait aucune configuration. Il est configuré à l'aide de la configuration automatique du ressort.


J'ai également essayé de configurer manuellement la source de données, le gestionnaire de transactions et le gestionnaire d'entités, mais j'ai eu deux exceptions de gestionnaire de transactions. L'un des deux haricots est fourni par lot de printemps et je pense que c'est peut-être le problème.


Double possible de stackoverflow.com/questions/22509529 /…


@MahmoudBenHassine +1 <3. Je ne l'ai pas encore essayé mais je pense que vous avez raison.


@MahmoudBenHassine J'ai essayé la solution dans la question que vous avez mentionnée et cela a fonctionné. thnx


Heureux d'apprendre que vous avez résolu votre problème.


3 Réponses :


1
votes

Il est difficile d'analyser ce qui se passe exactement sans voir le code complet. Cependant, sur la base de la documentation , printemps utilise un traitement orienté bloc. Voici ce qu'il dit:

Une fois que le nombre d'éléments lus est égal à l'intervalle de validation, le bloc entier est écrit via ItemWriter, puis la transaction est validée.

C'est peut-être la raison pour laquelle vous ne voyez aucune écriture de base de données immédiatement.

Concernant le gestionnaire de transactions, vous pouvez le définir comme ceci (expliqué ici ):

@Bean
public Job sampleJob(JobRepository jobRepository, Step sampleStep) {
    return this.jobBuilderFactory.get("sampleJob")
                            .repository(jobRepository)
                .start(sampleStep)
                .build();
}

/**
 * Note the TransactionManager is typically autowired in and not needed to be explicitly
 * configured
 */
@Bean
public Step sampleStep(PlatformTransactionManager transactionManager) {
        return this.stepBuilderFactory.get("sampleStep")
                                .transactionManager(transactionManager)
                                .<String, String>chunk(10)
                                .reader(itemReader())
                                .writer(itemWriter())
                                .build();
}


5 commentaires

En fonction du nombre d'éléments et de l'intervalle de validation, ce n'est malheureusement pas le cas. Lorsque vous démarrez le travail à partir du contrôleur, les enregistrements apparaissent immédiatement dans la base de données, mais lorsqu'ils le démarrent à partir de la méthode planifiée, le travail commence et se termine et rien n'est stocké dans la base de données


Donc, cela fonctionne lorsque vous appelez startParsing à partir du contrôleur et ne fonctionne pas lorsque vous l'appelez à partir du planificateur?


Cela fonctionne lorsque j'appelle startParsing depuis le contrôleur et cela ne fonctionne pas lorsque la méthode startParsing est déclenchée par le cadre de planification.


Juste pour essayer, pouvez-vous écrire une autre méthode dans le contrôleur, l'annoter avec @Schedule et appeler startParser à partir de là?


J'ai déjà fait ça. J'ai annoté la même méthode avec programmé et la situation est toujours la même. Je vais ajouter une nouvelle modification à la question dès maintenant. Veuillez le vérifier.




0
votes

Nous devons mentionner explicitement JpaTransactionManager au lieu des transactions par lots de printemps par défaut.

@Configuration
@EnableBatchProcessing
public class MyJob extends DefaultBatchConfigurer {
       
       @Autowired
       private DataSource dataSource;

       @Bean
       @Primary
       public JpaTransactionManager jpaTransactionManager() {
            final JpaTransactionManager tm = new JpaTransactionManager();
            tm.setDataSource(dataSource);
            return tm;
       }
}


0 commentaires