1

Consider the two classes below:

class TerminalMaintenance{

    @Id
    @Column(name="id")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_MAINTENANCE")
    @SequenceGenerator(name ="SEQ_MAINTENANCE", sequenceName = "SEQ_MAINTENANCE", allocationSize = 1)
    private long id;

    @Column(name="code", unique = true)
    private String code;

    @ManyToOne(fetch = FetchType.LAZY)
    private Terminal terminal;

    public TerminalMaintenance() {
    }

    public TerminalMaintenance(long id) {
        this.id = id;
    }

    public TerminalMaintenance(String code) {
        this.code = code;
    }
...
}

class Terminal{
    @Id
    @Column(name="id")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_TERMINAL")
    @SequenceGenerator(name ="SEQ_TERMINAL", sequenceName = "SEQ_TERMINAL", allocationSize = 1)
    private long id;

    @Column(name="code", unique = true)
    private String code;
    
    @OneToMany(
            fetch = FetchType.LAZY,
            cascade = CascadeType.ALL,
            orphanRemoval = true,
            mappedBy = "terminal")
    private List<TerminalMaintenance> maintenances;
...

}

interface TerminalRepo extends JpaRepository<Terminal, Long>{
    
}

interface TerminalMaintenanceRepo extends JpaRepository<TerminalMaintenance, Long>{

}

When I want to save the TerminalMaintenance entity, I already know id of its Terminal entity and I do not load its entire terminal object in order to persist object TerminalMaintenance like:

TerminalMaintenance terminalMaintenance = new TerminalMaintenance();
Terminal terminal = new Terminal(1); \\ using id constructor

terminalMaintenance.setTerminal(terminal);


terminalMaintenanceRepo.save(terminalMaintenance);


And it works fine. But when I want to instantiate the object Terminal using its code field - which is unique - like :

TerminalMaintenance terminalMaintenance = new TerminalMaintenance();
Terminal terminal = new Terminal("core123"); \\ using code constructor

terminalMaintenance.setTerminal(terminal);


terminalMaintenanceRepo.save(terminalMaintenance);

it thorws :

org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : ir.noorbank.entity.tms.TerminalMaintenance.terminal -> ir.noorbank.entity.tms.Terminal; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : ir.noorbank.entity.tms.TerminalMaintenance.terminal -> ir.noorbank.entity.tms.Terminal

    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:371)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:154)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy122.findAll(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:205)
    at com.sun.proxy.$Proxy111.findAll(Unknown Source)
    at ir.noorbank.repo.TerminalMaintenanceRepositoryTest.test(TerminalMaintenanceRepositoryTest.java:66)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : ir.noorbank.entity.tms.TerminalMaintenance.terminal -> ir.noorbank.entity.tms.Terminal
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:151)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1542)
    at org.hibernate.query.Query.getResultList(Query.java:165)
    at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:76)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:355)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:78)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.ImplementationInvocationMetadata.invoke(ImplementationInvocationMetadata.java:72)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:382)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:205)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:549)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:155)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    ... 66 more
Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : ir.noorbank.entity.tms.TerminalMaintenance.terminal -> ir.noorbank.entity.tms.Terminal
    at org.hibernate.engine.spi.CascadingActions$8.noCascade(CascadingActions.java:379)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:167)
    at org.hibernate.event.internal.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:158)
    at org.hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:148)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:81)
    at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:50)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
    at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1328)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1408)
    at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1565)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1533)
    ... 88 more


I also tried @NaturalId above code property of Terminal but there is no change.

So, Is there a way to persist an entity by initializing its sub-entity using one of its unique properties instead of initializing it by id property?

p.s: Terminal with id == 1 and code == code123 is already persisted in the database.

Morteza
  • 642
  • 7
  • 17
  • Does this solve your problem? https://stackoverflow.com/questions/2302802/how-to-fix-the-hibernate-object-references-an-unsaved-transient-instance-save – Chinez Dec 12 '20 at 13:15
  • 1
    @Chinez Unfortunately not. Did you read my question? It works when I initialize the child entity using its id but not when initialized with code. – Morteza Dec 12 '20 at 13:30

2 Answers2

4

This code:

TerminalMaintenance terminalMaintenance = new TerminalMaintenance();
Terminal terminal = new Terminal(1); \\ using id constructor

terminalMaintenance.setTerminal(terminal);


terminalMaintenanceRepo.save(terminalMaintenance);

works in Hibernate because the Terminal object contains the identifier and, unlike JPA, Hibernate can work just fine if the parent entity is represented by a POJO that has the id set.

On the other hand, this code:

TerminalMaintenance terminalMaintenance = new TerminalMaintenance();
Terminal terminal = new Terminal("core123"); \\ using code constructor

terminalMaintenance.setTerminal(terminal);


terminalMaintenanceRepo.save(terminalMaintenance);

doesn't work because the Terminal object reference doesn't have the id set, so there's no way for Hibernate to know what value to set for the associated FK column.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Now I understand and thanks a bunch. But one more little thing, I am not using hibernate I am using spring data jpa -which I think uses JPA under the hood!. So, can we conclude jpa also behaves the same way? – Morteza Dec 12 '20 at 16:41
  • 1
    By default, Spring Data JPA uses Hibernate. Check out the stacktrace you added in the question and you'll see the Hibernate classes that throw the exception. – Vlad Mihalcea Dec 12 '20 at 17:51
  • @VladMihalcea Great answer! Out of curiosity, using `new Terminal(1)` in this case has `em.getReference()/session.load()` semantics (assuming no `CascadeType.MERGE`), right? I tried looking it up in the docs, but without success – crizzis Dec 12 '20 at 18:33
  • The dummy POJO is a Hibernate feature only. The [`getReference`](https://vladmihalcea.com/entitymanager-find-getreference-jpa/) or the Spring Repository `getOne` methods are JPA compliant and allow you to initialize the Proxy object later. – Vlad Mihalcea Dec 12 '20 at 21:17
1

It won't work. TBH I'm genuinely surprised that the attempt with new Terminal(1) works.

You need to fetch the existing entity (terminalRepository.findByCode("123")) and use the result. Otherwise, as far as JPA is concerned, new Terminal("core123") is a brand new entity.

crizzis
  • 9,978
  • 2
  • 28
  • 47
  • yeah! and I am trying to figure it out how `new Terminal(1)` works. Since it really works, there should be a way `new Terminal("core123")` work too – Morteza Dec 12 '20 at 15:20