回答の場合は、この最後までスクロールします...
基本的な問題は、何度も尋ねられたのと同じです。イベントとユーザーの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 = new SimpleThreadScope(); ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory(); beanFactory.registerScope( "request"、threadScope ); ac.refresh(); ...
... トランザクションtx =((UserDaoImpl)udao).getSession()。beginTransaction(); tx.begin(); users = udao .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);
}
休止状態のセッショントランザクションメソッドを使用するべきではないと思いますが、春にそれをさせてください。
これをあなたの春の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です)
編集:ここに休止状態の観点から何が起こっているかを示します:
...すべての保存操作で同じ...
次に、すべてのユーザーをロードします(「from Users」クエリ)
コードを強化するいくつかのポイントを次に示します。
したがって、上記の点に対処するには、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));
...
(また、上記のコード拡張ポイント、リストではなく設定、コレクションの入力に対処することを忘れないでください)
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>
その後、リクエスト中にいつでもデータを遅延ロードできます。
私はここで、同様の問題に関するヒントを探しました。 Thierryが言及した解決策を試しましたが、うまくいきませんでした。その後、これらの行を試してみましたが、うまくいきました:
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
実際、私がやっていることは、Springの既存のマネージャー/サービスを活用する必要があるバッチプロセスです。コンテキストを読み込んでいくつかの呼び出しを行った後、有名な問題「コレクションの遅延初期化に失敗しました」を発見しました。これらの3行で解決しました。
問題は、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に注入する必要があります。
面白い!
@Controllerの@RequestMappingハンドラメソッドでも同じ問題が発生しました。簡単な解決策は、@ Transactionalアノテーションをハンドラーメソッドに追加して、メソッド本体の実行中にセッションが開いたままになるようにすることでした
実装する最も簡単なソリューション:
セッションの範囲内で[@Transactionalアノテーションが付けられたAPI内]、以下を実行します。
aに遅延ロードされるList <B>があった場合、リストがロードされていることを確認するAPIを呼び出すだけです
そのAPIとは?
サイズ(); ListクラスのAPI。
だから必要なのは:
Logger.log(a.getBList.size());
サイズを記録するこの単純な呼び出しは、リストのサイズを計算する前にリスト全体を取得することを確認します。これで例外は発生しません!
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 { }
}
たぶん、春でも動作するでしょう。