web-dev-qa-db-ja.com

Hibernate / Spring:遅延初期化に失敗しました-セッションまたはセッションが閉じられていません

回答の場合は、この最後までスクロールします...

基本的な問題は、何度も尋ねられたのと同じです。イベントとユーザーの2つのPOJOを持つ単純なプログラムがあります-ユーザーは複数のイベントを持つことができます。

@Entity
@Table
public class Event {
 private Long id;
 private String name;
 private User user;

 @Column
 @Id
 @GeneratedValue
 public Long getId() {return id;}
 public void setId(Long id) { this.id = id; }

 @Column
 public String getName() {return name;}
 public void setName(String name) {this.name = name;}

 @ManyToOne
 @JoinColumn(name="user_id")
 public User getUser() {return user;}
 public void setUser(User user) {this.user = user;}

}

ユーザー:

@Entity
@Table
public class User {
 private Long id;
 private String name;
 private List<Event> events;

 @Column
 @Id
 @GeneratedValue
 public Long getId() { return id; }
 public void setId(Long id) { this.id = id; }

 @Column
 public String getName() { return name; }
 public void setName(String name) { this.name = name; }

 @OneToMany(mappedBy="user", fetch=FetchType.LAZY)
 public List<Event> getEvents() { return events; }
 public void setEvents(List<Event> events) { this.events = events; }

}

注:これはサンプルプロジェクトです。私は本当にここで遅延フェッチを使用したいです。

次に、springとhibernateを設定し、ロード用の簡単なbasic-db.xmlを用意する必要があります。


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop 
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">


 <bean id="myDataSource" class="org.Apache.commons.dbcp.BasicDataSource"
  destroy-method="close"  scope="thread">
  <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  <property name="url" value="jdbc:mysql://192.168.1.34:3306/hibernateTest" />
  <property name="username" value="root" />
  <property name="password" value="" />
  <aop:scoped-proxy/>
 </bean>

 <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
  <property name="scopes">
   <map>
    <entry key="thread">
     <bean class="org.springframework.context.support.SimpleThreadScope" />
    </entry>
   </map>
  </property>
 </bean>

 <bean id="mySessionFactory"
  class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" scope="thread">
  <property name="dataSource" ref="myDataSource" />
  <property name="annotatedClasses">
   <list>
    <value>data.model.User</value>
    <value>data.model.Event</value>
   </list>
  </property>
  <property name="hibernateProperties">
   <props>
    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
    <prop key="hibernate.show_sql">true</prop>
    <prop key="hibernate.hbm2ddl.auto">create</prop>
   </props>
  </property>
  <aop:scoped-proxy/>

 </bean>

 <bean id="myUserDAO" class="data.dao.impl.UserDaoImpl">
  <property name="sessionFactory" ref="mySessionFactory" />
 </bean>

 <bean id="myEventDAO" class="data.dao.impl.EventDaoImpl">
  <property name="sessionFactory" ref="mySessionFactory" />
 </bean>

</beans>

注:CustomScopeConfigurerとSimpleThreadScopeをいじりましたが、何も変わりませんでした。

私は単純なdao-implを持っています(userDaoを貼り付けるだけです-EventDaoはほとんど同じです-「listWith」関数を除いて:


public class UserDaoImpl implements UserDao{

 private HibernateTemplate hibernateTemplate;

 public void  setSessionFactory(SessionFactory sessionFactory) {
  this.hibernateTemplate = new HibernateTemplate(sessionFactory);

 }

 @SuppressWarnings("unchecked")
 @Override
 public List listUser() {
  return hibernateTemplate.find("from User");
 }

 @Override
 public void saveUser(User user) {
  hibernateTemplate.saveOrUpdate(user);

 }

 @Override
 public List listUserWithEvent() {

  List users = hibernateTemplate.find("from User");
  for (User user : users) {
   System.out.println("LIST : " + user.getName() + ":");
   user.getEvents().size();
  }
  return users;
 }

}

Org.hibernate.LazyInitializationExceptionを取得しています-ロールのコレクションの遅延初期化に失敗しました:data.model.User.events、user.getEvents()の行でセッションまたはセッションが閉じられていません。サイズ();

最後になりましたが、私が使用するTestクラスは次のとおりです。


public class HibernateTest {

 public static void main(String[] args) {

  ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");


  UserDao udao = (UserDao) ac.getBean("myUserDAO");
  EventDao edao = (EventDao) ac.getBean("myEventDAO");


  System.out.println("New user...");
  User user = new User();
  user.setName("test");

  Event event1 = new Event();
  event1.setName("Birthday1");
  event1.setUser(user);

  Event event2 = new Event();
  event2.setName("Birthday2");
  event2.setUser(user);

  udao.saveUser(user);
  edao.saveEvent(event1);
  edao.saveEvent(event2);

  List users = udao.listUserWithEvent();
  System.out.println("Events for users");
  for (User u : users) {

   System.out.println(u.getId() + ":" + u.getName() + " --");
   for (Event e : u.getEvents())
   {
    System.out.println("\t" + e.getId() + ":" + e.getName());
   }
  }

  ((ConfigurableApplicationContext)ac).close();
 }

}

ここに例外があります:

 1621 [main] ERROR org.hibernate.LazyInitializationException-ロールのコレクションの遅延初期化に失敗しました:data.model.User.events、セッションまたはセッションは閉じられていません
 org.hibernate.LazyInitializationException: org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.Java:380)
でロールのコレクションを遅延初期化できませんでした:data.model.User.events、セッションまたはセッションが閉じられていません
 org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.Java:372)
 at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.Java:119)
 at org.hibernate.collection .PersistentBag.size(PersistentBag.Java:248)
 at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.Java:38)
 at HibernateTest.main(HibernateTest.Java:44)
スレッド「main」org.hibernate.LazyInitiの例外alizationException:org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.Java:380)[.____でロールまたはdata.model.User.eventsのコレクションを遅延初期化できませんでした。セッションまたはセッションは閉じられませんでした。 。] at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.Java:372)
 at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.Java:119)
 at org.hibernate .collection.PersistentBag.size(PersistentBag.Java:248)
 at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.Java:38)
 at HibernateTest.main(HibernateTest.Java:44 )

試したがうまくいかなかった:

  • threadScopeを割り当てて、ビーンファクトリーを使用します(「request」または「thread」を使用しました-違いはありません):
 //スコープスタッフ
スコープthreadScope = new SimpleThreadScope(); 
 ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory(); 
 beanFactory.registerScope( "request"、threadScope ); 
 ac.refresh(); 
 ... 
  • セッションオブジェクトをdeoから取得してトランザクションをセットアップします。
 ... 
トランザクションtx =((UserDaoImpl)udao).getSession()。beginTransaction(); 
 tx.begin(); 
 users = udao .listUserWithEvent(); 
 ... 
  • listUserWithEvent()内でトランザクションを取得する
 public List listUserWithEvent(){
 SessionFactory sf = hibernateTemplate.getSessionFactory(); 
 Session s = sf.openSession(); 
 Transaction tx = s.beginTransaction (); 
 tx.begin(); 
 
 List users = hibernateTemplate.find( "from User"); 
 for(User user:users){ 
 System.out.println( "LIST:" + user.getName()+ ":"); 
 user.getEvents()。size(); 
} 
 tx.commit(); 
ユーザーを返す; 
} 

私は今では本当にアイデアを失っています。また、listUserまたはlistEventを使用しても正常に機能します。

ステップフォワード:

ティエリーのおかげで、私はさらに一歩進んだ(私は思う)。 MyTransactionクラスを作成し、そこですべての作業を行い、すべてを春から取得します。新しいメインは次のようになります。


 public static void main(String[] args) {

  ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");

  // getting dao
  UserDao udao = (UserDao) ac.getBean("myUserDAO");
  EventDao edao = (EventDao) ac.getBean("myEventDAO");

  // gettting transaction template
  TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate");

  MyTransaction mt = new MyTransaction(udao, edao);
  transactionTemplate.execute(mt);

  ((ConfigurableApplicationContext)ac).close();
 }

残念ながら、nullポインター例外@:user.getEvents()。size();があります。 (daoImpl内)。

私はそれがnullであってはならないことを知っています(コンソールの出力からもdbレイアウトからも)。

詳細はコンソール出力です(user.getEvent()== nullをチェックし、「EVENT is NULL」と出力されました):

新しいユーザー... 
 Hibernate:ユーザー(名前)値に挿入(?)
 Hibernate:ユーザー(名前)値に挿入(?)
 Hibernate :イベントへの挿入(名前、user_id)値(?、?)
 Hibernate:イベントへの挿入(name、user_id)値(?、?)
 Hibernate:イベントへの挿入(name、user_id )値(?、?)
ユーザーのリスト:
 Hibernate:user0_.idをid0_として選択し、user0_.nameをname0_としてユーザーuser0 _ 
 1:User1 
から選択します2:User2 
イベントのリスト:
 Hibernate:id1_としてevent0_.idを、name1_としてevent0_.nameを、user3_1_としてevent0_.user_idを選択し、event0 _ 
 1:Birthday1から1: User1 
 2:Birthday2 for 1:User1 
 3:Wedding for 2:User2 
 Hibernate:user0_.idをid0_として選択し、user0_.nameをname0_としてユーザーuser0 _ [.____から選択します。]ユーザーのイベント
 1:User1-
 EVENTはNULL 
 2:User2-
 EVENTはNULL 

サンプルプロジェクトは http://www.gargan.org/code/hibernate-test1.tgz (Eclipse/mavenプロジェクトです)から入手できます。

ソリューション(コンソールアプリケーション用)

この問題には、実際には環境に応じて2つの解決策があります。

コンソールアプリケーションには、actutal dbロジックをキャプチャし、トランザクションを処理するトランザクションテンプレートが必要です。


public class UserGetTransaction implements TransactionCallback{

 public List users;

 protected ApplicationContext context;

 public UserGetTransaction (ApplicationContext context) {
  this.context = context;
 }

 @Override
 public Boolean doInTransaction(TransactionStatus arg0) {
  UserDao udao = (UserDao) ac.getBean("myUserDAO");
  users = udao.listUserWithEvent();
  return null;
 }

}

これを使用するには、次を呼び出します。


 TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate");
 UserGetTransaction mt = new UserGetTransaction(context);
 transactionTemplate.execute(mt);

これが機能するためには、春のテンプレートクラスを定義する必要があります(つまり、basic-db.xmlで):

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

別の(可能な)解決策

おかげでアンディ

    PlatformTransactionManager transactionManager = (PlatformTransactionManager) applicationContext.getBean("transactionManager");
    DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED);

transactionAttribute.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
    TransactionStatus status = transactionManager.getTransaction(transactionAttribute);
    boolean success = false;
    try {
      new UserDataAccessCode().execute();
      success = true;
    } finally {
      if (success) {
        transactionManager.commit(status);
      } else {
        transactionManager.rollback(status);
      }
    }

ソリューション(サーブレット用)

サーブレットはそれほど大きな問題ではありません。サーブレットがある場合、関数の最初にトランザクションを開始してバインドし、最後に再びバインドを解除できます。

public void doGet(...) {
  SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
  Session session = SessionFactoryUtils.getSession(sessionFactory, true);
  TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));

// Your code....

  TransactionSynchronizationManager.unbindResource(sessionFactory);
}
38
Niko

休止状態のセッショントランザクションメソッドを使用するべきではないと思いますが、春にそれをさせてください。

これをあなたの春のconfに追加してください:

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="mySessionFactory" />
</bean>

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="txManager"/>
</bean>

そして、私はあなたのテストメソッドを修正して、春のトランザクションテンプレートを使用します:

public static void main(String[] args) {
    // init here (getting dao and transaction template)

    transactionTemplate.execute(new TransactionCallback() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
          // do your hibernate stuff in here : call save, list method, etc
        }
    }
}

補足として、@ OneToManyアソシエーションはデフォルトで遅延しているため、遅延注釈を付ける必要はありません。 (@ * ToManyはデフォルトでLAZY、@ * ToOneはデフォルトでEAGERです)

編集:ここに休止状態の観点から何が起こっているかを示します:

  • セッションを開く(トランザクション開始時)
  • ユーザーを保存し、セッションに保持します(キーがエンティティIDであるエンティティハッシュマップとしてセッションキャッシュを参照)
  • イベントを保存してセッションに保持する
  • 別のイベントを保存してセッションに保持する
  • ...すべての保存操作で同じ...

  • 次に、すべてのユーザーをロードします(「from Users」クエリ)

  • その時点で、休止状態にはセッション内にすでにオブジェクトがあることがわかります。そのため、リクエストから取得したオブジェクトを破棄し、セッションからオブジェクトを返します。
  • セッションのユーザーのイベントコレクションは初期化されていないため、nullになります。
  • ...

コードを強化するいくつかのポイントを次に示します。

  • モデルで、コレクションの順序付けが不要な場合は、コレクションのリストではなくセットを使用します(プライベートリストイベントではなく、プライベートセットイベント)
  • モデルにコレクションを入力します。そうしないと、hibernateはどのエンティティを取得しません(プライベートSet <Event>イベント)
  • 双方向リレーションの片側を設定し、同じトランザクションでリレーションのmappedBy側を使用する場合は、両側を設定します。 Hibernateは、次のtx(セッションがdb状態からの新しいビューであるとき)の前にそれを行いません。

したがって、上記の点に対処するには、1つのトランザクションで保存を行い、別のトランザクションで読み込みを行います。

public static void main(String[] args) {
    // init here (getting dao and transaction template)
    transactionTemplate.execute(new TransactionCallback() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
          // save here
        }
    }

    transactionTemplate.execute(new TransactionCallback() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
          // list here
        }
    }
}

または両側を設定します。

...
event1.setUser(user);
...
event2.setUser(user);
...
user.setEvents(Arrays.asList(event1,event2));
...

(また、上記のコード拡張ポイント、リストではなく設定、コレクションの入力に対処することを忘れないでください)

26
Thierry

Webアプリケーションの場合、リクエストごとのセッションを行う特別なフィルターをweb.xmlで宣言することもできます。

<filter>
    <filter-name>openSessionInViewFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>openSessionInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

その後、リクエスト中にいつでもデータを遅延ロードできます。

9
weekens

私はここで、同様の問題に関するヒントを探しました。 Thierryが言及した解決策を試しましたが、うまくいきませんでした。その後、これらの行を試してみましたが、うまくいきました:

SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));

実際、私がやっていることは、Springの既存のマネージャー/サービスを活用する必要があるバッチプロセスです。コンテキストを読み込んでいくつかの呼び出しを行った後、有名な問題「コレクションの遅延初期化に失敗しました」を発見しました。これらの3行で解決しました。

7
Fran Jiménez

問題は、daoが1つの休止状態セッションを使用しているが、user.getNameの遅延ロード(スローする場所だと想定しています)がそのセッションの外部で発生していることです。通常、hibernateセッションを開きますbefore DAO呼び出しを行い、すべての遅延読み込みが完了するまで閉じません。通常、Web要求は大きなセッションにラップされるため、これらの問題は発生しません。

通常、daoおよびlazy呼び出しをSessionWrapperでラップしました。次のようなもの:

public class SessionWrapper {
    private SessionFactory sessionFactory;
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.hibernateTemplate = new HibernateTemplate(sessionFactory);
    }
    public <T> T runLogic(Callable<T> logic) throws Exception {
        Session session = null;
        // if the session factory is already registered, don't do it again
        if (TransactionSynchronizationManager.getResource(sessionFactory) == null) {
            session = SessionFactoryUtils.getSession(sessionFactory, true);
            TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
        }

        try {
            return logic.call();
        } finally {
            // if we didn't create the session don't unregister/release it
            if (session != null) {
                TransactionSynchronizationManager.unbindResource(sessionFactory);
                SessionFactoryUtils.releaseSession(session, sessionFactory);
            }
        }
    }
}

SessionFactoryは、daoに注入されたのと同じSessionFactoryです。


この場合、listUserWithEvent本体全体をこのロジックでラップする必要があります。何かのようなもの:

public List listUserWithEvent() {
    return sessionWrapper.runLogic(new Callable<List>() {
        public List call() {
            List users = hibernateTemplate.find("from User");
            for (User user : users) {
                System.out.println("LIST : " + user.getName() + ":");
                user.getEvents().size();
            }
        }
    });
}

SessionWrapperインスタンスをdaosに注入する必要があります。

3
Gray

面白い!

@Controllerの@RequestMappingハンドラメソッドでも同じ問題が発生しました。簡単な解決策は、@ Transactionalアノテーションをハンドラーメソッドに追加して、メソッド本体の実行中にセッションが開いたままになるようにすることでした

2
mhimu

実装する最も簡単なソリューション:

セッションの範囲内で[@Transactionalアノテーションが付けられたAPI内]、以下を実行します。

aに遅延ロードされるList <B>があった場合、リストがロードされていることを確認するAPIを呼び出すだけです

そのAPIとは?

サイズ(); ListクラスのAPI。

だから必要なのは:

Logger.log(a.getBList.size());

サイズを記録するこの単純な呼び出しは、リストのサイズを計算する前にリスト全体を取得することを確認します。これで例外は発生しません!

1
ydntn

JBossでうまくいったのは、 このサイトJava Code Geeks

Web.xml:

  <filter>
      <filter-name>ConnectionFilter</filter-name>
      <filter-class>web.ConnectionFilter</filter-class>
  </filter>
  <filter-mapping>
      <filter-name>ConnectionFilter</filter-name>
      <url-pattern>/faces/*</url-pattern>
  </filter-mapping>

ConnectionFilter:

import Java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.transaction.UserTransaction;

public class ConnectionFilter implements Filter {
    @Override
    public void destroy() { }

    @Resource
    private UserTransaction utx;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            utx.begin();
            chain.doFilter(request, response);
            utx.commit();
        } catch (Exception e) { }
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException { }
}

たぶん、春でも動作するでしょう。

0
EpicPandaForce