Spring ControllerでJPAおよびHibernateとのFetchType.LAZY関連付けをフェッチする方法


146

私はPersonクラスを持っています:

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Role> roles;
    // etc
}

怠惰な多対多の関係で。

私のコントローラーには

@Controller
@RequestMapping("/person")
public class PersonController {
    @Autowired
    PersonRepository personRepository;

    @RequestMapping("/get")
    public @ResponseBody Person getPerson() {
        Person person = personRepository.findOne(1L);
        return person;
    }
}

そして、PersonRepositoryはこのガイドに従って書かれたまさにこのコードです

public interface PersonRepository extends JpaRepository<Person, Long> {
}

ただし、このコントローラーでは実際には遅延データが必要です。読み込みをトリガーするにはどうすればよいですか?

アクセスしようとすると失敗します

ロールのコレクションを遅延初期化できませんでした:no.dusken.momus.model.Person.roles、プロキシを初期化できませんでした-セッションなし

または私がやろうとしていることに応じて他の例外。

必要に応じて、私のxml-description

ありがとう。


Personパラメータを指定してオブジェクトをフェッチするクエリを作成するメソッドを記述できますか?そのQuery中に、fetch句を含め、Rolesその人のためにロードします。
SudoRahul 2013年

回答:


206

初期化するには、レイジーコレクションを明示的に呼び出す必要があります(.size()この目的のために呼び出すのが一般的です)。Hibernateにはこれ専用のメソッド(Hibernate.initialize())がありますが、JPAにはこれに相当するものはありません。もちろん、セッションがまだ利用可能な場合は、呼び出しが完了していることを確認する必要があるため、コントローラーメソッドにで注釈を付けます@Transactional。別の方法は、遅延コレクションを初期化するメソッドを公開できる、コントローラーとリポジトリーの間に中間サービス層を作成することです。

更新:

上記のソリューションは簡単ですが、データベースに対して2つの異なるクエリが発生することに注意してください(1つはユーザー用、もう1つはそのロール用)。より良いパフォーマンスを実現したい場合は、次のメソッドをSpring Data JPAリポジトリインターフェースに追加します。

public interface PersonRepository extends JpaRepository<Person, Long> {

    @Query("SELECT p FROM Person p JOIN FETCH p.roles WHERE p.id = (:id)")
    public Person findByIdAndFetchRolesEagerly(@Param("id") Long id);

}

このメソッドは、JPQLのフェッチ結合句を使用して、ロールの関連付けをデータベースへの1回のラウンドトリップで積極的にロードします。したがって、上記のソリューションの2つの異なるクエリによって発生するパフォーマンスのペナルティが軽減されます。


3
これは簡単な解決策ですが、データベースに対する2つの異なるクエリが発生することに注意してください(1つはユーザー用、もう1つはそのロール用)。より良いパフォーマンスを達成したい場合は、他の人が提案するように、JPQLまたはCriteria APIを使用して、ユーザーとその関連ロールを単一のステップで熱心にフェッチする専用メソッドを作成してみてください。
zagyi

私は今、ホセの答えの例を求めました、私は完全に理解していないことを認めなければなりません。
Matsemann

私の更新された回答で、目的のクエリメソッドの可能な解決策を確認してください。
zagyi 2013年

7
興味深いことに注意してください。単にがjoinない場合fetch、セットはで返されinitialized = falseます。したがって、セットにアクセスすると、2番目のクエリが発行されます。fetchリレーションシップが完全にロードされていることを確認し、2番目のクエリを回避するための鍵です。
FGreg 2013年

フェッチと結合の両方を実行することの問題は、結合述語基準が無視され、リストまたはマップのすべてを取得することになるようです。すべてが必要な場合はフェッチを使用し、特定のものが必要な場合は結合を使用しますが、前述のとおり、結合は空になります。これは、.LAZYロードを使用する目的を無効にします。
K.Nicholas、2015年

37

これは古い投稿ですが、@ NamedEntityGraph(Javax Persistence)と@EntityGraph(Spring Data JPA)の使用を検討してください。組み合わせが機能します。

@Entity
@Table(name = "Employee", schema = "dbo", catalog = "ARCHO")
@NamedEntityGraph(name = "employeeAuthorities",
            attributeNodes = @NamedAttributeNode("employeeGroups"))
public class EmployeeEntity implements Serializable, UserDetails {
// your props
}

そして、以下のように春のレポ

@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String>           {

    @EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
    EmployeeEntity getByUsername(String userName);

}

1
@NamedEntityGraph4.3.0バージョンの前に休止状態で実装されていませんJPA 2.1 APIの一部です。
naXa

2
@EntityGraph(attributePaths = "employeeGroups")Spring Data Repositoryで直接使用して、@NamedEntityGraph@ Entityを必要とせずにメソッドに注釈を付けることができます-少ないコードで、リポジトリを開いたときに理解しやすくなります。
Desslav Kamenov

13

いくつかのオプションがあります

  • RJが提案するように、初期化されたエンティティを返すメソッドをリポジトリに記述します。

より多くの作業、最高のパフォーマンス。

  • OpenEntityManagerInViewFilterを使用して、リクエスト全体に対してセッションを開いたままにします。

作業が少なく、通常はWeb環境で許容されます。

  • 必要に応じて、ヘルパークラスを使用してエンティティを初期化します。

作業が少なく、Swingアプリケーションなど、OEMIVが選択できない場合に役立ちますが、リポジトリの実装でもエンティティを一度に初期化するのに役立ちます。

最後のオプションとして、ユーティリティクラスJpaUtilsを記述して、エンティティをいくつかのdephで初期化しました。

例えば:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}

私のすべてのリクエストはレンダリングなどのない単純なREST呼び出しなので、トランザクションは基本的に私のリクエスト全体です。ご入力いただきありがとうございます。
Matsemann

最初の方法を教えてください。私はクエリの書き方を知っていますが、あなたの言うことをする方法はわかりません。例を見せていただけませんか?とても役に立ちます。
Matsemann

zagyiは彼の答えの例を提供しましたが、とにかく正しい方向に向けてくれてありがとう。
Matsemann

クラスがどのように呼び出されるかわかりません!完了していないソリューションは他の人の時間を浪費する
Shady Sherif 2015年

OpenEntityManagerInViewFilterを使用して、リクエスト全体に対してセッションを開いたままにします。私のエンティティのすべてのコレクションをフェッチする追加のリクエストを作成します。
Yan Khonski 2016


6

ビューのレンダリング中にセッションを開いたままにするには、OpenSessionInViewFilterが必要だと思います(ただし、あまり良い方法ではありません)。


1
私は、JSPなどを使用せず、REST-apiを作成するだけで、@ Transactionalが役立ちます。しかし、他の場合にも役立ちます。ありがとう。
Matsemann

@Matsemann私は今遅いことを知っています...しかし、コントローラでもOpenSessionInViewFilterを使用でき、応答がコンパイルされるまでセッションが存在します...
Vishwas Shashidhar '30

@Matsemannありがとう!トランザクション注釈が私にとってはトリックでした!fyi:レストクラスのスーパークラスに注釈を付けるだけでも機能します。
desperateCoder

3

春のデータ JpaRepository

Spring Data JpaRepositoryは次の2つのメソッドを定義します。

しかし、あなたのケースでは、あなたはどちらか呼び出していませんでしたgetOnefindById

Person person = personRepository.findOne(1L);

したがって、findOneメソッドはで定義したメソッドであると想定しますPersonRepository。ただし、このfindOne方法はあなたの場合にはあまり役に立ちません。Personis rolesコレクションとともにフェッチする必要があるため、findOneWithRoles代わりにメソッドを使用することをお勧めします。

カスタムSpring Dataメソッド

PersonRepositoryCustom次のようにインターフェースを定義できます。

public interface PersonRepository
    extends JpaRepository<Person, Long>, PersonRepositoryCustom { 

}

public interface PersonRepositoryCustom {
    Person findOneWithRoles(Long id);
}

そして、次のようにその実装を定義します。

public class PersonRepositoryImpl implements PersonRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Person findOneWithRoles(Long id)() {
        return entityManager.createQuery("""
            select p 
            from Person p
            left join fetch p.roles
            where p.id = :id 
            """, Person.class)
        .setParameter("id", id)
        .getSingleResult();
    }
}

それでおしまい!


自分でクエリを作成し、@ rakpan回答のEntityGraphのようなソリューションを使用しなかった理由はありますか?これは同じ結果を生み出しませんか?
Jeroen Vandevelde

EntityGraphを使用するオーバーヘッドは、JPQLクエリよりも高くなります。長期的には、クエリを記述する方がよいでしょう。
Vlad Mihalcea

オーバーヘッドについて詳しく説明していただけますか(どこから来たのですか、目立ちますか?)。原因両方が同じクエリを生成するとオーバーヘッドが高くなる理由がわかりません。
Jeroen Vandevelde

1
EntityGraphsプランはJPQLのようにキャッシュされないためです。これはパフォーマンスに大きな影響を与える可能性があります。
Vlad Mihalcea

1
丁度。時間があったらそれについて記事を書かなければならない。
Vlad Mihalcea

1

次のように同じことができます:

@Override
public FaqQuestions getFaqQuestionById(Long questionId) {
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    FaqQuestions faqQuestions = null;
    try {
        faqQuestions = (FaqQuestions) session.get(FaqQuestions.class,
                questionId);
        Hibernate.initialize(faqQuestions.getFaqAnswers());

        tx.commit();
        faqQuestions.getFaqAnswers().size();
    } finally {
        session.close();
    }
    return faqQuestions;
}

コントローラーでfaqQuestions.getFaqAnswers()。size()を使用するだけで、リスト自体を取得せずに、遅延して初期化されたリストのサイズを取得できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.