web-dev-qa-db-ja.com

多対多の休止状態-フェッチメソッドの熱心なvs怠惰な

Hibernateは初めてです。

私はユーザーグループに多対多の関係を持っています。 3つのテーブル:ユーザー、グループ、ユーザーグループのマッピングテーブル。

エンティティ:

@Entity
@Table(name = "user")
public class User {

@Id
@Column (name = "username")
private String userName;

@Column (name = "password", nullable = false)
private String password;


@ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
@JoinTable(name="usergroup", 
            joinColumns={@JoinColumn(name="username")}, 
            inverseJoinColumns={@JoinColumn(name="groupname")})
private Set<Group> userGroups = new HashSet<Group>();

... setter and getters



@Entity
@Table(name = "group")
public class Group {

@Id
@Column(name = "groupname")
private String groupName;

@Column(name = "admin", nullable = false)
private String admin;

@ManyToMany(mappedBy = "userGroups", fetch = FetchType.EAGER)
private Set<User> users = new HashSet<User>();

... setter and getters

グループエンティティでは、フェッチメソッドEAGERを使用していることに注意してください。ここで、DAOを呼び出して、次の基準を使用してシステム内のすべてのグループを取得します。

  Criteria criteria = session.createCriteria(Group.class);
  return criteria.list();

グループの実際の数を取得する代わりに、mappginテーブル(ユーザーグループ)からすべての行を取得しています...

たとえば、ユーザーテーブルにある場合

 username password
 -----------------
 user1     user1
 user2     user2

グループテーブル内

 groupname admin
 ---------------
 grp1      user1
 grp2      user2

ユーザーグループテーブル内

 username groupname
 ------------------
 user1     grp1
 user2     grp2
 user1     grp2
 user2     grp1

結果は次のリストになります-{grp1、grp2、grp2、grp1}

{grp1、grp2}の代わりに

グループエンティティでフェッチメソッドをLAZYに変更すると、正しい結果が得られますが、休止状態では別の場所でLazyExceptionがスローされます...

どのフェッチ方法を使用する必要があり、その理由を教えてください。

ありがとう!

9
john Smith

怠惰な人は、常に_FetchType.EAGER_を直感に反して使用するように指示します。これらは、一般的にデータベースのパフォーマンスを気にせず、開発作業を楽にすることだけを気にする人々です。パフォーマンスを向上させるには、_FetchType.LAZY_を使用する必要があると言います。データベースアクセスは通常、ほとんどのアプリケーションのパフォーマンスのボトルネックであるため、少しでも役立ちます。

グループのユーザーのリストを実際に取得する必要がある場合、トランザクションセッション内からgetUsers()を呼び出す限り、そのLazyLoadingExceptionは取得されません。すべての新しいHibernateユーザー。

次のコードは、それらのグループのユーザーのリストを入力せずに、すべてのグループを取得します

_//Service Class
@Transactional
public List<Group> findAll(){
    return groupDao.findAll();
}
_

次のコードは、DAOレベルでそれらのグループのユーザーを持つすべてのグループを取得します。

_//DAO class
@SuppressWarnings("unchecked")
public List<Group> findAllWithUsers(){
    Criteria criteria = getCurrentSession().createCriteria(Group.class);

    criteria.setFetchMode("users", FetchMode.SUBSELECT);
    //Other restrictions here as required.

    return criteria.list();
}
_

編集1:このコードを提供してくれたAdrianShumに感謝します

さまざまなタイプのFetchModeの詳細については、 ここ を参照してください。

コレクションオブジェクトにアクセスするためだけに別のDAOメソッドを作成する必要がない場合は、親オブジェクトのフェッチに使用されたのと同じSessionにいる限り、Hibernate.initialize()子コレクションオブジェクトの初期化を強制するメソッド。親オブジェクトの_List<T>_に対してこれを行うことはお勧めしません。それはデータベースにかなりの負荷をかけるでしょう。

_//Service Class
@Transactional
public Group findWithUsers(UUID groupId){
    Group group = groupDao.find(groupId);

    //Forces the initialization of the collection object returned by getUsers()
    Hibernate.initialize(group.getUsers());

    return group;
}
_

上記のコードを使用しなければならない状況に遭遇したことはありませんが、比較的効率的であるはずです。 Hibernate.initialize()の詳細については、 ここ を参照してください。

これは、DAOでフェッチするのではなく、サービスレイヤーで行いました。これは、別のDAOメソッドを作成するのではなく、サービスで1つの新しいメソッドを作成するだけでよいためです。重要なことは、トランザクション内でgetUsers()呼び出しをラップしたため、Hibernateが追加のクエリを実行するために使用できるセッションが作成されることです。これは、DAOでコレクションに結合基準を書き込むことによっても実行できますが、自分で実行する必要はありませんでした。

とはいえ、最初のメソッドよりも2番目のメソッドをはるかに多く呼び出していることに気付いた場合は、フェッチタイプをEAGERに変更し、データベースに処理を任せることを検討してください。

18
JamesENL

JamesENLからの回答はほぼ正しいですが、いくつかの非常に重要な側面が欠けています。

彼が行っているのは、トランザクションがまだアクティブなときに、遅延読み込みプロキシを強制的に読み込むことです。 LazyInitializationエラーは解決されましたが、遅延読み込みは1つずつ実行されるため、パフォーマンスが大幅に低下します。基本的に、これはFetchType.EAGERと同じ結果を手動で達成するだけであり(さらに悪い方法では、JOINおよびSUBSELECT戦略を使用する可能性を逃したため)、パフォーマンスの懸念と矛盾することさえあります。

混乱を避けるために:LAZYフェッチタイプの使用は正しいです。

ただし、遅延読み込みの例外を回避するには、ほとんどの場合、リポジトリ(またはDAO?)に必要なプロパティをフェッチさせる必要があります。

最も非効率的な方法は、対応するプロパティにアクセスして遅延読み込みをトリガーすることです。いくつかの本当に大きな欠点があります:

  1. 複数レベルのデータを取得する必要がある場合に何が起こるか想像してみてください。
  2. 結果セットが大きくなる場合は、DBにn + 1個のSQLを発行しています。

より適切な方法は、1つのクエリ(またはいくつか)ですべての関連データをフェッチしようとすることです。

Spring-dataのような構文を使用した例を挙げてください(Hibernateリポジトリ/ DAOを手作りするために移植するのに十分直感的である必要があります):

interface GroupRepository {
    @Query("from Group")
    List<Group> findAll();

    @Query("from Group g left join fetch g.users")
    List<Group> findAllWithUsers();
}

結合のフェッチは、Criteria APIでも同様に簡単です(ただし、左結合しか使用できないようです)。Hibernateのドキュメントから引用されています。

List cats = session.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .setFetchMode("mate", FetchMode.EAGER)
    .setFetchMode("kittens", FetchMode.EAGER)
    .list();
2
Adrian Shum