web-dev-qa-db-ja.com

Hibernate / JPAからレイジーフェッチしたアイテムを私のコントローラに読み込む方法

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> {
}

しかし、このコントローラーでは、実際にはlazy-dataが必要です。ロードを開始するにはどうすればよいですか?

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

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

または私が試した内容によってはその他の例外があります。

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

ありがとう。

126
Matsemann

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

更新:

上記の解決方法は簡単ですが、データベースに対して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の fetch join 句を使用して、データベースへの1回のラウンドトリップでロールの関連付けを積極的にロードします。したがって、上記のソリューションでは2つの異なるクエリによるパフォーマンスの低下が軽減されます。

182
zagyi

これは古い投稿ですが、@ 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);

}
31
rakpan

いくつかの選択肢があります

  • R.Jが提案したように、初期化されたエンティティを返すメソッドをrepositoryに書きます。

もっと仕事、最高のパフォーマンス。

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

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

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

作業が少なく、SwingアプリケーションのようにOEMIVが最適ではない場合に便利ですが、エンティティの初期化にリポジトリの実装にも役立つことがあります。

最後のオプションとして、エンティティクラスをある程度初期化するためのユーティリティクラス JpaUtils を書きました。

例えば:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

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

トランザクション内でのみ遅延ロードすることができます。それで、あなたはあなたのリポジトリのコレクションにアクセスすることができます - それはトランザクションを持っています - あるいは何 私は通常get with associationであるか、またはfetchmodeをeagerに設定します。

6
NimChimpsky

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

6

あなたはこのように同じことをすることができます:

@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()を使用するだけで、リスト自体を取得せずに、遅延初期化リストの場合はサイズが取得されます。

0
neel4soft