Friday, August 19, 2005

ใช้ Spring AOP ในการ initialize Hibernate Lazy Properties

ปัญหาของการใช้ Hibernate ตัวหนึ่งที่เชื่อว่าทุกคนคงจะเคยเจอ (โดยเฉพาะพวกที่
พัฒนา Web Application) ก็คือ ประเด็นเรื่อง LazyInitializationException
ซึ่งเกิดจากการพยายาม access Lazy Properties หลังจากที่ได้มีการ
close Session ไปแล้ว

สภาพปัญหา
สมมติว่าเราออกแบบ Class Customer กับ Class CustomerHistory
โดย CustomerHistory ต้องการจะไว้ใช้เก็บประวัติการซื้อของลูกค้า



เวลาเรา Mapping เข้ากับ Hibernate
Navigation จาก customer ไปยัง CustomerHistory จะ config ได้ดังนี้
<set
name="customerHistory"
lazy="false"
cascade="none"
sort="unsorted"
order-by="invoiceDate"
>

<key
column="customer_id"
>
</key>
<one-to-many
class="domain.CustomerHistory"
/>

</set>

จะเห็นว่าใน code ตัวอย่างนี้มี lazy="false"

เมื่อไรก็ตามที่สั่ง query Customer นี้เช่น
List results = session.createQuery("from Customer as c").list();

Note:สำหรับผู้ที่สงสัยว่าทำไมไม่เขียน session.find("from Customer...")
เพราะว่าใน Hibernate3 Class Session ไม่มี method find ให้ใช้แล้ว


Hibernate ก็จะจัดการ query โดยการ
select * from Customer
จากนั้น แต่ละ Customer ที่ได้มา
select * from customer_history

ซึ่งจะเห็นว่าถ้าเอาไปใช้งานในหน้าจอประเภทสอบถามที่ดึง customer ทีเดียวหลายๆคน
หรือหน้าจอรายงาน ก็จะเกิดปัญหาในด้าน Performance Issue แน่นอน

ดังนั้นเรามักจะกำหนดให้ lazy="true" เสียมากกว่า
ซึ่งเมื่อกำหนดเช่นนี้แล้ว Hibernate จะยังไม่ select ข้อมูลจาก customer_history
จนกว่าจะมีการ access จริงเกิดขึ้น (เช่นมีการเรียกใช้ customer.getCustomerHistory().size())
ซึ่งการ set lazy="true" นี่แหละที่เป็นที่มาของปัญหา
LazyInitializeException

ดังนั้นในกรณีที่เรารู้ว่า Object เราจะถูกใช้หลังจากที่ Session close แล้ว
และมีการ access lazy Properties ด้วย. Hibernate เปิดโอกาสให้เรา
initialize Lazy Property ด้วยคำสั่ง Hibernate.initialize(..)

ในแง่การปฎิบัติแล้ว วิธี Hibernate.initialize ก็สามารถใช้การได้ดี
แต่ในแง่ความสวยงามของ Design แล้วดูมันผิดที่ิผิดทางอยู่
สุดท้ายพวกคนที่ใช้ Spring ก็เลยมี Idea ที่จะแก้ไขปัญหานี้ด้วยการใช้
Spring AOP เข้ามาช่วย ​(เพื่อให้การกำหนด initialize property
มีลักษณะเป็น Declarative มากขึ้น)

ลองมาดูวิธีการที่เขาใช้
เริ่มจากสถานะการณ์ปกติที่เราใช้ Spring ก่อน

define Service Layer ด้วย Interface
public interface ICustomerServices {    
public List findAllCustomer();
}


ใน config file เรา declare CustomerService โดยใช้ Proxy
เพื่อจะได้ใส่ hibernate interceptor ที่ทำหน้าที่ open, close Session
ให้เราอัตโนมัติ
<bean id="myHibernateInterceptor" 
class="org.springframework.orm.hibernate3.HibernateInterceptor">
<property name="sessionFactory">
<ref bean="mySessionFactory"/>
</property>
</bean>

<bean id="customerServiceTarget" class="service.CustomerService">
<property name="sessionFactory">
<ref bean="mySessionFactory"/>
</property>
</bean>

<bean id="customerService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>service.ICustomerServices</value>
</property>
<property name="interceptorNames">
<list>
<value>myHibernateInterceptor</value>
<value>customerServiceTarget</value>
</list>
</property>
</bean>


เมื่อไรก็ตามที่เราต้องการ Initialize Properties เราก็สามารถ
แทรก Interceptor เข้าไปได้ดังนี้
<bean id="cust-service-findall_initialize" 
class="org.springmodules.aop.framework.TouchingNameMatchMethodAdvisor">
<property name="mappedNames">
<value>findAllCustomer</value>
</property>
<property name="advice.ognl">
<list>
<value>#returned.{customerHistory.size}</value>
</list>
</property>
</bean>

และทำการแทรก Interceptor เข้าไปใน proxy ดังนี้
<bean id="customerService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>service.ICustomerServices</value>
</property>
<property name="interceptorNames">
<list>
<value>myHibernateInterceptor</value>
<value>cust-service-findall_initialize</value>
<value>customerServiceTarget</value>
</list>
</property>
</bean>

Class TouchingNameMatchMethodAdvisor จะคอยดักว่ามีการ เรียกใช้ method
ที่กำหนดหรือไม่ ถ้ามีการเรียกใช้ ก็จะใช้ OGNL Expression ที่เรากำหนด
เข้าไป access property

สรุป
ในแง่ Design การยกเอา Initilize code ออกมาเป็น Declarative ดู
มี style กว่า แต่ในแง่ปฏิบัติและความยุ่งยากแล้ว อาจจะไม่คุ้มกันก็ได้
เพราะถ้าเขียนเป็น code แล้วจะมี code แค่เพียง 1 บรรทัด แต่ถ้ายกออกมา
เป็น declarative แล้วต้องเขียน code ขึ้นอีก 12 บรรทัด

Note: Class TouchingNameMatchMethodAdvisor อยู่ใน Project SpringModules
และยังไม่รวมอยู่ใน release build ของ SpringModules



อ่านเพิ่มเติม

Related link from Roti

No comments: