Why does Hibernate generate a SELECT when calling saveOrFlush on a Spring Data Repository

#1

Hi! I’m working on a project using springboot 2.1.2 (since Hibernate is the default implementation for springdata jpa, I consider here a right place to ask for help) and I encounter a problem. To start with, I’ll show 2 of my entity classes as below:

@Entity
@Getter
@Setter
@NoArgsConstructor
public class User {

    @Id
    private String email;
    private String userName, password;

    @OneToMany(targetEntity = Address.class
            , fetch = FetchType.EAGER, mappedBy = "user"
            , cascade = {CascadeType.PERSIST})
    private List<Address> addressList = new ArrayList<>();

    public User(String email, String userName, String password) {
        this.email = email;
        this.userName = userName;
        this.password = password;
    }

    public void addAddress(Address address) {
        addressList.add(address);
    }

}


@Entity
@Getter
@Setter
@NoArgsConstructor
public class Address {
    @Id
    private long id;
    private String name, phone, location;
    @ManyToOne(targetEntity = User.class)
    private User user;

    public Address(String name, String phone, String location, User user) {
        this.name = name;
        this.phone = phone;
        this.location = location;
        this.user = user;
    }
}

And here’s the test case:

@Test
public void test1() {
    User user = new User("email", "userName", "password");
    Address address1 = new Address("name1", "phone1", "loc1", user);
    Address address2 = new Address("name2", "phone2", "loc2", user);
    user.addAddress(address1);
    user.addAddress(address2);
    userRepository.saveAndFlush(user);

    List<Address> addressList = addressRepository.findAllByUser(user);
    addressList.stream().forEach(x -> System.out.println(x.getName()));
}

As you can see, there is a OneToMany relationship between User and Address, and I’ve set the cascade method so that Address objects can be persisted when persisting a User object. However, the above test case run into these exceptions:

org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find ycqian.yummy.entity.Address with id 0; nested exception is javax.persistence.EntityNotFoundException: Unable to find ycqian.yummy.entity.Address with id 0

at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:378)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
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:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy91.saveAndFlush(Unknown Source)
at ycqian.yummy.repository.UserRepositoryTest.test1(UserRepositoryTest.java:31)
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.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Caused by: javax.persistence.EntityNotFoundException: Unable to find ycqian.yummy.entity.Address with id 0
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:162)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:230)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:281)
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:124)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:92)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1257)
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1140)
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:682)
at org.hibernate.type.EntityType.resolve(EntityType.java:464)
at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:239)
at org.hibernate.type.EntityType.resolve(EntityType.java:457)
at org.hibernate.type.EntityType.replace(EntityType.java:358)
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:551)
at org.hibernate.type.CollectionType.replace(CollectionType.java:699)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:163)
at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:393)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:327)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:170)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:69)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:901)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887)
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.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:308)
at com.sun.proxy.$Proxy88.merge(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:492)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:503)
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.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 40 more

It’s so strange, how can I receive a EntityNotFoundException when I’m persisting an object? I print out the sql generated by Hibernate, and it shows:

Hibernate: select user0_.email as email1_1_0_, user0_.password as password2_1_0_, user0_.user_name as user_nam3_1_0_ from user user0_ where user0_.email=?
Hibernate: select addresslis0_.user_email as user_ema5_0_0_, addresslis0_.id as id1_0_0_, addresslis0_.id as id1_0_1_, addresslis0_.location as location2_0_1_, addresslis0_.name as name3_0_1_, addresslis0_.phone as phone4_0_1_, addresslis0_.user_email as user_ema5_0_1_ from address addresslis0_ where addresslis0_.user_email=?
Hibernate: select address0_.id as id1_0_0_, address0_.location as location2_0_0_, address0_.name as name3_0_0_, address0_.phone as phone4_0_0_, address0_.user_email as user_ema5_0_0_, user1_.email as email1_1_1_, user1_.password as password2_1_1_, user1_.user_name as user_nam3_1_1_ from address address0_ left outer join user user1_ on address0_.user_email=user1_.email where address0_.id=?

So the question is:
Why does the Hibernate generate a select sql before inserting data?
And how can I fix the problem?
Any idea would be grateful, thanks.

0 Likes

#2

Why does the Hibernate generate a select sql before inserting data?

It’s not because of Hibernate, but because Spring calls merge instead of persist for entities using the assigned identifier in the saveAndFlush method.

So, you have two options:

  1. You either inject the EntityManager so you can call persist yourself
  2. You add a @Version property so that Spring Data figures out this is a transient entity, and not a detached one.

For more details, check out this article.

0 Likes

#3

thanks for your answer!
after thinking about your reply, i came up with a idea: is EntityManager more powerful but less convenient than repository? And in reality, what’s the point to choose to use EntityManager or Repository?

0 Likes

#4

The Spring Data Repository is just an abstraction layer on top of EntityManager. Powerful is not the right term when it comes to comparing them.

And in reality, what’s the point to choose to use EntityManager or Repository?

Theoretically, the EntityManager is already a Data Access Layer component providing CRUD functionality. Now, you have to ask yourself why do you use it? What functionality does a Repository provide to you that’s both useful and not available in the JPA EntityManager?

0 Likes

#5

well, i solve this problem by adding @GeneratedValue at Address’ id

0 Likes