「ロールのコレクションを遅延して初期化できませんでした」Hibernate例外を解決する方法


363

私はこの問題を抱えています:

org.hibernate.LazyInitializationException:役割のコレクションの遅延初期化に失敗しました:mvc3.model.Topic.comments、セッションがない、またはセッションが閉じられました

これがモデルです:

@Entity
@Table(name = "T_TOPIC")
public class Topic {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @ManyToOne
    @JoinColumn(name="USER_ID")
    private User author;

    @Enumerated(EnumType.STRING)    
    private Tag topicTag;

    private String name;
    private String text;

    @OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
    private Collection<Comment> comments = new LinkedHashSet<Comment>();

    ...

    public Collection<Comment> getComments() {
           return comments;
    }

}

モデルを呼び出すコントローラーは次のようになります。

@Controller
@RequestMapping(value = "/topic")
public class TopicController {

    @Autowired
    private TopicService service;

    private static final Logger logger = LoggerFactory.getLogger(TopicController.class);


    @RequestMapping(value = "/details/{topicId}", method = RequestMethod.GET)
    public ModelAndView details(@PathVariable(value="topicId") int id)
    {

            Topic topicById = service.findTopicByID(id);
            Collection<Comment> commentList = topicById.getComments();

            Hashtable modelData = new Hashtable();
            modelData.put("topic", topicById);
            modelData.put("commentList", commentList);

            return new ModelAndView("/topic/details", modelData);

     }

}

jsp-pageは次のように見えます。

<%@page import="com.epam.mvc3.helpers.Utils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
      <title>View Topic</title>
</head>
<body>

<ul>
<c:forEach items="${commentList}" var="item">
<jsp:useBean id="item" type="mvc3.model.Comment"/>
<li>${item.getText()}</li>

</c:forEach>
</ul>
</body>
</html>

jspを表示すると例外が発生します。沿って、C:forEachのループ

回答:


214

Commentを取得するたびにすべてのを表示したいことがわかっている場合はTopic、フィールドマッピングを次のcommentsように変更します。

@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();

コレクションはデフォルトで遅延ロードされます。詳しく知りたい場合は、こちらをご覧ください。


35
申し訳ありませんが、レイジーロードを使用したいと思います。そこで、「LinkedHashSet」タイプを「PersistentList」に変更しました。それでも例外が発生する
ユージーン

242
これは回避策として使用できますが、問題の実際の解決策ではありません。遅延フェッチする必要がある場合はどうなりますか?
Dkyc 2014年

14
しかし、もし私たちがレイジーを望むなら、この解決策は機能せず、ほとんどの場合、レイジーのみを望む。
2015年

103
これは、スタックオーバーフローのあらゆる場所でポップアップ表示されるタイプの回答です。要するに、要点として、問題と誤解を解決します。将来の読者にとっては、自分に好意を示し、正確に怠惰で熱心に取られていることを学び、その結果を理解してください。
2016年

13
@darrengorman JPAを始めたとき、OPに関する質問を投稿しました。私はあなたが与えたのと同じ返事を受け取りました。数十万行のテストを実行するとすぐに、何が起こったと思いますか?私はそれが誤解を招くと思います。それは、ほとんど初心者が直面する問題に対して非常に単純な答えを提供し、注意深くない場合はすぐにデータベース全体がメモリにロードされるためです(そして、彼らは注意しないので、そうしません)それに注意してください):)
Ced

182

私の経験から、有名なLazyInitializationExceptionを解決するための次の方法があります。

(1)Hibernate.initializeを使用する

Hibernate.initialize(topics.getComments());

(2)JOIN FETCHを使用する

JPQLのJOIN FETCH構文を使用して、子コレクションを明示的にフェッチできます。これはEAGERフェッチに似ています。

(3)OpenSessionInViewFilterを使用する

LazyInitializationExceptionは、ビューレイヤーで頻繁に発生します。Springフレームワークを使用する場合は、OpenSessionInViewFilterを使用できます。ただし、そうすることはお勧めしません。正しく使用しないと、パフォーマンスの問題が発生する可能性があります。


5
(1)完全に機能しました。私の場合:Hibernate.initialize(registry.getVehicle()。getOwner()。getPerson()。getAddress());
Leonel Sanches da Silva

6
Hibernate.initializeがEntityManagerで動作しないようです
marionmaiden

8
これが正解です。たとえば、私のプロジェクトでは、EAGERフェッチを使用することは明示的に想定されていません。この特定のシステムで問題が発生します。
スティーブウォーターズ

魅力的ではあるが、別のケースで実装するドキュメントが不足しているようです...このソリューションを実装する方法に関するリンクまたは説明をいくつか提供していただけませんか?
Pipo

58

古い質問であることは承知していますが、お手伝いしたいと思います。必要なサービスメソッドにトランザクションアノテーションを付けることができます。この場合、findTopicByID(id)には

@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)

この注釈についての詳細はここにあります

他のソリューションについて:

fetch = FetchType.EAGER 

これは良い方法ではありません。必要な場合にのみ使用してください。

Hibernate.initialize(topics.getComments());

hibernate初期化子は、クラスをhibernateテクノロジーにバインドします。あなたが柔軟であることを目指しているなら、行くには良い方法ではありません。

それが役に立てば幸い


3
@Transactionalアノテーションは私にとってはうまくいきましたが、少なくともSpring Boot 1.4.2(Spring 4.3)では、Propagation.REQUIREDがデフォルトであることに注意してください。
ben3000

4
はい、ですが、実際に伝播パラメータを変更できることを明確にしていただければ幸いです
sarbuLopex

@TransactionalSpring専用のものではないですか?
カンパ

@Campaはいそうです。手動で処理する場合は、エンティティマネージャーから取得したトランザクション内にビジネスロジックを配置する必要があります
sarbuLopex

54

問題の原因:

デフォルトでは、Hibernateはコレクション(関係)を遅延してロードします。つまりcollection、コード(ここcommentsではTopicクラスのフィールド)でを使用すると、Hibernateがデータベースから取得します。問題は、コントローラー(JPAセッション)でコレクションを取得することです。これは、(commentsコレクションをロードしている)例外を引き起こすコード行です:

    Collection<Comment> commentList = topicById.getComments();

コントローラで「コメント」コレクション(topic.getComments())を取得し(JPA session終了したところ)、例外が発生します。また、comments(コントローラで取得するのではなく)次のようにjspファイルでコレクションを取得した場合:

<c:forEach items="topic.comments" var="item">
//some code
</c:forEach>

同じ理由で、同じ例外があります。

問題を解決する:

FetchType.EagerEntityクラスで(熱心にフェッチされたコレクション)を使用できるコレクションは2つだけであり、遅延ローディングは熱心にロードするよりも効率的であるため、問題を解決するこの方法は単にFetchTypeをeagerに変更するよりも優れていると思います。

コレクションを遅延初期化し、これを機能させる場合は、次のコードスニペットをに追加することをお勧めしますweb.xml

<filter>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

このコードが行うことはJPA session、ドキュメントの長さが長くなること、またはドキュメントに記載され"to allow for lazy loading in web views despite the original transactions already being completed."ているように、JPAセッションが少し長く開かれるため、jspファイルとコントローラークラスのコレクションを遅延読み込みできるためです。 。


7
JPSセッションが閉じられるのはなぜですか?閉じないようにする方法は?レイジーコレクションを実行する方法
2015

1
エンティティごとに2つのFetchType.Eagerコレクションの制限を定義するものは何ですか?
chrisinmtown

Spring Bootでは、「application.properties」に「spring.jpa.open-in-view = true」を追加できます
Askar

28

その理由は、遅延ロードを使用すると、セッションが閉じられるためです。

2つの解決策があります。

  1. 遅延ロードは使用しないでください。

    lazy=falseXMLまたはSet @OneToMany(fetch = FetchType.EAGER)Inアノテーションで設定します。

  2. 遅延ロードを使用します。

    lazy=trueXMLまたはSet @OneToMany(fetch = FetchType.LAZY)Inアノテーションで設定します。

    そしてOpenSessionInViewFilter filterあなたに追加web.xml

詳細POSTを参照してください。


1
...それでも両方のソリューションは良くありません。EAGERの使用は大きな問題を引き起こす可能性があることを示唆しています。OpenSessionInViewFilterの使用はアンチパターンです。
ラファエル

27
@Controller
@RequestMapping(value = "/topic")
@Transactional

これを追加すること@Transactionalでこの問題を解決します。これでセッションを開くことができると思います


なぜこれはマイナス票を受け取ったのですか?操作にトランザクションを追加すると、セッションが延長されます
Tudor Grigoriu

1
@Transactionalをコントローラーにアドすることは悪い習慣です。
Rafael

@Rafaelなぜそれが悪い習慣なのですか?
Amr Ellafy

@AmrEllafy->これは良い説明です:stackoverflow.com/a/18498834/1261162
Rafael

22

この問題は、休止状態のセッションを閉じた状態で属性にアクセスすることによって発生します。コントローラーに休止トランザクションがありません。

可能な解決策:

  1. このすべてのロジックをサービス層で実行します、コントローラーではなく(@Transactionalを使用)で実行します。これを行うには適切な場所が必要です。これは、コントローラー(この場合は、モデルをロードするためのインターフェース)ではなく、アプリのロジックの一部です。サービス層のすべての操作はトランザクションである必要があります。つまり、この行をTopicService.findTopicByIDメソッドに移動します。

    コレクションcommentList = topicById.getComments();

  2. 'lazy'ではなく 'eager'を使用してください。今、あなたは「レイジー」を使用していません..それは本当の解決策ではありません、レイジーを使用したい場合は、一時的な(非常に一時的な)回避策のように機能します。

  3. コントローラで@Transactionalを使用します。ここでは使用しないでください。サービスレイヤーとプレゼンテーションを混在させているため、良いデザインではありません。
  4. OpenSessionInViewFilterを使用すると、多くの欠点が報告され、不安定になる可能性があります。

一般的に、最善の解決策は1です。


2
それは正しくありイーガーは、Hibernateは、最初のクエリではないすべての場所を、すべてのデータを引かれることが想定の種類を取得する
ЖасуланБердибеков

他のすべてがアンチパターンであるため、最良のソリューションは1であることを大文字にする必要があります。
Rafael

19

コレクションを遅延ロードするには、アクティブなセッションが必要です。Webアプリでは、これを行う2つの方法があります。あなたは使用することができますオープンセッション内の表示は、使用パターン、インターセプタをリクエストの最初にセッションを開いて、最後にそれを閉じることを。そこには、確実な例外処理が必要になるリスクがあります。そうしないと、すべてのセッションをバインドしてアプリがハングする可能性があります。

これを処理するもう1つの方法は、コントローラーで必要なすべてのデータを収集し、セッションを閉じて、モデルにデータを詰め込むことです。MVCパターンの精神に少し近いように見えるので、私は個人的にこのアプローチを好みます。また、この方法でデータベースからエラーが発生した場合は、ビューレンダラーで発生した場合よりもはるかに適切に処理できます。このシナリオの友はHibernate.initialize(myTopic.getComments())です。また、すべてのリクエストで新しいトランザクションを作成しているため、オブジェクトをセッションに再アタッチする必要があります。そのためには、session.lock(myTopic、LockMode.NONE)を使用します。


15

この記事で説明したように、を処理する最良の方法LazyInitializationExceptionは、次のようにクエリ時にフェッチすることです。

select t
from Topic t
left join fetch t.comments

次のアンチパターンは常に避けてください。

したがって、FetchType.LAZY関連付けはクエリ時に初期化するか、セカンダリコレクションに@Transactional使用する元のスコープ内で初期化してくださいHibernate.initialize


1
Vlad Springで生成されたリポジトリのfindById()メソッドによってフェッチされたエンティティで遅延初期化されたコレクションを操作するための提案はありますか?私はクエリを記述しておらず、トランザクションはコード外です。
chrisinmtown

チェックアウトこの記事を怠惰なコレクションの初期化の詳細については。
Vlad Mihalcea

「元の@Transactionalスコープ内」の意味を明確にして
いただけ

スコープ内では、最上位のトランザクションサービスメソッド(トランザクションゲートウェイとも呼ばれます)。TrassctionInterceptorスタックトレースのを確認してください。
Vlad Mihalcea

これまでで最高の答えの1つ...これは正しいものとしてマークする必要があります。ところで、OSIVがアンチパターンであると仮定すると、最近のバージョンのSpring-Bootでデフォルトで有効にできる方法は何ですか?...多分それは悪いことではありませんか?
Rafael

10

エンティティとJavaオブジェクトのコレクションまたはリスト(たとえば、Long型)の間に関係を作成しようとしている場合は、次のようになります。

@ElementCollection(fetch = FetchType.EAGER)
    public List<Long> ids;

1
多くの場合、あなたは本当にそうしたくないのです。ここで遅延読み込みのすべての利点を
失い

EAGERの使用は専門的なソリューションではありません。
Rafael

9

最善の解決策の1つは、application.propertiesファイルに以下を追加することです 。spring.jpa.properties.hibernate.enable_lazy_load_no_trans= true


1
OPが正確に何をしているのか、副作用、パフォーマンスへの影響を教えてください。
PeS

3
遅延読み込みの最後に、関連付けが遅延読み込みされるたびに新しいセッションが分岐されるため、より多くの接続が分岐され、接続プールに少しの圧力がかかります。接続数に制限がある場合、このプロパティは使用に適さない場合があります。
sreekmatta

2
いくつかのために、それはアンチパターンとして考えられているvladmihalcea.com/...
ウリロヤ・

7

私は宣言していることが分かった@PersistenceContextとしてEXTENDEDもすると、この問題を解決します。

@PersistenceContext(type = PersistenceContextType.EXTENDED)

1
こんにちは、そのような変更に注意してください。TRANSACTIONスコープの永続コンテキストの作成は、OPの意図である遅延です。したがって問題は、あなたが無国籍になりたいかどうかです。この設定はシステムの目的に依存しており、あまり変更しないでください...熱心に。お分かりでしょうが。ここで読むstackoverflow.com/questions/2547817/...
kiedysktos

危険な。これは正解ではありません。より正確で安全なものは他にもあります。
Rafael

5

それは私が使用して解決した最近直面した問題でした

<f:attribute name="collectionType" value="java.util.ArrayList" />

ここでより詳細な説明とこれは私の日を救った。


5

リストは遅延読み込みであるため、リストは読み込まれませんでした。リストに載るための呼び出しは十分ではありません。リストを初期化するには、Hibernate.initializeで使用します。dosnt作業がリスト要素で実行される場合は、それぞれに対してHibernate.initializeを呼び出します。これは、トランザクションスコープから戻る前に行う必要があります。この投稿を見てください。
検索する -

Node n = // .. get the node
Hibernate.initialize(n); // initializes 'parent' similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session 

4

私の場合の問題を解決するには、この行が欠落していた

<tx:annotation-driven transaction-manager="myTxManager" />

アプリケーションコンテキストファイル内。

@Transactionalメソッドオーバー注釈が考慮されていませんでした。

答えが誰かを助けることを願っています


4

コントローラの@Transactionalアノテーションがありません

@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}

17
トランザクション管理は、ビジネスロジックが存在するサービスレイヤーに属していると私は主張します。
2016年

トランザクションアノテーションは欠落していません。コントローラーにはそのような注釈があってはなりません。これらの注釈はサービスレベルである必要があります。
Rafael

4

hibernate @Transactionalアノテーションを使用すると、遅延フェッチされた属性を持つデータベースからオブジェクトを取得した場合、次のようにこれらの属性をフェッチするだけでこれらを取得できます。

@Transactional
public void checkTicketSalePresence(UUID ticketUuid, UUID saleUuid) {
        Optional<Ticket> savedTicketOpt = ticketRepository.findById(ticketUuid);
        savedTicketOpt.ifPresent(ticket -> {
            Optional<Sale> saleOpt = ticket.getSales().stream().filter(sale -> sale.getUuid() == saleUuid).findFirst();
            assertThat(saleOpt).isPresent();
        });
}

ここで、Hibernateプロキシ管理トランザクションでは、ticket.getSales()明示的に要求したため、呼び出しを行うと、別のクエリを実行して売上をフェッチします。


4

あなたが持っているべき二つのことfetch = FetchType.LAZY

@Transactional

そして

Hibernate.initialize(topicById.getComments());

2

働く人々のための基準、私がことがわかりました

criteria.setFetchMode("lazily_fetched_member", FetchMode.EAGER);

必要なことはすべてやった。

コレクションの初期フェッチモードはパフォーマンスを提供するためにFetchMode.LAZYに設定されていますが、データが必要な場合は、その行を追加するだけで、完全に入力されたオブジェクトを楽しむことができます。


2

私の場合、次のコードが問題でした:

entityManager.detach(topicById);
topicById.getComments() // exception thrown

データベースから切り離され、Hibernateは必要なときにフィールドからリストを取得しなくなったためです。だから私はデタッチする前にそれを初期化します:

Hibernate.initialize(topicById.getComments());
entityManager.detach(topicById);
topicById.getComments() // works like a charm

1

その理由は、サービス内のセッションを閉じた後、コントローラーでcommentListを取得しようとしているためです。

topicById.getComments();

上記は、Hibernateセッションがアクティブな場合にのみcommentListをロードします。これは、サービスで閉じたようです。

したがって、セッションを閉じる前にcommentListを取得する必要があります。


2
はい、これは問題の説明です。回答も入力してくださいAnswer
Sarz

1

commentsモデルクラスのコレクションTopicは遅延読み込みされます。これは、fetch = FetchType.EAGER特に注釈を付けない場合のデフォルトの動作です。

findTopicByIDサービスがステートレスHibernateセッションを使用している可能性があります。ステートレスセッションには、1次レベルのキャッシュがありません。つまり、永続コンテキストはありません。後で反復しようとするとcomments、Hibernateが例外をスローします。

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed

解決策は次のとおりです。

  1. 注釈commentsを付けるfetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
  2. それでもコメントの遅延読み込みを希望する場合は、Hibernateのステートフルセッションを使用して、後で必要に応じてコメントを取得できるようにします。


1

私の場合、私は、W /マッピングbを持っていたABのように

A 持っている

@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;

DAO層、方法がで注釈付けする必要があります@Transactionalあなたが持つマッピング注釈を付けていない場合は、フェッチ・タイプ-イーガー


1

最善の解決策ではありませんが、LazyInitializationException特にSerializationこれに直面している人のために役立ちます。ここでは、遅延初期化されたプロパティとnullそれらへの設定を確認します。そのために以下のクラスを作成します

public class RepositoryUtil {
    public static final boolean isCollectionInitialized(Collection<?> collection) {
        if (collection instanceof PersistentCollection)
            return ((PersistentCollection) collection).wasInitialized();
        else 
            return true;
    }   
}

遅延初期化されたプロパティを持つエンティティクラス内に、以下に示すようなメソッドを追加します。このメソッド内にすべての遅延読み込みプロパティを追加します。

public void checkLazyIntialzation() {
    if (!RepositoryUtil.isCollectionInitialized(yourlazyproperty)) {
        yourlazyproperty= null;
    }

checkLazyIntialzation()データをロードするすべての場所でこのメソッドを呼び出します。

 YourEntity obj= entityManager.find(YourEntity.class,1L);
  obj.checkLazyIntialzation();

0

こんにちはすべての投稿はかなり遅くなることを願っています。 のHibernate.initialize(オブジェクト)

Lazy = "true"の場合

Set<myObject> set=null;
hibernateSession.open
set=hibernateSession.getMyObjects();
hibernateSession.close();

今セッションを閉じた後に「設定」にアクセスすると、例外がスローされます。

私の解決策:

Set<myObject> set=new HashSet<myObject>();
hibernateSession.open
set.addAll(hibernateSession.getMyObjects());
hibernateSession.close();

Hibernateセッションを閉じた後でも「set」にアクセスできるようになりました。


0

さらに別の方法として、TransactionTemplateを使用して遅延フェッチをラップできます。お気に入り

Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());

0

この問題は、データベースへの「接続」が閉じているときにコードが遅延JPA関係にアクセスしているために発生します(永続コンテキストはHibernate / JPAの観点からは正しい名前です)。

Spring Bootでそれを解決する簡単な方法は、サービスレイヤーを定義し、@Transactionalアノテーションを使用することです。メソッド内のこのアノテーションは、リポジトリレイヤーに伝播するトランザクションを作成し、メソッドが終了するまで永続コンテキストを開いたままにします。トランザクションメソッド内のコレクションにアクセスすると、Hibernate / JPAはデータベースからデータをフェッチします。

あなたのケースでは、あなたはあなた@TransactionalのメソッドfindTopicByID(id)で注釈を付け、そのメソッドでTopicServiceコレクションのフェッチを強制する必要があります(例えば、サイズを尋ねることによって):

    @Transactional(readOnly = true)
    public Topic findTopicById(Long id) {
        Topic topic = TopicRepository.findById(id).orElse(null);
        topic.getComments().size();
        return topic;
    }

0

遅延初期化例外を取り除くには、デタッチされたオブジェクトを操作するときに遅延コレクションを呼び出さないでください。

私の意見では、最良のアプローチはエンティティではなくDTOを使用することです。この場合、使用したいフィールドを明示的に設定できます。いつものようにそれで十分です。jacksonのようなものObjectMapper、またはhashCodeLombokによって生成されたものが暗黙的にメソッドを呼び出すことを心配する必要はありません。

特定のケースでは、@EntityGrpaphアノテーションを使用できます。これにより、エンティティー内にいてeagerもロードを行うことができますfetchType=lazy


0

この遅延初期化の問題には複数の解決策があります-

1)関連付けのフェッチタイプをLAZYからEAGERに変更しますが、パフォーマンスが低下するため、これは良い方法ではありません。

2)関連オブジェクトでFetchType.LAZYを使用し、サービスレイヤーメソッドでトランザクションアノテーションを使用して、セッションが開いたままになり、topicById.getComments()を呼び出すと、子オブジェクト(コメント)が読み込まれるようにします。

3)また、コントローラーレイヤーでエンティティの代わりにDTOオブジェクトを使用してみてください。あなたの場合、セッションはコントローラ層で閉じられます。サービスレイヤーでエンティティをDTOに変換する方が適切です。


-11

セットの代わりにリストを使用して解決しました:

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