java - Error "detached entity passed to persist” when selecting data via the repository, I don't understand - S

I'm having trouble updating an entity (Formation) in a ManyToMany relationship with another entity

I'm having trouble updating an entity (Formation) in a ManyToMany relationship with another entity (Intervenant).

My integration test fails because of the following error:

.springframework.dao.InvalidDataAccessApiUsageException: detached
entity passed to persist: fr.foo.bar.model.Intervenant

The problem is that, according to the stacktrace, the exception is caught on a method that just retrieves data, so no call to save() is made. I don't understand why this error appears.

Here is my test:

@BeforeEach
void setUp() {
    Intervenant newIntervenant = new Intervenant();
    newIntervenant.setIdnum("IDNUM_NEW");
    newIntervenant.setIdentite("New Intervenant");

    Formation formation = new Formation();
    formation.setLibelle("Sample Formation");
    formation.setCodeApogee("APOGEE123");
    formation.setCodeVet("VET789");
    formation.setIntervenants(Set.of(newIntervenant));

    formationDto = formationMapper.toDto(formation);
}
    
@Test
@Transactional
void whenUpdateFormation_thenIntervenantsAreUpdated() throws Exception {
    
    FormationDto createdFormationDto = formationService.create(formationDto);

    formationRepository.flush();

    IntervenantDto newIntervenantDto = new IntervenantDto(null, "New IDNUM", "New Name");
    createdFormationDto.intervenants().add(newIntervenantDto);

    Intervenant existingIntervenant = new Intervenant();
    existingIntervenant.setIdnum("IDNUM_EXISTING");
    existingIntervenant.setIdentite("Existing Intervenant");
    existingIntervenant = intervenantRepository.saveAndFlush(existingIntervenant);
    
    existingIntervenant = intervenantRepository.findById(existingIntervenant.getId()).orElseThrow();
    createdFormationDto.intervenants().add(intervenantMapper.toDto(existingIntervenant));

    FormationDto updatedFormationDto = formationService.update(createdFormationDto.id(), createdFormationDto); // detached entity passed to persist: fr.foo.bar.model.Intervenant
    
    [...]
}

The exception occurs in the last line of the test, the one calling update()

Here is the update() method:

@Override
@Transactional
public FormationDto update(Long id, FormationDto formationDto) throws Exception {
    Formation formation = formationRepository
            .findById(id)
            .orElseThrow(() -> new EntityNotFoundException(String.format("La formation %d n'existe pas", id)));

    Set<Intervenant> updatedIntervenants = new HashSet<>();
    for (IntervenantDto intervenantDto : formationDto.intervenants()) {
        if (intervenantDto.id() == null) {
            IntervenantDto createdDto = intervenantService.create(intervenantDto);
            updatedIntervenants.add(intervenantService.toEntity(createdDto));
        } else {
            Intervenant existing = intervenantService.getEntity(intervenantDto.id());
            updatedIntervenants.add(existing);
        }
    }
    formation.setIntervenants(updatedIntervenants);

    Collection<FormationEtudiant> existingEtudiants = formationEtudiantService.findAllByFormation(formation); // detached entity passed to persist: fr.foo.bar.model.Intervenant

    [...]
}

And the exception occurs when the formationEtudiantService.findAllByFormation() method is called, as shown below:

@Override
public List<FormationEtudiant> findAllByFormation(Formation formation) {

    return formationEtudiantRepository.findAllByFormation(formation);
}

And here is the repository:

public interface FormationEtudiantRepository extends JpaRepository<FormationEtudiant, FormationEtudiantId> {

    List<FormationEtudiant> findAllByFormation(Formation formation);
}

Here's an extract from the Formation entity:

@Entity(name = "FormationEntity")
@Table(name = "formation")
public class Formation implements Identifiable<Long> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", columnDefinition = "INT UNSIGNED not null")
    private Long id;

    @Column(name = "libelle", length = 100)
    private String libelle;

    @Column(name = "code_apogee", length = 100)
    private String codeApogee;

    @Column(name = "code_vet", length = 100)
    private String codeVet;

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
    @JoinTable(name = "formation_intervenant",
            joinColumns = @JoinColumn(name = "id_formation"),
            inverseJoinColumns = @JoinColumn(name = "id_intervenant"))
    private Set<Intervenant> intervenants = new LinkedHashSet<>();
    
    public Set<Intervenant> getIntervenants() {
        return intervenants;
    }

    public void setIntervenants(Set<Intervenant> intervenants) {
        this.intervenants = intervenants;
    }
}

And finally, the Intervenant entity:

@Entity(name = "IntervenantEntity")
@Table(name = "intervenant")
public class Intervenant implements Identifiable<Long> {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", columnDefinition = "INT UNSIGNED not null")
    private Long id;

    @Column(name = "idnum", nullable = false, length = 50)
    private String idnum;

    @Column(name = "identite", length = 100)
    private String identite;

    @Override
    public Long getId() {
        return id;
    }

    @ManyToMany(mappedBy = "intervenants")
    private Set<Formation> formations = new LinkedHashSet<>();

    public void setId(Long id) {
        this.id = id;
    }

    public String getIdnum() {
        return idnum;
    }

    public void setIdnum(String idnum) {
        this.idnum = idnum;
    }

    public String getIdentite() {
        return identite;
    }

    public void setIdentite(String identite) {
        this.identite = identite;
    }

    public Set<Formation> getFormations() {
        return formations;
    }

    public void setFormations(Set<Formation> formations) {
        this.formations = formations;
    }
}

It really drives me crazy... Can you help me ?

Edit: here's the stacktrace

.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: fr.foo.bar.model.Intervenant

    at .springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:290)
    at .springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
    at .springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
    at .springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at .springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at .springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:134)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218)
    at jdk.proxy2/jdk.proxy2.$Proxy167.findAllByFormation(Unknown Source)
    at fr.foo.bar.services.application.FormationEtudiantServiceImpl.findAllByFormation(FormationEtudiantServiceImpl.java:26)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at .springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:699)
    at fr.foo.bar.services.application.FormationEtudiantServiceImpl$$SpringCGLIB$$0.findAllByFormation(<generated>)
    at fr.foo.bar.services.application.FormationServiceImpl.update(FormationServiceImpl.java:226)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at .springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at .springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
    at .springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
    at .springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
    at .springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
    at .springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)
    at fr.foo.bar.services.application.FormationServiceImpl$$SpringCGLIB$$0.update(<generated>)
    at fr.foo.bar.FormationControllerIntegrationTests.whenUpdateFormation_thenIntervenantsAreUpdated(FormationControllerIntegrationTests.java:138)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at .junit.platformmons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
    at .junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at .junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
    at .junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
    at .junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
    at .junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
    at .junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at .junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
    at .junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
    at .junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
    at .junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
    at .junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at .junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at .junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at .junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at .junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at .junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at .junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
    at .junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at .junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
    at .junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at .junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
    at .junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
    at .junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: .hibernate.PersistentObjectException: detached entity passed to persist: fr.foo.bar.model.Intervenant
    at .hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:121)
    at .hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:118)
    at .hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:799)
    at .hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:342)
    at .hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:332)
    at .hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:511)
    at .hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:432)
    at .hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
    at .hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:545)
    at .hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:475)
    at .hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:435)
    at .hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
    at .hibernate.engine.internal.Cascade.cascade(Cascade.java:151)
    at .hibernate.event.internal.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:155)
    at .hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145)
    at .hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:79)
    at .hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:48)
    at .hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
    at .hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1385)
    at .hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$0(ConcreteSqmSelectQueryPlan.java:100)
    at .hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:305)
    at .hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:246)
    at .hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:546)
    at .hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:363)
    at .hibernate.query.sqm.internal.QuerySqmImpl.list(QuerySqmImpl.java:1032)
    at .hibernate.query.Query.getResultList(Query.java:94)
    at .springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:127)
    at .springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:90)
    at .springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:148)
    at .springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:136)
    at .springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136)
    at .springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120)
    at .springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164)
    at .springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:77)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
    at .springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
    at .springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
    ... 96 more

edit: here is the code called from intervenantService.toEntity()

    @Override
    public Intervenant toEntity(IntervenantDto intervenantDto) {
    if ( intervenantDto == null ) {
        return null;
    }

    Intervenant intervenant = new Intervenant();

    intervenant.setId( intervenantDto.id() );
    intervenant.setIdnum( intervenantDto.idnum() );
    intervenant.setIdentite( intervenantDto.identite() );

    return intervenant;
}

I'm having trouble updating an entity (Formation) in a ManyToMany relationship with another entity (Intervenant).

My integration test fails because of the following error:

.springframework.dao.InvalidDataAccessApiUsageException: detached
entity passed to persist: fr.foo.bar.model.Intervenant

The problem is that, according to the stacktrace, the exception is caught on a method that just retrieves data, so no call to save() is made. I don't understand why this error appears.

Here is my test:

@BeforeEach
void setUp() {
    Intervenant newIntervenant = new Intervenant();
    newIntervenant.setIdnum("IDNUM_NEW");
    newIntervenant.setIdentite("New Intervenant");

    Formation formation = new Formation();
    formation.setLibelle("Sample Formation");
    formation.setCodeApogee("APOGEE123");
    formation.setCodeVet("VET789");
    formation.setIntervenants(Set.of(newIntervenant));

    formationDto = formationMapper.toDto(formation);
}
    
@Test
@Transactional
void whenUpdateFormation_thenIntervenantsAreUpdated() throws Exception {
    
    FormationDto createdFormationDto = formationService.create(formationDto);

    formationRepository.flush();

    IntervenantDto newIntervenantDto = new IntervenantDto(null, "New IDNUM", "New Name");
    createdFormationDto.intervenants().add(newIntervenantDto);

    Intervenant existingIntervenant = new Intervenant();
    existingIntervenant.setIdnum("IDNUM_EXISTING");
    existingIntervenant.setIdentite("Existing Intervenant");
    existingIntervenant = intervenantRepository.saveAndFlush(existingIntervenant);
    
    existingIntervenant = intervenantRepository.findById(existingIntervenant.getId()).orElseThrow();
    createdFormationDto.intervenants().add(intervenantMapper.toDto(existingIntervenant));

    FormationDto updatedFormationDto = formationService.update(createdFormationDto.id(), createdFormationDto); // detached entity passed to persist: fr.foo.bar.model.Intervenant
    
    [...]
}

The exception occurs in the last line of the test, the one calling update()

Here is the update() method:

@Override
@Transactional
public FormationDto update(Long id, FormationDto formationDto) throws Exception {
    Formation formation = formationRepository
            .findById(id)
            .orElseThrow(() -> new EntityNotFoundException(String.format("La formation %d n'existe pas", id)));

    Set<Intervenant> updatedIntervenants = new HashSet<>();
    for (IntervenantDto intervenantDto : formationDto.intervenants()) {
        if (intervenantDto.id() == null) {
            IntervenantDto createdDto = intervenantService.create(intervenantDto);
            updatedIntervenants.add(intervenantService.toEntity(createdDto));
        } else {
            Intervenant existing = intervenantService.getEntity(intervenantDto.id());
            updatedIntervenants.add(existing);
        }
    }
    formation.setIntervenants(updatedIntervenants);

    Collection<FormationEtudiant> existingEtudiants = formationEtudiantService.findAllByFormation(formation); // detached entity passed to persist: fr.foo.bar.model.Intervenant

    [...]
}

And the exception occurs when the formationEtudiantService.findAllByFormation() method is called, as shown below:

@Override
public List<FormationEtudiant> findAllByFormation(Formation formation) {

    return formationEtudiantRepository.findAllByFormation(formation);
}

And here is the repository:

public interface FormationEtudiantRepository extends JpaRepository<FormationEtudiant, FormationEtudiantId> {

    List<FormationEtudiant> findAllByFormation(Formation formation);
}

Here's an extract from the Formation entity:

@Entity(name = "FormationEntity")
@Table(name = "formation")
public class Formation implements Identifiable<Long> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", columnDefinition = "INT UNSIGNED not null")
    private Long id;

    @Column(name = "libelle", length = 100)
    private String libelle;

    @Column(name = "code_apogee", length = 100)
    private String codeApogee;

    @Column(name = "code_vet", length = 100)
    private String codeVet;

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
    @JoinTable(name = "formation_intervenant",
            joinColumns = @JoinColumn(name = "id_formation"),
            inverseJoinColumns = @JoinColumn(name = "id_intervenant"))
    private Set<Intervenant> intervenants = new LinkedHashSet<>();
    
    public Set<Intervenant> getIntervenants() {
        return intervenants;
    }

    public void setIntervenants(Set<Intervenant> intervenants) {
        this.intervenants = intervenants;
    }
}

And finally, the Intervenant entity:

@Entity(name = "IntervenantEntity")
@Table(name = "intervenant")
public class Intervenant implements Identifiable<Long> {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", columnDefinition = "INT UNSIGNED not null")
    private Long id;

    @Column(name = "idnum", nullable = false, length = 50)
    private String idnum;

    @Column(name = "identite", length = 100)
    private String identite;

    @Override
    public Long getId() {
        return id;
    }

    @ManyToMany(mappedBy = "intervenants")
    private Set<Formation> formations = new LinkedHashSet<>();

    public void setId(Long id) {
        this.id = id;
    }

    public String getIdnum() {
        return idnum;
    }

    public void setIdnum(String idnum) {
        this.idnum = idnum;
    }

    public String getIdentite() {
        return identite;
    }

    public void setIdentite(String identite) {
        this.identite = identite;
    }

    public Set<Formation> getFormations() {
        return formations;
    }

    public void setFormations(Set<Formation> formations) {
        this.formations = formations;
    }
}

It really drives me crazy... Can you help me ?

Edit: here's the stacktrace

.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: fr.foo.bar.model.Intervenant

    at .springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:290)
    at .springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
    at .springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
    at .springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at .springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at .springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:134)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218)
    at jdk.proxy2/jdk.proxy2.$Proxy167.findAllByFormation(Unknown Source)
    at fr.foo.bar.services.application.FormationEtudiantServiceImpl.findAllByFormation(FormationEtudiantServiceImpl.java:26)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at .springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:699)
    at fr.foo.bar.services.application.FormationEtudiantServiceImpl$$SpringCGLIB$$0.findAllByFormation(<generated>)
    at fr.foo.bar.services.application.FormationServiceImpl.update(FormationServiceImpl.java:226)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at .springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at .springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
    at .springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
    at .springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
    at .springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
    at .springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)
    at fr.foo.bar.services.application.FormationServiceImpl$$SpringCGLIB$$0.update(<generated>)
    at fr.foo.bar.FormationControllerIntegrationTests.whenUpdateFormation_thenIntervenantsAreUpdated(FormationControllerIntegrationTests.java:138)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at .junit.platformmons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
    at .junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at .junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
    at .junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
    at .junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
    at .junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
    at .junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at .junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at .junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
    at .junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
    at .junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
    at .junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
    at .junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at .junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at .junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at .junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at .junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at .junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at .junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at .junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at .junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
    at .junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at .junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
    at .junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
    at .junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at .junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
    at .junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
    at .junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: .hibernate.PersistentObjectException: detached entity passed to persist: fr.foo.bar.model.Intervenant
    at .hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:121)
    at .hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:118)
    at .hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:799)
    at .hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:342)
    at .hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:332)
    at .hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:511)
    at .hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:432)
    at .hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
    at .hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:545)
    at .hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:475)
    at .hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:435)
    at .hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
    at .hibernate.engine.internal.Cascade.cascade(Cascade.java:151)
    at .hibernate.event.internal.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:155)
    at .hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145)
    at .hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:79)
    at .hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:48)
    at .hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
    at .hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1385)
    at .hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$0(ConcreteSqmSelectQueryPlan.java:100)
    at .hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:305)
    at .hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:246)
    at .hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:546)
    at .hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:363)
    at .hibernate.query.sqm.internal.QuerySqmImpl.list(QuerySqmImpl.java:1032)
    at .hibernate.query.Query.getResultList(Query.java:94)
    at .springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:127)
    at .springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:90)
    at .springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:148)
    at .springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:136)
    at .springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136)
    at .springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120)
    at .springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164)
    at .springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:77)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
    at .springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
    at .springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at .springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at .springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
    ... 96 more

edit: here is the code called from intervenantService.toEntity()

    @Override
    public Intervenant toEntity(IntervenantDto intervenantDto) {
    if ( intervenantDto == null ) {
        return null;
    }

    Intervenant intervenant = new Intervenant();

    intervenant.setId( intervenantDto.id() );
    intervenant.setIdnum( intervenantDto.idnum() );
    intervenant.setIdentite( intervenantDto.identite() );

    return intervenant;
}
Share Improve this question edited Nov 25, 2024 at 8:40 Harkonnen asked Nov 19, 2024 at 14:52 HarkonnenHarkonnen 8132 gold badges9 silver badges21 bronze badges 5
  • In your question you talking about stacktrace. Is the reason you hiding it from us? – talex Commented Nov 19, 2024 at 15:36
  • @talex moved to Codidact, no problem, I thought it was just too long, but here it is – Harkonnen Commented Nov 20, 2024 at 13:47
  • Problem may hide in intervenantService.toEntity(createdDto). Does it set id? – talex Commented Nov 20, 2024 at 14:23
  • This method just calls a mapper generated by MapStruct. I have edited the post and added the code. The id is set – Harkonnen Commented Nov 25, 2024 at 8:43
  • I edited my answer to provide solution. – talex Commented Nov 25, 2024 at 8:46
Add a comment  | 

1 Answer 1

Reset to default 0

Search methods trigger saving dirty (technical term) objects to db. Because those objects can be potentially result of search.

Second part of your puzzle is the fact that your Intervenant is not saved.
You should explicitly attach entity to session (call persist) in case you want to use it.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1742421626a4440797.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信