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

时间:2012-08-08 02:09:37

标签: java spring hibernate spring-data spring-data-jpa

我正在使用:

  1. Spring 3.1
  2. Spring Data JPA 1.1
  3. Hibernate 4.1
  4. 我有问题。我有一个标准的Spring Data JPA DAO:

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

    以下是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
    }
    

    实现类的一个例子:

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

    另外,这是我对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 />
    

    第1轮 - 问题

    DAO的客户端是DnarManagerImpl类:

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

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

    1. getSession().persist(entity);
    2. getSession().merge(entity);
    3. getSession().merge(entity);
    4. Hibernate为上述输出的SQL是:

      1. INSERT INTO ...
      2. INSERT INTO ...
      3. INSERT INTO ...
      4. 第2轮 - 黑客修复

        通过一些实验,我发现我可以使用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;
        }
        

        Hibernate会话调用再次是:

        1. getSession().persist(entity);
        2. getSession().merge(entity);
        3. getSession().merge(entity);
        4. Hibernate输出的SQL现在是正确的:

          1. INSERT INTO ...
          2. 更新......
          3. 更新......
          4. 问题

            根据Hibernate docs,这就是merge()的工作原理:

            1. 如果存在当前与持久性上下文关联的相同标识符的托管实例,则将给定对象的状态复制到托管实例
            2. 如果当前没有与持久性上下文关联的托管实例,请尝试从数据库加载它,或者创建新的托管实例
            3. 返回托管实例
            4. 给定的实例不与持久性上下文相关联,它仍然是分离的并且通常被丢弃
            5. 显然,在我的例子中,第2项没有发生。 Hibernate不会尝试从数据库加载此实体。 Hibernate 从不输出一个select(我的hack修复中的异常)。 Sooo ....我哪里错了?我确信Hibernate按照文档工作,只是因为我做了一些愚蠢的事情。

2 个答案:

答案 0 :(得分:0)

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

修改

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

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

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

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

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

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

答案 1 :(得分:0)

查看此代码:

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

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

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

Hibernate saveOrUpdate behavior

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