關(guān)于Spring和Hibernate整合后的懶加載和DAO模式應(yīng)用,尤其介紹了其中的Hibernate懶加載在應(yīng)用Spring的情況下在各個層次實現(xiàn)的情況。暫時是英文的,有時間試著翻譯一下!~
Hibernate and Lazy Initialization
Hibernate object relational mapping offers both lazy and non-lazy
modes of object initialization. Non-lazy initialization retrieves an
object and all of its related objects at load time. This can result in
hundreds if not thousands of select statements when retrieving one
entity. The problem is compounded when bi-directional relationships are
used, often causing entire databases to be loaded during the initial
request. Of course one could tediously examine each object relationship
and manually remove those most costly, but in the end, we may be losing
the ease of use benefit sought in using the ORM tool.
The obvious solution is to employ the lazy loading mechanism
provided by hibernate. This initialization strategy only loads an
object's one-to-many and many-to-many relationships when these fields
are accessed. The scenario is practically transparent to the developer
and a minimum amount of database requests are made, resulting in major
performance gains. One drawback to this technique is that lazy loading
requires the Hibernate session to remain open while the data object is
in use. This causes a major problem when trying to abstract the
persistence layer via the Data Access Object pattern. In order to fully
abstract the persistence mechanism, all database logic, including
opening and closing sessions, must not be performed in the application
layer. Most often, this logic is concealed behind the DAO
implementation classes which implement interface stubs. The quick and
dirty solution is to forget the DAO pattern and include database
connection logic in the application layer. This works for small
applications but in large systems this can prove to be a major design
flaw, hindering application extensibility.
Being Lazy in the Web Layer
Fortunately for us, the Spring Framework has developed an out of box
web solution for using the DAO pattern in combination with Hibernate
lazy loading. For anyone not familiar with using the Spring Framework in combination with Hibernate, I will not go into the details here, but I encourage you to read Hibernate Data Access with the Spring Framework. In the case of a web application, Spring comes with both the OpenSessionInViewFilter and the OpenSessionInViewInterceptor.
One can use either one interchangeably as both serve the same function.
The only difference between the two is the interceptor runs within the
Spring container and is configured within the web application context
while the Filter runs in front of Spring and is configured within the
web.xml. Regardless of which one is used, they both open the hibernate
session during the request binding this session to the current thread.
Once bound to the thread, the open hibernate session can transparently
be used within the DAO implementation classes. The session will remain
open for the view allowing lazy access to the database value objects.
Once the view logic is complete, the hibernate session is closed either
in the Filter doFilter method or the Interceptor postHandle method.
Below is an example of the configuration of each component:
Interceptor Configuration
1 <beans>
2 <bean id="urlMapping"
3 class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
4 <property name="interceptors">
5 <list>
6 <ref bean="openSessionInViewInterceptor"/>
7 </list>
8 </property>
9 <property name="mappings">
10 
11 </bean>
12 
13 <bean name="openSessionInViewInterceptor"
14 class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
15 <property name="sessionFactory"><ref bean="sessionFactory"/></property>
16 </bean>
17 </beans>
Filter Configuration
1 <web-app>
2 <filter>
3 <filter-name>hibernateFilter</filter-name>
4 <filter-class>
5 org.springframework.orm.hibernate.support.OpenSessionInViewFilter
6 </filter-class>
7 </filter>
8
9 <filter-mapping>
10 <filter-name>hibernateFilter</filter-name>
11 <url-pattern>*.spring</url-pattern>
12 </filter-mapping>
13 
14 </web-app>
Implementing the Hibernate DAO's to use the open session is simple.
In fact, if you are already using the Spring Framework to implement
your Hibernate DAO's, most likely you will not have to change a thing.
The DAO's must access Hibernate through the convenient
HibernateTemplate utility, which makes database access a piece of cake.
Below is an example DAO.
Example DAO
1 public class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO {
2
3 public Product getProduct(Integer productId) {
4 return (Product)getHibernateTemplate().load(Product.class, productId);
5 }
6
7 public Integer saveProduct(Product product) {
8 return (Integer) getHibernateTemplate().save(product);
9 }
10
11 public void updateProduct(Product product) {
12 getHibernateTemplate().update(product);
13 }
14 }
Being Lazy in the Business Layer
Even outside the view, the Spring Framework makes it easy to use
lazy load initialization, through the AOP interceptor
HibernateInterceptor. The hibernate interceptor transparently
intercepts calls to any business object configured in the Spring
application context, opening a hibernate session before the call, and
closing the session afterward. Let's run through a quick example.
Suppose we have an interface BusinessObject:
1 public interface BusinessObject {
2
3 public void doSomethingThatInvolvesDaos();
4 }
The class BusinessObjectImpl implements BusinessObject:
1 public class BusinessObjectImpl implements BusinessObject {
2
3 public void doSomethingThatInvolvesDaos() {
4 // lots of logic that calls
5 // DAO classes Which access
6 // data objects lazily
7 }
8 }
Through some configurations in the Spring application context, we
can instruct the HibernateInterceptor to intercept calls to the
BusinessObjectImpl allowing it's methods to lazily access data objects.
Take a look at the fragment below:
1 <beans>
2 <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
3 <property name="sessionFactory">
4 <ref bean="sessionFactory"/>
5 </property>
6 </bean>
7 <bean id="businessObjectTarget" class="com.acompany.BusinessObjectImpl">
8 <property name="someDAO"><ref bean="someDAO"/></property>
9 </bean>
10 <bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
11 <property name="target"><ref bean="businessObjectTarget"/></property>
12 <property name="proxyInterfaces">
13 <value>com.acompany.BusinessObject</value>
14 </property>
15 <property name="interceptorNames">
16 <list>
17 <value>hibernateInterceptor</value>
18 </list>
19 </property>
20 </bean>
21 </beans>
When the businessObject bean is referenced, the HibernateInterceptor
opens a hibernate session and passes the call onto the
BusinessObjectImpl. When the BusinessObjectImpl has finished executing,
the HibernateInterceptor transparently closes the session. The
application code has no knowledge of any persistence logic, yet it is
still able to lazily access data objects.
Being Lazy in your Unit Tests
Last but not least, we'll need the ability to test our lazy
application from J-Unit. This is easily done by overriding the setUp
and tearDown methods of the TestCase class. I prefer to keep this code
in a convenient abstract TestCase class for all of my tests to extend.
1 public abstract class MyLazyTestCase extends TestCase {
2
3 public void setUp() throws Exception {
4 super.setUp();
5 SessionFactory sessionFactory = (SessionFactory) getBean("sessionFactory");
6 Session s = sessionFactory.openSession();
7 TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));
8
9 }
10
11 protected Object getBean(String beanName) {
12 //Code to get objects from Spring application context
13 }
14
15 public void tearDown() throws Exception {
16 super.tearDown();
17 SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.
18 getResource(sessionFactory);
19 Session s = holder.getSession();
20 s.flush();
21 TransactionSynchronizationManager.unbindResource(sessionFactory);
22 SessionFactoryUtils.closeSessionIfNecessary(s, sessionFactory);
23 }
24}
Traceback:
http://jroller.com/kbaum/entry/orm_lazy_initialization_with_dao
在這里我試著翻譯一下,有部分地方改變了一下,希望看到的朋友多多指正。
Hibernate和延遲裝載
Hibernate ORM(關(guān)系-對象映射)針對對象的初始化分別提供了延遲和非延遲模式。非延遲裝載將在讀取時獲得對象和其所關(guān)聯(lián)的所有對象,這樣當(dāng)獲取一個實體對象時,將會導(dǎo)致成千上萬的select語句產(chǎn)生。這個問題在使用雙向關(guān)聯(lián)的時候?qū)⒏踊靵y,經(jīng)常會導(dǎo)致在初始化請求就需要讀取整個數(shù)據(jù)庫。當(dāng)然,某個人可以去繁重地檢查每一個對象之間的關(guān)系,并且花費更多的代價去手動地移除它們。但是最終,我們可能已經(jīng)失去了使用ORM工具換來的易用性。
因此,顯而易見的解決方案是使用Hibernate提供的延遲加載機制。這種初始化策略僅在當(dāng)那些字段訪問時讀取這個對象的一對多和多對多關(guān)系。這種情況對于開發(fā)者實際上是透明的,并且只創(chuàng)建一小部分?jǐn)?shù)量的數(shù)據(jù)庫請求,這樣就可以很大地提升性能。這種技術(shù)的一個缺點就是當(dāng)這個數(shù)據(jù)對象在使用時,延遲加載就需要Hibernate session一直打開。這就導(dǎo)致了一個主要問題:當(dāng)嘗試通過DAO(數(shù)據(jù)訪問對象)模式來抽象化持久層時,為了完整地抽象持久化機制,所有的數(shù)據(jù)庫邏輯,包括打開和關(guān)閉session,都將不會被應(yīng)用層調(diào)用執(zhí)行。最經(jīng)常的情況,這些邏輯被后面的實現(xiàn)某些DAO接口的類來關(guān)注。但是,這個快速但不好的解決方案違反了DAO模式,并且在應(yīng)用層包含了數(shù)據(jù)庫連接邏輯。這樣做對于小型應(yīng)用可能適合,但是在大型系統(tǒng)中,這會產(chǎn)生一個重大的設(shè)計失誤,阻止了應(yīng)用的擴(kuò)展。
在WEB層上延遲
我們是幸運的,Spring框架開發(fā)了一個開箱即用(out of box)的WEB解決方案,用來在組合Hibernate延遲加載時使用DAO模式。對于不熟悉使用Spring如何組合Hibernate的人,在這里我不會進(jìn)入詳細(xì)的細(xì)節(jié),但是我鼓勵你去閱讀使用Spring框架進(jìn)行Hibernate數(shù)據(jù)訪問《Hibernate Data Access
with the Spring Framework》。就一個WEB應(yīng)用來說,Spring提供了OpenSessionInViewFilter和OpenSessionInViewInterceptor,一個可替換另一個使用而得到同樣的功能。兩者的唯一區(qū)別就是攔截器(Interceptor)在Spring容器中運行并且在WEB應(yīng)用程序上下文(Application Context)中被配置,而過濾器(Filter)運行Spring之前并且是在web.xml中配置的。不管使用哪一個,它們都在綁定了session到當(dāng)前線程的請求中打開Hibernate Session。一旦綁定到了線程,打開的Hibernate
Session就能夠透明地在DAO實現(xiàn)類中使用了。這個Session將一直打開,為視圖(view)提供對數(shù)據(jù)庫值對象的延遲訪問。視圖邏輯如果完成了,Hibernate
Session就會在過濾器的doFilter或攔截器的postHandle方法中關(guān)閉。下面是各個部件配置的一個例子:
攔截器配置
1 <beans>
2 <bean id="urlMapping"
3 class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
4 <property name="interceptors">
5 <list>
6 <ref bean="openSessionInViewInterceptor"/>
7 </list>
8 </property>
9 <property name="mappings">
10
11 </bean>
12
13 <bean name="openSessionInViewInterceptor"
14 class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
15 <property name="sessionFactory"><ref bean="sessionFactory"/></property>
16 </bean>
17 </beans>
過濾器配置
1 <web-app>
2 <filter>
3 <filter-name>hibernateFilter</filter-name>
4 <filter-class>
5 org.springframework.orm.hibernate.support.OpenSessionInViewFilter
6 </filter-class>
7 </filter>
8
9 <filter-mapping>
10 <filter-name>hibernateFilter</filter-name>
11 <url-pattern>*.spring</url-pattern>
12 </filter-mapping>
13
14 </web-app>
實現(xiàn)Hibernate DAO來使用打開的Session很簡單。實際上,如果你準(zhǔn)備用Spring框架來實現(xiàn)你的Hibernate DAO,最可能的是你不需要改變?nèi)魏我惶幋a。強制DAO必須通過方便的HibernateTemplate工具來訪問Hibernate,將會使得數(shù)據(jù)庫訪問是小菜一碟。下面是一個DAO的例子。
DAO例子
1 public class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO {
2
3 public Product getProduct(Integer productId) {
4 return (Product)getHibernateTemplate().load(Product.class, productId);
5 }
6
7 public Integer saveProduct(Product product) {
8 return (Integer) getHibernateTemplate().save(product);
9 }
10
11 public void updateProduct(Product product) {
12 getHibernateTemplate().update(product);
13 }
14 }
在業(yè)務(wù)層延遲
即使是在視圖外面,Spring框架通過AOP攔截器HibernateInterceptor,也讓延遲加載初始化變得很容易使用。這個Hibernate攔截器透明地攔截在Spring應(yīng)用程序上下文(Application Context)中配置的任何業(yè)務(wù)對象的調(diào)用,在調(diào)用前打開一個Hibernate
Session,同時在調(diào)用后關(guān)閉Session。讓我們通過一個快速的例子來運行一下。假設(shè)我們有一個BusinessObject接口:
1 public interface BusinessObject {
2
3 public void doSomethingThatInvolvesDaos();
4 }
BusinessObjectImpl類實現(xiàn)了BusinessObject接口:
1 public class BusinessObjectImpl implements BusinessObject {
2
3 public void doSomethingThatInvolvesDaos() {
4 // lots of logic that calls
5 // DAO classes Which access
6 // data objects lazily
7 }
8 }
通過在Spring應(yīng)用程序上下文(Application Context)中的一些配置,我們能構(gòu)造HibernateInterceptor來攔截BusinessObjectImpl的調(diào)用,以允許它的方法延遲訪問數(shù)據(jù)對象。看一看下面的代碼片斷:
1 <beans>
2 <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
3 <property name="sessionFactory">
4 <ref bean="sessionFactory"/>
5 </property>
6 </bean>
7 <bean id="businessObjectTarget" class="com.acompany.BusinessObjectImpl">
8 <property name="someDAO"><ref bean="someDAO"/></property>
9 </bean>
10 <bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
11 <property name="target"><ref bean="businessObjectTarget"/></property>
12 <property name="proxyInterfaces">
13 <value>com.acompany.BusinessObject</value>
14 </property>
15 <property name="interceptorNames">
16 <list>
17 <value>hibernateInterceptor</value>
18 </list>
19 </property>
20 </bean>
21 </beans>
當(dāng)businessObject bean被引用時,HibernateInterceptor打開一個Hibernate Session并且將調(diào)用傳遞給BusinessObjectImpl。當(dāng)BusinessObjectImpl完成執(zhí)行時,HibernateInterceptor透明地關(guān)閉這個Session。應(yīng)用程序代碼沒有任何的持久化邏輯,然而它也能夠延遲地訪問數(shù)據(jù)對象。
在你的單元測試中延遲
最后一項重點是,我們需要能夠從Junit中測試我們的延遲應(yīng)用的能力。這很容易通過覆蓋用例測試(TestCase)類中的setUp和tearDown方法做到。我更趨向于將這些代碼放于一個方便的能被我所有的測試?yán)^承的抽象測試用例(TestCase)類中。
1 public abstract class MyLazyTestCase extends TestCase {
2
3 public void setUp() throws Exception {
4
super.setUp();
5
SessionFactory sessionFactory = (SessionFactory) getBean("sessionFactory");
6
Session s = sessionFactory.openSession();
7
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));
8
9 }
10
11 protected Object getBean(String beanName) {
12 //Code to get objects from Spring application context
13 }
14
15 public void tearDown() throws Exception {
16
super.tearDown();
17
SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.
18
getResource(sessionFactory);
19
Session s = holder.getSession();
20
s.flush();
21
TransactionSynchronizationManager.unbindResource(sessionFactory);
22
SessionFactoryUtils.closeSessionIfNecessary(s, sessionFactory);
23
}
24}