Tuesday, May 09, 2006

Hibernate, Spring, open session in view

ถ้าใครใช้ Hibernate ใน web application มักจะพบเจอปัญหา LazyInitialize ทุกราย
ซึ่ง case classic ก็คือ
ใน data access layer มีการเปิด session และทำการ query ข้อมูล
จากนั้นก็ปิด session แล้ว return ข้อมูลกลับไปยัง UI
ซึ่งพอ jsp ทำการ render ข้อมูล
และเกิดไปแตะข้อมูลที่ยังไม่ได้ initialize
ก็จะเกิด LazyInitializeException ขึ้น
(แต่ถ้าเกิดการแตะข้อมูลที่ยังไม่ initialize ในขณะที่ session ยังเปิดค้างไว้อยู่,
Hibernate จะ initialize ข้อมูลให้โดยอัตโนมัต)

ทางแก้ทางหนึ่งก็คือโอนความรับผิดชอบในการเปิดปิด session
ไปให้กับ Servlet Filter เสีย

Spring ได้เตรียม Filter ที่ทำหน้าที่แบบนี้ไว้ให้แล้ว
ชื่อว่า OpenSessionInViewFilter

การ config ก็แค่เพิ่ม block นี้ลงใน web.xml
<filter> 
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate.support.OpenSessionInViewFilter</filter-class>
</filter>

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

การทำงานภายในของ OpenSessionInView ก็คือ
เมื่อเกิด request เข้ามา
มันจะทำการค้นหา SessionFactory จากใน Spring Context
โดยมันจะตั้ง Assumption ว่า มันจะค้นหา SessionFactory
จาก Bean ที่มีชื่อว่า sessionFactory
public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";

protected SessionFactory lookupSessionFactory() {

WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
return (SessionFactory) wac.getBean(getSessionFactoryBeanName(), SessionFactory.class);
}

จากนั้น มันก็จะทำการ สร้าง session และ bind session โดยเรียกใช้
TransactionSynchronizationManager.bindResource
ซึ่งจะทำการ put ค่า session นั้นเก็บไว้ใน ThreadLocal
session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory,
new SessionHolder(session));

doFilter ...

TransactionSynchronizationManager.unbindResource(sessionFactory);


ที่นี้บางคนก็จะมีคำถามว่า แล้วเวลาเรียกใช้ session หล่ะ
มันจะเกิดอะไรขึ้น
ใน spring, เรามักใช้ HibernateTemplate เข้้ามาช่วยอยู่แล้ว
ซึ่ง HibernateTemplate เอง มันจะเรียกเปิด openSession
ซึ่งภายใน จะ delegate ต่อไปยัง SessionFactoryUtils
protected Session getSession() {

.. //another case

return SessionFactoryUtils.getSession(
getSessionFactory(),
getEntityInterceptor(),
getJdbcExceptionTranslator());

}

ถ้าตามไปดู code ใน SessionFactoryUtils ก็จะเห็นว่า
มีการใช้ TransactionSynchronizationManager
เข้าไปดึงค่า SessionHolder ที่ OpenSessionInView เป็นคนสร้าง
และเก็บไว้ใน ThreadLocal
private static Session getSession(
SessionFactory sessionFactory, Interceptor entityInterceptor,
SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate)
throws DataAccessResourceFailureException, IllegalStateException {

SessionHolder sessionHolder = (SessionHolder)
TransactionSynchronizationManager.getResource(sessionFactory);

... // too much, just pretend that it not exist

return sessionHolder.getSession();

... // case for กรณีไม่ได้ใช้ filter
}


ถ้าสรุปง่ายๆ (ลอกจาก Configuring Hibernate, Spring and (MyFaces) JSF)
  • Sets up the Servlet filter that manages the Hibernate session
  • Associates a Hibernate session with the current thread assigned to the current request
  • Classes that use HibernateTemplate will access the same session by default
  • HibernateTemplate uses SessionFactoryUtils.getSession
  • SessionFactoryUtils.getSession uses existing Hibernate Session associated with the current thread

ง่ายๆเลย ไม่ต้องไปใล่ดู code ให้เสียเวลา

Related link from Roti

No comments: