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 />

ラウンド 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);

上記に対して 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が選択を出力することはありません(私のハック修正では例外です)。すっごく....どこが間違っているのですか?Hibernate はドキュメントどおりに動作すると確信していますが、それは私が何かばかげたことをしただけです。

4

2 に答える 2

0

問題は、saveAndFlush の代わりに保存するためだと思います。save はオブジェクトがデータベースに挿入されることを保証しないため、Hibernate は 3 つの挿入を行います。

編集

OK、JPAリポジトリからメソッドを保存します。主キーの存在に基づいて更新または作成するため、作成または更新とは呼ばれません-ソースコードを見てください。また、save はオブジェクトの状態に関係なくオブジェクトを保存することになっています。そのため、 saveと呼ばれるのは、それがマージであるか永続化であるかを気にせず、状態に関係なくオブジェクトを保存するためです。

あなたが扱っているもう1つのことは、オブジェクトの「平等」です。これは、JPAでは主キーレベルで実装されています。保存しようとしているオブジェクト内に一致する ID がある場合は、渡す ID => マージ、そうでない場合 => 永続化します。

したがって、あなたのロジックは(ほぼ)完全に理にかなっています。最初のケースでは、オブジェクトの ID (主キー) がまだ存在しないため、save は 3 つの挿入を行います。

2 番目のケースでは、ID を永続的なコンテキストに持ち込むだけです。最初の挿入、次に残りは更新です。

時間がないため、テストケースを完全に再現できませんでした:(しかし、私にとっては、あなたが見ているものは完全に有効です.

経験豊富なJPA開発者がこの質問についてコメントしてくれることを願っています.

于 2012-08-08T07:01:29.693 に答える
0

このコードを見ると:

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

新しく作成したオブジェクトを保存したい場合は、実際に適切なことをしていると思います。ただし、既存のものを変更するだけの場合は、update を使用する必要があります。その dnar インスタンスをセッションから取得した場合は、それが (少なくともセッション スコープ内に) 存在することがわかっているので、それに対して update を使用できます。自分で作成したオブジェクトを db に保存したい場合は、save を使用する必要があります。

「saveOrUpdate」と呼ばれる、それを処理する多額のメソッド呼び出しがあります。これは、以前はデータベースになかったsaveを呼び出してエンティティを永続化しようとするか(識別子がないことに基づいて)、updateを呼び出しますあなたのエンティティにIDがある場合。daoにsaveOrupdateがあると仮定すると、コードはこれに変更されます。これは、動作を説明する SO の良い答えです。

休止状態の saveOrUpdate の動作

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