当对象不在持久化上下文中时,Hibernate合并不查询DB

时间:2022-09-11 16:52:29

I'm using:

  1. Spring 3.1
  2. Spring Data JPA 1.1
  3. Spring Data JPA 1.1

  4. Hibernate 4.1

I have a problem. I have a standard Spring Data JPA DAO:

我有个问题。我有一个标准的Spring Data JPA DAO:

public interface DnarDao extends JpaRepository<Dnar, Long> {
  // insert Spring Data magic here!
}

Here is the Dnar code:

这是Dnar代码:

@Entity
@Table(name="req_dnar", schema=SCHEMA)
@Inheritance(strategy=JOINED)
@DiscriminatorColumn(name="form_type", discriminatorType=STRING, length=64)
public abstract class Dnar {

  @Id
  @GeneratedValue(strategy=SEQUENCE, generator="dnar_gen_seq")
  @SequenceGenerator(name="dnar_gen_seq", sequenceName="req_dnar_seq")
  @Column(name=C_DNAR_ID, nullable=false)
  private Long dnarId;

  @Column(name="form_type", nullable=false, length=64)
  @Enumerated(EnumType.STRING)
  private FormType formType;
  // remainder omitted
}

And an example of an implementation class:

以及实现类的示例:

@Entity
@Table(name="req_dnar_general_lv")
@DiscriminatorValue("GENERAL_LV")
public class GeneralLvDnar extends Dnar {
  //remainder omitted
}

Also, here is my Spring config for the Hibernate stuff:

另外,这是我对Hibernate的Spring配置:

<bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
  <property name="uRL" value="${JDBC_URL}"/>
  <property name="user" value="${USER_ID}"/>
  <property name="password" value="${PASSWORD}"/>
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="packagesToScan">
    <list><value>com/mycomp/domain</value></list>
  </property>
  <property name="jpaVendorAdapter">
     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="database" value="ORACLE" />
        <property name="generateDdl" value="true" />
        <property name="showSql" value="true" />
     </bean>
  </property>
  <property name="jpaProperties">
    <props>
      <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
      <prop key="hibernate.id.new_generator_mappings">true</prop>
    </props>
  </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<jpa:repositories base-package="com.mycomp.dao" />

<tx:annotation-driven />

Round 1 - the problem

第1轮 - 问题

The client of the DAO is the DnarManagerImpl class:

DAO的客户端是DnarManagerImpl类:

@Transactional
@Override
public Dnar save(final Dnar dnar) {

  Dnar managedDnar = dnarDao.save(dnar);
  return managedDnar;
}

The above code works but it always creates a new dnar row in the database. I've debugged the Spring Data JPA code, and it appears to be working correctly. For example if I save the same object 3 times, the Hibernate invocations are:

上面的代码可以工作,但它总是在数据库中创建一个新的dnar行。我调试了Spring Data JPA代码,它似乎工作正常。例如,如果我将同一对象保存3次,则Hibernate调用为:

  1. getSession().persist(entity);
  2. getSession().merge(entity);
  3. getSession().merge(entity);

The SQL that Hibernate outputs for the above is:

Hibernate为上述输出的SQL是:

  1. INSERT INTO ...
  2. 插入 ...

  3. INSERT INTO ...
  4. 插入 ...

  5. INSERT INTO ...
  6. 插入 ...

Round 2 - a hack fix

第2轮 - 修复黑客攻击

With a bit of experimentation, I found that I can tell the current session that this entity exists by using dnarDao.findOne(id);. And this fixes my problem:

通过一些实验,我发现我可以通过使用dnarDao.findOne(id);告诉当前会话该实体是否存在。这解决了我的问题:

@Transactional
@Override
public Dnar save(final Dnar dnar) {

  // Hack to get the Dnar in the current persistence context
  if (dnar.getDnarId() != null) {
    dnarDao.findOne(dnar.getDnarId());
  }
  Dnar managedDnar = dnarDao.save(dnar);
  return managedDnar;
}

Again the Hibernate session invocations are:

Hibernate会话调用再次是:

  1. getSession().persist(entity);
  2. getSession().merge(entity);
  3. getSession().merge(entity);

The SQL that Hibernate outputs is now correct:

Hibernate输出的SQL现在是正确的:

  1. INSERT INTO ...
  2. 插入 ...

  3. UPDATE ...
  4. UPDATE ...

The Question

According to the Hibernate docs, this is how merge() works:

根据Hibernate文档,这是merge()的工作原理:

  1. if there is a managed instance with the same identifier currently associated with the persistence context, copy the state of the given object onto the managed instance
  2. 如果存在具有当前与持久性上下文关联的相同标识符的托管实例,请将给定对象的状态复制到托管实例上

  3. if there is no managed instance currently associated with the persistence context, try to load it from the database, or create a new managed instance
  4. 如果当前没有与持久性上下文关联的托管实例,请尝试从数据库加载它,或者创建新的托管实例

  5. the managed instance is returned
  6. 将返回托管实例

  7. the given instance does not become associated with the persistence context, it remains detached and is usually discarded
  8. 给定的实例不与持久性上下文相关联,它仍然是分离的并且通常被丢弃

Clearly, in my example, Item 2 is not occurring. Hibernate is not trying to load this entity from the database. Hibernate never outputs a select (exception in my hack fix). Sooo.... Where am I going wrong? I'm sure that Hibernate does work as per the docs, it's just that I've done something foolish.

显然,在我的例子中,第2项没有发生。 Hibernate不会尝试从数据库加载此实体。 Hibernate永远不会输出一个select(我的hack修复中的异常)。 Sooo ....我哪里错了?我确信Hibernate确实按照文档工作,只是我做了一些愚蠢的事情。

2 个解决方案

#1


0  

Looking at this code:

看看这段代码:

@Transactional
@Override
public Dnar save(final Dnar dnar) {
  Dnar managedDnar = dnarDao.save(dnar);
  return managedDnar;
}

I would think you are actually doing the proper thing if you want to save a newly created object. However, if you are merely modifying an existing one, you should use update. If you obtained that dnar instance out of the session, you know it exists (in the session scope at least), so you could just use update on it. If you have created the object yourself and want to save it in the db, you should use save.

如果你想保存一个新创建的对象,我认为你实际上正在做正确的事情。但是,如果您只是修改现有的,则应使用更新。如果你从会话中获得了那个dnar实例,你知道它存在(至少在会话范围内),所以你可以在它上面使用update。如果您自己创建了对象并希望将其保存在数据库中,则应使用save。

There is a hefty method call that takes care of that for you, called 'saveOrUpdate' which is going to try to either persist an entity by calling save that was not in db previously (based on the absence of the identifier) or will call update if your entity has an id. Your code will then change to this, assuming you have saveOrupdate in your dao. Here is a nice answer in SO that describes the behavior:

有一个重要的方法调用,为你处理,称为'saveOrUpdate',它将尝试通过调用之前不在db中的save来保持实体(基于缺少标识符)或将调用更新如果您的实体有id。假设你的dao中有saveOrupdate,那么你的代码将改为this。这是一个很好的答案,在SO中描述了行为:

Hibernate saveOrUpdate behavior

Hibernate saveOrUpdate行为

@Transactional
@Override
public Dnar save(final Dnar dnar) {
   Dnar managedDnar = dnarDao.saveOrUpdate(dnar);
   return managedDnar;
}

#2


0  

I think that the problem is because you do save instead of saveAndFlush. save does not guarantee that the object is inserted into the database, thus Hibernate does 3 inserts.

我认为问题是因为你保存而不是saveAndFlush。 save不保证将对象插入到数据库中,因此Hibernate会进行3次插入。

EDIT.

Ok, so save method from the JPA Repository : it is NOT called create or update, because it EITHER updates or creates based on the presence of the primary key - look at the source code. Also save is supposed to save an Object no matter the state it is in, that is why it is called save because it does not care if it is a merge or persist, it going to save the object NO MATTER the state it is in.

好的,所以从JPA Repository中保存方法:它不称为创建或更新,因为它根据主键的存在更新或创建 - 查看源代码。另外,save也可以保存一个Object,无论它处于什么状态,这就是为什么它被称为save,因为它不关心它是合并还是持久,它会保存对象NO MATTER它所处的状态。

One more thing that you are dealing with is object "equality", which in JPA is implemented at the primary key level. If inside the object that you are trying to store you have a ID that matches then one you are passing in => merge, else => persist.

您正在处理的另一件事是对象“相等”,它在JPA中是在主键级实现的。如果你想要存储的对象内部有一个匹配的ID,那么你传入的ID => merge,else => persist。

Thus your logic makes (almost) perfect sense. In the first case save does 3 inserts, because the ID(primary key) of the object does NOT exist yet.

因此,你的逻辑(几乎)完美无缺。在第一种情况下,save执行3次插入,因为对象的ID(主键)尚不存在。

In the second case by bringing the ID in the persistent context you do only! the first insert, then the rest are updates.

在第二种情况下,通过在持久化上下文中引入ID,您只需执行!第一个插入,然后其余是更新。

I could not fully reproduce your test-case because I do not have the time for it :(, but, to me, the thing that you are seeing IS PERFECTLY valid.

我无法完全重现你的测试用例,因为我没有时间:(但是,对我来说,你看到的东西是完全有效的。

I wish some experienced JPA developer would comment too on this question.

我希望一些有经验的JPA开发人员也会对这个问题发表评论。

#1


0  

Looking at this code:

看看这段代码:

@Transactional
@Override
public Dnar save(final Dnar dnar) {
  Dnar managedDnar = dnarDao.save(dnar);
  return managedDnar;
}

I would think you are actually doing the proper thing if you want to save a newly created object. However, if you are merely modifying an existing one, you should use update. If you obtained that dnar instance out of the session, you know it exists (in the session scope at least), so you could just use update on it. If you have created the object yourself and want to save it in the db, you should use save.

如果你想保存一个新创建的对象,我认为你实际上正在做正确的事情。但是,如果您只是修改现有的,则应使用更新。如果你从会话中获得了那个dnar实例,你知道它存在(至少在会话范围内),所以你可以在它上面使用update。如果您自己创建了对象并希望将其保存在数据库中,则应使用save。

There is a hefty method call that takes care of that for you, called 'saveOrUpdate' which is going to try to either persist an entity by calling save that was not in db previously (based on the absence of the identifier) or will call update if your entity has an id. Your code will then change to this, assuming you have saveOrupdate in your dao. Here is a nice answer in SO that describes the behavior:

有一个重要的方法调用,为你处理,称为'saveOrUpdate',它将尝试通过调用之前不在db中的save来保持实体(基于缺少标识符)或将调用更新如果您的实体有id。假设你的dao中有saveOrupdate,那么你的代码将改为this。这是一个很好的答案,在SO中描述了行为:

Hibernate saveOrUpdate behavior

Hibernate saveOrUpdate行为

@Transactional
@Override
public Dnar save(final Dnar dnar) {
   Dnar managedDnar = dnarDao.saveOrUpdate(dnar);
   return managedDnar;
}

#2


0  

I think that the problem is because you do save instead of saveAndFlush. save does not guarantee that the object is inserted into the database, thus Hibernate does 3 inserts.

我认为问题是因为你保存而不是saveAndFlush。 save不保证将对象插入到数据库中,因此Hibernate会进行3次插入。

EDIT.

Ok, so save method from the JPA Repository : it is NOT called create or update, because it EITHER updates or creates based on the presence of the primary key - look at the source code. Also save is supposed to save an Object no matter the state it is in, that is why it is called save because it does not care if it is a merge or persist, it going to save the object NO MATTER the state it is in.

好的,所以从JPA Repository中保存方法:它不称为创建或更新,因为它根据主键的存在更新或创建 - 查看源代码。另外,save也可以保存一个Object,无论它处于什么状态,这就是为什么它被称为save,因为它不关心它是合并还是持久,它会保存对象NO MATTER它所处的状态。

One more thing that you are dealing with is object "equality", which in JPA is implemented at the primary key level. If inside the object that you are trying to store you have a ID that matches then one you are passing in => merge, else => persist.

您正在处理的另一件事是对象“相等”,它在JPA中是在主键级实现的。如果你想要存储的对象内部有一个匹配的ID,那么你传入的ID => merge,else => persist。

Thus your logic makes (almost) perfect sense. In the first case save does 3 inserts, because the ID(primary key) of the object does NOT exist yet.

因此,你的逻辑(几乎)完美无缺。在第一种情况下,save执行3次插入,因为对象的ID(主键)尚不存在。

In the second case by bringing the ID in the persistent context you do only! the first insert, then the rest are updates.

在第二种情况下,通过在持久化上下文中引入ID,您只需执行!第一个插入,然后其余是更新。

I could not fully reproduce your test-case because I do not have the time for it :(, but, to me, the thing that you are seeing IS PERFECTLY valid.

我无法完全重现你的测试用例,因为我没有时间:(但是,对我来说,你看到的东西是完全有效的。

I wish some experienced JPA developer would comment too on this question.

我希望一些有经验的JPA开发人员也会对这个问题发表评论。