5

以下の JPA2/Hibernate4 プロキシの動作を観察しようとしましたが、

// 遅延読み込みによる循環エンティティ:

@Entity
public class Employee {

 @Id@Generated
 int id;
 String name;
 @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
 Employee boss;

 public String toString() {
  return id + "|" + name + "|" + boss;
 }

 //getters and setters ...

}

// エンティティを永続化します:

// Outer entity:
Employee employee = new Employee();
employee.setName("engineer");
// Inner entity:
Employee boss = new Employee();
boss.setName("manager");
employee.setBoss(boss);

entityTransaction.begin();
entityManager.persist(employee);
entityTransaction.commit();
System.out.println(employee);

// 出力:

Hibernate: insert into Employee (id, boss_id, name) values (default, ?, ?)
Hibernate: insert into Employee (id, boss_id, name) values (default, ?, ?)

2|engineer|1|manager|null

// 外側のエンティティをロード:

String queryString = "select e from Employee e where e.id=" + employee.getId();
Query query = entityManager.createQuery(queryString);
Object loadedEmployee = query.getSingleResult();
System.out.println(loadedEmployee.getClass().getSimpleName());

// 出力:

Hibernate: select employee0_.id as id2_, employee0_.boss_id as boss3_2_, employee0_.name as name2_ from Employee employee0_ where employee0_.id=2 limit ?

Employee

驚いたことに、上記のロードされた外部エンティティはまだプレーンなものですがHibernate proxylazy loading. ここで何かを見逃した可能性があるので、どうすれば正しくなりますか?シンプルで具体的な例は大歓迎です!

@編集

私からの回答によると、@kostja以下のコードを適応させてSEモードでデバッグしたところ、LazyInitializationException生成もboss propertyプロキシもされませんでした。さらにヒントはありますか?

コード ウィンドウ

デバッグ ウィンドウ

@EDIT 2

最後に、からの答え@kostjaが間違いなく素晴らしいことを確認します。

私はEEモードでテストしたので、proxied boss property以下が観察されました。

//LazyInitializationExceptionスロー:

public Employee retrieve(int id) {
 Employee employee = entityManager.find(Employee.class, id);
 // access to the proxied boss property outside of persistence/transaction ctx
 Employee boss = employee.getBoss();
 System.out.println(boss instanceof HibernateProxy);
 System.out.println(boss.getClass().getSimpleName());
 return boss;
}

// 配置後の緑色のライトSpring Tx:

@Transactional
public Employee retrieve(int id) ...

// 出力:

true
Employee_$$_javassist_0

また、20.1.4 を参照できます。Hibernate ドキュメントからのコレクションとプロキシの初期化。

4

1 に答える 1

8

これは予期される JPA の動作です。クエリのエンティティがプロキシされる理由はありません。これはクエリの通常の結果です。ただし、このエンティティのbossプロパティはプロキシである必要があります。ただし、尋ねられてもわかりません。管理対象エンティティの遅延ロードされたプロパティで操作を実行すると、フェッチがトリガーされます。

そのため、トランザクションの外で Boss プロパティにアクセスする必要があります。フェッチされていない場合は、LazyInitializationException.

どのように処理するかは、EntityManagerとの種類によって異なりPersistenceContextます。

  • JPA 2.0 以降でのみ機能します - プロパティを呼び出しem.detach(loadedEmployee)てからアクセスしbossます。

JPA 1 の場合:

  • Java EE 環境の場合は、メソッドを でマークして@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)、トランザクションを中断します。

  • ユーザートランザクションのある SE 環境では、プロパティtransaction.commit()にアクセスする前に呼び出しbossます。

  • PersistenceContextトランザクションより長く存続する EXTENDED を使用する場合は、 を呼び出しますem.clear()

EIDT: 例外が発生しない理由はFetchType.LAZY、JPA プロバイダーの単なるヒントであると思われるため、プロパティが遅延して読み込まれる保証はありません。それとは反対に、FetchType.EAGER熱心なフェッチを保証します。JPA プロバイダーは熱心にロードすることを選択したと思います。

例を再現しましたが、少し異なりますが、再現可能にLazyInitializationExceptionログ ステートメントを取得しています。このテストは、Hibernate 4.0.1 上の JPA 2.0 を使用して JBoss 7.1.1 で実行される Arquillian テストです。

@RunWith(Arquillian.class)
public class CircularEmployeeTest {
    @Deployment
    public static Archive<?> createTestArchive() {
        return ShrinkWrap
                .create(WebArchive.class, "test.war")
                .addClasses(Employee.class, Resources.class)
                .addAsResource("META-INF/persistence.xml",
                        "META-INF/persistence.xml")
                .addAsResource("testSeeds/2CircularEmployees.sql", "import.sql")
                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
    }

    @PersistenceContext
    EntityManager em;

    @Inject
    UserTransaction tx;

    @Inject
    Logger log;

    @Test
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void testConfirmLazyLoading() throws Exception {
        String query = "SELECT e FROM Employee e WHERE e.id = 1";

        tx.begin();
        Employee employee = em.createQuery(query,
                Employee.class).getSingleResult();
        tx.commit();
        log.info("retrieving the boss: {}", employee.getBoss());
    }
}
于 2012-11-14T16:05:42.560 に答える