복붙노트

[SPRING] Hibernate / Spring : 지연 초기화에 실패 - 세션이나 세션이 닫히지 않았습니다.

SPRING

Hibernate / Spring : 지연 초기화에 실패 - 세션이나 세션이 닫히지 않았습니다.

대답의 끝까지이 스크롤 ...

기본 문제는 여러 번 묻는 질문과 같습니다. 나는 두 개의 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; }

}

참고 : 이것은 샘플 프로젝트입니다. 여기에 Lazy 페칭을 사용하고 싶습니다.

이제 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 - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
 at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
 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)
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
 at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
 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)

시도했지만 작동하지 않는 것 :

  // scope stuff
  Scope threadScope = new SimpleThreadScope();
  ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory();
  beanFactory.registerScope("request", threadScope);
  ac.refresh();
...
...
  Transaction 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();
  return users;
 }

나는 지금까지 아이디어가 정말 부족합니다. 또한 listUser 또는 listEvent를 사용하면 올바르게 작동합니다.

앞으로 단계:

Thierry 덕분에 한 걸음 더 나아갔습니다 (제 생각 엔). 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 포인터 Exception @ : user.getEvents (). size (); (daoImpl에서).

나는 그것이 콘솔의 출력이나 DB 레이아웃에서 나올 수 없다는 것을 안다.

자세한 정보는 콘솔 출력입니다 (user.getEvent () == null이고 "EVENT is NULL"입니다)에 대한 확인을했습니다.

New user...
Hibernate: insert into User (name) values (?)
Hibernate: insert into User (name) values (?)
Hibernate: insert into Event (name, user_id) values (?, ?)
Hibernate: insert into Event (name, user_id) values (?, ?)
Hibernate: insert into Event (name, user_id) values (?, ?)
List users:
Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_
1:User1
2:User2
List events:
Hibernate: select event0_.id as id1_, event0_.name as name1_, event0_.user_id as user3_1_ from Event event0_
1:Birthday1 for 1:User1
2:Birthday2 for 1:User1
3:Wedding for 2:User2
Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_
Events for users
1:User1 --
EVENT is NULL
2:User2 --
EVENT is NULL

샘플 프로젝트는 http://www.gargan.org/code/hibernate-test1.tgz (Eclipse / Maven 프로젝트)에서 얻을 수 있습니다.

솔루션 (콘솔 애플리케이션 용)

환경에 따라 실제로이 문제에 대한 두 가지 솔루션이 있습니다.

콘솔 애플리케이션의 경우 실제 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>

다른 (가능한) 솔루션

고마워요 andi

    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);
}

해결법

  1. ==============================

    1.난 당신이 최대 절전 세션 트랜잭션 방식을 사용하지 말아야한다고 생각하지만, 봄 그렇게.

    난 당신이 최대 절전 세션 트랜잭션 방식을 사용하지 말아야한다고 생각하지만, 봄 그렇게.

    이것을 spring 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입니다)

    수정 : 최대 절전 모드에서 현재 무슨 일이 일어나고 있습니다 :

    다음은 코드 개선을위한 몇 가지 사항입니다.

    위의 사항을 해결하려면 하나의 트랜잭션에서 저장을 수행하고 다른 트랜잭션에서 저장을 수행하십시오.

    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));
    ...
    

    (위의 코드 개선 사항, Set Not List, 콜렉션 입력 문제를 해결하는 것도 잊지 마십시오.)

  2. ==============================

    2.웹 응용 프로그램의 경우 web.xml에서 요청 당 세션을 수행하는 특수 필터를 선언 할 수도 있습니다.

    웹 응용 프로그램의 경우 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>
    

    그 후에 요청하는 동안 언제든지 데이터를 lazyload 할 수 있습니다.

  3. ==============================

    3.나는 비슷한 문제에 관한 힌트를 찾고있다. 나는 Thierry가 언급 한 솔루션을 시도했으나 작동하지 않았습니다. 그 후 나는이 라인들을 시도해 보았다.

    나는 비슷한 문제에 관한 힌트를 찾고있다. 나는 Thierry가 언급 한 솔루션을 시도했으나 작동하지 않았습니다. 그 후 나는이 라인들을 시도해 보았다.

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

    실제로 내가하고있는 일은 스프링 존재 관리자 / 서비스를 활용해야하는 배치 프로세스입니다. 컨텍스트를로드하고 일부 호출을 수행 한 후 "나는 lazily 컬렉션을 초기화하지 못했습니다."라는 유명한 문제를 만들었습니다. 그 3 줄은 저를 위해 그것을 해결했습니다.

  4. ==============================

    4.문제는 귀하의 DAO가 하나의 최대 절전 모드 세션을 사용하고 있지만 user.getName의 게으른로드가 세션 외부에서 일어나는 것입니다 (전혀 세션에서 또는 다른 세션에서 발생 함). 일반적으로 우리는 DAO 호출을하기 전에 최대 절전 모드 세션을 열고 모든 게으른로드가 끝날 때까지 세션을 닫지 않습니다. 웹 요청은 대개 큰 세션으로 래핑되므로 이러한 문제는 발생하지 않습니다.

    문제는 귀하의 DAO가 하나의 최대 절전 모드 세션을 사용하고 있지만 user.getName의 게으른로드가 세션 외부에서 일어나는 것입니다 (전혀 세션에서 또는 다른 세션에서 발생 함). 일반적으로 우리는 DAO 호출을하기 전에 최대 절전 모드 세션을 열고 모든 게으른로드가 끝날 때까지 세션을 닫지 않습니다. 웹 요청은 대개 큰 세션으로 래핑되므로 이러한 문제는 발생하지 않습니다.

    일반적으로 우리는 SessionWrapper에서 dao 및 lazy 호출을 감 쌉니다. 다음과 같은 것 :

    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에 삽입해야합니다.

  5. ==============================

    5.흥미 롭 군!

    흥미 롭 군!

    @ Controller의 @RequestMapping 핸들러 메소드에서 같은 문제가 발생했습니다. 간단한 해결책은 @Transactional 어노테이션을 핸들러 메소드에 추가하여 세션이 메소드 본문 실행의 전체 기간 동안 열려 있도록 유지하는 것입니다

  6. ==============================

    6.구현할 수있는 가장 쉬운 솔루션 :

    구현할 수있는 가장 쉬운 솔루션 :

    세션 범위 [@Transactional로 주석 된 API 내부]에서 다음을 수행하십시오.

    A에 지연로드 된 List 가있는 경우 List가로드되도록 API를 호출하기 만하면됩니다.

    그 API는 무엇입니까?

    크기(); List 클래스의 API입니다.

    그래서 필요한 것은 다음과 같습니다.

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

    크기를 기록하는 간단한 호출은 목록의 크기를 계산하기 전에 전체 목록을 가져 오도록합니다. 이제 예외는 없습니다!

  7. ==============================

    7.JBoss에서 우리에게 도움이 된 것은 Java Code Geeks의이 사이트에서 얻은 솔루션 # 2입니다.

    JBoss에서 우리에게 도움이 된 것은 Java Code Geeks의이 사이트에서 얻은 솔루션 # 2입니다.

    웹 :

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

    어쩌면 스프링과도 잘 어울릴 것입니다.

  8. from https://stackoverflow.com/questions/3041259/hibernate-spring-failed-to-lazily-initialize-no-session-or-session-was-closed by cc-by-sa and MIT license