4

我在用着:

  1. 春天 3.1
  2. 春季数据 JPA 1.1
  3. 休眠 4.1

我有个问题。我有一个标准的 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 />

第一轮 - 问题

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);

Hibernate 为上述输出的 SQL 是:

  1. 插入 ...
  2. 插入 ...
  3. 插入 ...

第 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);

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

  1. 插入 ...
  2. 更新 ...
  3. 更新 ...

问题

根据Hibernate docs,这是如何merge()工作的:

  1. 如果存在与当前与持久性上下文关联的具有相同标识符的托管实例,则将给定对象的状态复制到托管实例上
  2. 如果当前没有与持久性上下文关联的托管实例,请尝试从数据库中加载它,或者创建一个新的托管实例
  3. 返回托管实例
  4. 给定的实例没有与持久化上下文相关联,它保持分离并且通常被丢弃

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

4

2 回答 2

0

我认为问题在于您确实保存而不是 saveAndFlush。save 不保证对象被插入到数据库中,因此 Hibernate 做了 3 次插入。

编辑

好的,所以来自 JPA 存储库的保存方法:它不称为创建或更新,因为它根据主键的存在更新或创建 - 查看源代码。此外,无论对象处于何种状态,save 都应该保存一个对象,这就是为什么它被称为save的原因,因为它不关心它是合并还是持久化,它会保存对象而不管它所处的状态。

您正在处理的另一件事是对象“平等”,它在 JPA 中是在主键级别实现的。如果在您尝试存储的对象内部有一个匹配的 ID,那么您传入的 ID => 合并,否则 => 持久。

因此,您的逻辑(几乎)完全合理。在第一种情况下 save 做了 3 次插入,因为对象的 ID(主键)还不存在。

在第二种情况下,只需将 ID 带入持久上下文中即可!第一个插入,然后是更新。

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

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

于 2012-08-08T07:01:29.693 回答
0

看这段代码:

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

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

有一个重要的方法调用可以为您处理这个问题,称为“saveOrUpdate”,它将尝试通过调用之前不在 db 中的 save 来持久化一个实体(基于没有标识符)或调用 update如果您的实体有 id。然后,假设您的 dao 中有 saveOrupdate,您的代码将更改为此。这是 SO 中描述行为的一个很好的答案:

休眠保存或更新行为

@Transactional
@Override
public Dnar save(final Dnar dnar) {
   Dnar managedDnar = dnarDao.saveOrUpdate(dnar);
   return managedDnar;
}
于 2012-08-10T08:34:54.563 回答