164

私は 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 。

ありがとう。

4

7 に答える 7

232

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

アップデート:

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

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つの異なるクエリによって発生するパフォーマンスの低下を軽減します。

于 2013-03-12T11:47:24.410 に答える
40

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

}
于 2015-01-10T17:54:09.777 に答える
14

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

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

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

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

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

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

少ない作業で、Swing アプリケーションなどで OEMIV を使用できない場合に役立ちますが、リポジトリの実装でもエンティティを 1 回で初期化するのに役立つ場合があります。

最後のオプションとして、ユーティリティ クラスJpaUtilsを作成して、エンティティを特定の deph で初期化しました。

例えば:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}
于 2013-03-12T11:58:23.400 に答える
9

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

于 2013-03-12T11:51:11.580 に答える
6

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

于 2013-03-12T11:51:50.237 に答える
2

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

@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()n を使用するだけで、リスト自体をフェッチせずに、遅延初期化されたリストのサイズを取得できます。

于 2015-04-06T11:08:14.637 に答える