0

I'm using Hibernate JPA and trying to update nested entity.

Entities:

@Entity
@Table(name = "profile")
@AllArgsConstructor
@NoArgsConstructor
public class ProfileEntity implements Serializable {

........

    @OneToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    @JoinColumn(name="office_address")
    private AddressEntity officeAddress;
}

@Entity
@Table(name="address")
@AllArgsConstructor
@NoArgsConstructor
public class AddressEntity implements Serializable{

......

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="user_id")
    private ProfileEntity userProfile;
}

Updating Profile entity with new Address entity

@Transactional
@Modifying
@Query("update ProfileEntity set officeAddress=:address where userId=:userId")
int updateProfile(
        @Param("userId") Long userId,
        @Param("address") AddressEntity address);

Error:

org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.entities.AddressEntity; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.entities.AddressEntity
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:381)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:436)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    .
    .
    .
    .

I understand as the transaction is not committed after a new AddressEntity is inserted, and hibernate is flushing before committing. Thus, it's unable to make an update to ProfileEntity with the new address PK.

But how to fix it ?

I also tried inserting a new AddressEntity and after committing the transaction, updating ProfileEntity, that works. But that's not a good practice, because if ProfileEntity update throws an exception AddressEntity insert should also be rolled back.

Prateek Pande
  • 495
  • 3
  • 12
  • Does this answer your question? [How to fix the Hibernate "object references an unsaved transient instance - save the transient instance before flushing" error](https://stackoverflow.com/questions/2302802/how-to-fix-the-hibernate-object-references-an-unsaved-transient-instance-save) – SternK Feb 14 '20 at 09:21
  • Why on earth are you updating using a query rather then using the standard EnityManager operations as suggested in the answer below? – Alan Hay Feb 15 '20 at 11:55
  • @SternK No.Cascade works for save. Not for updating the entity. – Prateek Pande Feb 18 '20 at 16:28

1 Answers1

1

I guess address you pass is not present in the database. CascadeType.ALL does not work because of updating by @Query. Cascading is provided for PERSIST, MERGE, REMOVE, REFRESH, DETACH operations only.

Use the following

@Transactional
public ProfileEntity updateProfile(Long userId, AddressEntity address) {
    ProfileEntity profileEntity = profileEntityRepository.findByUserId(userId);

    profileEntity.setOfficeAddress(address);
    address.setUserProfile(profileEntity);

    return profileEntityRepository.save(profileEntity);
}
Oleksii Valuiskyi
  • 2,691
  • 1
  • 8
  • 22
  • That i already tried. But what if we have to roleback. I have to manually delete entity. I wanted to do all in a single transaction. Abstract from my question :-- I also tried inserting a new AddressEntity and after committing the transaction, updating ProfileEntity, that works. But that's not a good practice, because if ProfileEntity update throws an exception AddressEntity insert should also be rolled back. – Prateek Pande Feb 14 '20 at 09:19
  • So use the provided code with `@Transactional`. It should be deleted. I have edited the answer – Oleksii Valuiskyi Feb 14 '20 at 09:24
  • In the answer provided code `AddressEntity address` is allowed to be `transient` – Oleksii Valuiskyi Feb 14 '20 at 09:46
  • Thanks Alex. So just to be clear we need to fetch existing entity and perform a save/update. But, it's not possible to update ProfileEntity directly by setting new AddressEntity. – Prateek Pande Feb 15 '20 at 13:48
  • 1
    Spring data provide a lot of useful features (cascading, optimistic locking, entity listeners etc...) which are accessible only using this approach. Direct updating by @Query lays all responsibility on you – Oleksii Valuiskyi Feb 15 '20 at 16:54