在沒有使用
Spring
提供的
Open Session In View
情況下,因需要在
service(or Dao)
層里把
session
關(guān)閉,所以
lazy loading
為
true
的話,要在應(yīng)用層內(nèi)把關(guān)系集合都初始化,如
company.getEmployees()
,否則
Hibernate
拋
session already closed Exception;
??? Open Session In View
提供了一種簡便的方法,較好地解決了
lazy loading
問題
.
??? 它有兩種配置方式OpenSessionInViewInterceptor
和OpenSessionInViewFilter(具體參看SpringSide)
,功能相同,只是一個(gè)在
web.xml
配置,另一個(gè)在
application.xml
配置而已。
??? Open Session In View
在
request
把
session
綁定到當(dāng)前
thread
期間一直保持
hibernate session
在
open
狀態(tài),使
session
在
request
的整個(gè)期間都可以使用,如在
View
層里
PO
也可以
lazy loading
數(shù)據(jù),如
${ company.employees }
。當(dāng)
View
層邏輯完成后,才會(huì)通過
Filter
的
doFilter
方法或
Interceptor
的
postHandle
方法自動(dòng)關(guān)閉
session
。
OpenSessionInViewInterceptor配置
-
<beans>
-
<bean name="openSessionInViewInterceptor"
-
class
="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
-
<property name="sessionFactory">
-
<ref bean="sessionFactory"/>
-
</property>
-
</bean>
-
<bean id="urlMapping"
-
class
="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
-
<property name="interceptors">
-
<list>
-
<ref bean="openSessionInViewInterceptor"/>
-
</list>
-
</property>
-
<property name="mappings">
-
...
-
</property>
-
</bean>
-
...
-
</beans>
OpenSessionInViewFilter配置
-
<web-app>
-
...
-
<filter>
-
<filter-name>hibernateFilter</filter-name>
-
<filter-class>
-
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
-
</filter-class>
-
<!-- singleSession默認(rèn)為true,若設(shè)為false則等于沒用OpenSessionInView -->
-
<init-param>
-
<param-name>singleSession</param-name>
-
<param-value>true</param-value>
-
</init-param>
-
</filter>
-
...
-
<filter-mapping>
-
<filter-name>hibernateFilter</filter-name>
-
<url-pattern>*.do</url-pattern>
-
</filter-mapping>
-
...
-
</web-app>
很多人在使用OpenSessionInView過程中提及一個(gè)錯(cuò)誤:
-
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations
-
are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into
-
FlushMode.AUTO or remove 'readOnly' marker from transaction definition
看看OpenSessionInViewFilter里的幾個(gè)方法
-
protected
void
doFilterInternal
(
HttpServletRequest request,
HttpServletResponse response,FilterChain filterChain)
throws ServletException, IOException
{
SessionFactory sessionFactory = lookupSessionFactory();
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
Session session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource
(
sessionFactory, new SessionHolder(session));
try
{
filterChain.doFilter(request, response);
}
finally
{
TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
closeSession(session, sessionFactory);
}
}
?
-
protected
Session getSession(SessionFactory sessionFactory
)
throws
DataAccessResourceFailureException
{
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.setFlushMode(FlushMode.NEVER);
return session;
}
-
protected
void
closeSession
(
Session session, SessionFactory sessionFactory
)
throws
CleanupFailureDataAccessException
{
SessionFactoryUtils.closeSessionIfNecessary(session, sessionFactory);
}
??????????關(guān)于綁定session的方式,通過看spring里TransactionSynchronizationManager的實(shí)現(xiàn),發(fā)現(xiàn):它維護(hù)一個(gè)java.lang.ThreadLocal類型的resources,resources負(fù)責(zé)持有線程局部變量,這里resources持有的是一個(gè)HashMap,通過TransactionSynchronizationManager.bindResource()方法在map里綁定和線程相關(guān)的所有變量到他們的標(biāo)識(shí)上,包括如上所述的綁定在sessionFactory上的線程局部session。sessionHolder只不過是存放可以hold一個(gè)session并可以和transtaction同步的容器。可以看到OpenSessionInViewFilter在getSession的時(shí)候,會(huì)把獲取回來的session的flush mode 設(shè)為FlushMode.NEVER。然后把該sessionFactory綁定到TransactionSynchronizationManager,使request的整個(gè)過程都使用同一個(gè)session,在請求過后再接除該sessionFactory的綁定,最后closeSessionIfNecessary根據(jù)該session是否已和transaction綁定來決定是否關(guān)閉session。綁定以后,就可以防止每次不會(huì)新開一個(gè)Session呢?看看HibernateDaoSupport的情況:
1. public
final void setSessionFactory(SessionFactory sessionFactory) { 2. this.hibernateTemplate = new HibernateTemplate(sessionFactory); 3. } 4. protectedfinal HibernateTemplate getHibernateTemplate() { 5. return hibernateTemplate; 6. }
??????? 我們的DAO將使用這個(gè)template進(jìn)行操作.
public abstract class BaseHibernateObjectDao extends HibernateDaoSupport
implements BaseObjectDao {
???? protected BaseEntityObject getByClassId(final long id) {
??????????? ????BaseEntityObject obj =(BaseEntityObject) getHibernateTemplate().execute(new HibernateCallback() {
??????????????? ??????? public Object doInHibernate(Session session) throws HibernateException {
????????????????? ????????????????? return session.get(getPersistentClass(),new Long(id));
???????????? ?????????? }
???????????? ?? });
????????????? ??return obj;
??? ??}
???? public void save(BaseEntityObject entity) {
???????????????? ?getHibernateTemplate().saveOrUpdate(entity);
???? }
??? public void remove(BaseEntityObject entity) {
???????????? ?try {
???????????????????? getHibernateTemplate().delete(entity);
????????????? } catch (Exception e) {
????????????????????? throw new FlexEnterpriseDataAccessException(e);
???????????? }
???? }
????? public void refresh(final BaseEntityObject entity) {
???????????????getHibernateTemplate().execute(new HibernateCallback() {
????????????????????????? public Object doInHibernate(Session session) throws HibernateException {
????????????????????????????????????? session.refresh(entity);
????????????????????????????????????? return null;
??????????????????????? ??}
????????????? ?});
????? }
???? public void replicate(final Object entity) {
??????????????? getHibernateTemplate().execute(new HibernateCallback() {
????????????????????????? public Object doInHibernate(Session session)throws HibernateException {
????????????????????????????????????? session.replicate(entity,ReplicationMode.OVERWRITE);
????????????????????????????????????? return null;
?????????????? }
?????????????? ?});
????? }
}
??????? 而HibernateTemplate試圖每次在execute之前去獲得Session,執(zhí)行完就力爭關(guān)閉Session
1. public
Object execute(HibernateCallback action) throws DataAccessException {??2. ???Session session = (!this.allowCreate ???3. ?????????SessionFactoryUtils.getSession(getSessionFactory(), 4. false) :??5. ?????????SessionFactoryUtils.getSession(getSessionFactory(), 6. getEntityInterceptor(), 7. getJdbcExceptionTranslator()));??8. ????boolean existingTransaction = 9. TransactionSynchronizationManager.hasResource(getSessionFactory());??10.????if (!existingTransaction && getFlushMode() == FLUSH_NEVER) {??11.??????????session.setFlushMode(FlushMode.NEVER);??12.????}??13.????try {??14.??????????Object result = action.doInHibernate(session);??15.??????????flushIfNecessary(session, existingTransaction);??16.??????????return result;??17.????}??18.????catch (HibernateException ex) {??19.??????????throw convertHibernateAccessException(ex);??20.????}??21.????finally {??22.??????????SessionFactoryUtils.closeSessionIfNecessary( 23. session, getSessionFactory());??24.????} 25. }
????? 而這個(gè)SessionFactoryUtils能否得到當(dāng)前的session以及closeSessionIfNecessary是否真正關(guān)閉session,端取決于這個(gè)session是否用sessionHolder和這個(gè)sessionFactory在我們最開始提到的TransactionSynchronizationManager綁定。
???? publicstaticvoidcloseSessionIfNecessary(Session session, SessionFactory sessionFactory)
-
throws
CleanupFailureDataAccessException {
-
if
(
session == null ||
TransactionSynchronizationManager.hasResource(sessionFactory)){
-
return
;
-
}
-
logger.debug("Closing Hibernate session");
-
try
{
-
session.close();
-
}
-
catch
(
JDBCException ex){
-
// SQLException underneath
-
throw
new
CleanupFailureDataAccessException("Could not close Hibernate session", ex.getSQLException());
-
}
-
catch
(
HibernateException ex){
-
throw
new
CleanupFailureDataAccessException("Could not close Hibernate session", ex);
-
}
-
}
??? 在這個(gè)過程中,若HibernateTemplate 發(fā)現(xiàn)自當(dāng)前session有不是readOnly的transaction,就會(huì)獲取到FlushMode.AUTO Session,使方法擁有寫權(quán)限。也即是,如果有不是readOnly的transaction就可以由Flush.NEVER轉(zhuǎn)為Flush.AUTO,擁有insert,update,delete操作權(quán)限,如果沒有transaction,并且沒有另外人為地設(shè)flush model的話,則doFilter的整個(gè)過程都是Flush.NEVER。所以受transaction保護(hù)的方法有寫權(quán)限,沒受保護(hù)的則沒有。
- 可能的解決方式有:
1、將singleSession設(shè)為false,這樣只要改web.xml,缺點(diǎn)是Hibernate Session的Instance可能會(huì)大增,使用的JDBC Connection量也會(huì)大增,如果Connection Pool的maxPoolSize設(shè)得太小,很容易就出問題。
2、在控制器中自行管理Session的FlushMode,麻煩的是每個(gè)有Modify的Method都要多幾行程式。
????? session.setFlushMode(FlushMode.AUTO);
????? session.update(user);
????? session.flush();
3、Extend OpenSessionInViewFilter,Override protected Session getSession(SessionFactory sessionFactory),將FlushMode直接改為Auto。
4、讓方法受Spring的事務(wù)控制。這就是常使用的方法:
采用spring的事務(wù)聲明,使方法受transaction控制
-
? <bean id="baseTransaction"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
? ? ? ? ? abstract="true">
? ? ? ? <property name="transactionManager" ref="transactionManager"/>
? ? ? ? <property name="proxyTargetClass" value="true"/>
? ? ? ? <property name="transactionAttributes">
? ? ? ? ? ? <props>
? ? ? ? ? ? ? ? <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
? ? ? ? ? ? ? ? <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
? ? ? ? ? ? ? ? <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
? ? ? ? ? ? ? ? <prop key="save*">PROPAGATION_REQUIRED</prop>
? ? ? ? ? ? ? ? <prop key="add*">PROPAGATION_REQUIRED</prop>
? ? ? ? ? ? ? ? <prop key="update*">PROPAGATION_REQUIRED</prop>
? ? ? ? ? ? ? ? <prop key="remove*">PROPAGATION_REQUIRED</prop>
? ? ? ? ? ? </props>
? ? ? ? </property>
? ? </bean>
-
? ? <bean id="userService" parent="baseTransaction">
? ? ? ? <property name="target">
? ? ? ? ? ? <bean class="com.phopesoft.security.service.impl.UserServiceImpl"/>
? ? ? ? </property>
? ? </bean>
對于上例,則以save,add,update,remove開頭的方法擁有可寫的事務(wù),如果當(dāng)前有某個(gè)方法,如命名為importExcel(),則因沒有transaction而沒有寫權(quán)限,這時(shí)若方法內(nèi)有insert,update,delete操作的話,則需要手動(dòng)設(shè)置flush model為Flush.AUTO,如
-
session.setFlushMode(FlushMode.AUTO);
-
session.save(user);
-
session.flush();
?????盡管Open Session In View看起來還不錯(cuò),其實(shí)副作用不少。看回上面OpenSessionInViewFilter的doFilterInternal方法代碼,這個(gè)方法實(shí)際上是被父類的doFilter調(diào)用的,因此,我們可以大約了解的OpenSessionInViewFilter調(diào)用流程: request(請求)->open session并開始transaction->controller->View(Jsp)->結(jié)束transaction并close session.
???? 一切看起來很正確,尤其是在本地開發(fā)測試的時(shí)候沒出現(xiàn)問題,但試想下如果流程中的某一步被阻塞的話,那在這期間connection就一直被占用而不釋放。最有可能被阻塞的就是在寫Jsp這步,一方面可能是頁面內(nèi)容大,response.write的時(shí)間長,另一方面可能是網(wǎng)速慢,服務(wù)器與用戶間傳輸時(shí)間久。當(dāng)大量這樣的情況出現(xiàn)時(shí),就有連接池連接不足,造成頁面假死現(xiàn)象。
Open Session In View是個(gè)雙刃劍,放在公網(wǎng)上內(nèi)容多流量大的網(wǎng)站請慎用。
?
另外:這樣會(huì)產(chǎn)生一點(diǎn)危險(xiǎn)性,畢竟把數(shù)據(jù)庫訪問的環(huán)境放到了表現(xiàn)層。(:用VO)
?
?
?
?
?
??????? Hibernate是對JDBC的輕量級對象封裝,Hibernate本身是不具備Transaction處理功能的,Hibernate的Transaction實(shí)際上是底層的JDBC Transaction的封裝,或者是JTA Transaction的封裝,下面我們詳細(xì)的分析:
Hibernate可以配置為JDBCTransaction或者是JTATransaction,這取決于你在hibernate.properties中的配置:
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JDBCTransactionFactory
如果你什么都不配置,默認(rèn)情況下使用JDBCTransaction,如果你配置為:
hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory
將使用JTATransaction,不管你準(zhǔn)備讓Hibernate使用JDBCTransaction,還是JTATransaction,我的忠告就是什么都不配,將讓它保持默認(rèn)狀態(tài),如下:
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JDBCTransactionFactory
在下面的分析中我會(huì)給出原因。
一、JDBC Transaction
看看使用JDBC Transaction的時(shí)候我們的代碼例子:
Session session = sf.openSession();
Transaction tx = session.beginTransactioin();
...
session.flush();
tx.commit();
session.close();
這是默認(rèn)的情況,當(dāng)你在代碼中使用Hibernate的Transaction的時(shí)候?qū)嶋H上就是JDBCTransaction。那么JDBCTransaction究竟是什么東西呢?來看看源代碼就清楚了:
Hibernate2.0.3源代碼中的類
net.sf.hibernate.transaction.JDBCTransaction:
public void begin() throws HibernateException {
...
if (toggleAutoCommit) session.connection().setAutoCommit(false);
...
}
這是啟動(dòng)Transaction的方法,看到 connection().setAutoCommit(false) 了嗎?是不是很熟悉?
再來看
public void commit() throws HibernateException {
...
try {
if ( session.getFlushMode()!=FlushMode.NEVER ) session.flush();
try {
session.connection().commit();
committed = true;
}
...
toggleAutoCommit();
}
這是提交方法,看到connection().commit() 了嗎?下面就不用我多說了,這個(gè)類代碼非常簡單易懂,通過閱讀使我們明白Hibernate的Transaction都在干了些什么?我現(xiàn)在把用Hibernate寫的例子翻譯成JDBC,大家就一目了然了:
Connection conn = ...;????????????? ?<--- session = sf.openSession();
conn.setAutoCommit(false);?? <--- tx = session.beginTransactioin();
... <--- ...
conn.commit();?????????????????????????? <--- tx.commit(); (對應(yīng)左邊的兩句)
conn.setAutoCommit(true);
conn.close();????????????????????????????? <--- session.close();
看明白了吧,Hibernate的JDBCTransaction根本就是conn.commit而已,根本毫無神秘可言,只不過在Hibernate中,Session打開的時(shí)候,就會(huì)自動(dòng)conn.setAutoCommit(false),不像一般的JDBC,默認(rèn)都是true,所以你最后不寫commit也沒有關(guān)系,由于Hibernate已經(jīng)把AutoCommit給關(guān)掉了,所以用Hibernate的時(shí)候,你在程序中不寫Transaction的話,數(shù)據(jù)庫根本就沒有反應(yīng)。?
?二、JTATransaction
如果你在EJB中使用Hibernate,或者準(zhǔn)備用JTA來管理跨Session的長事務(wù),那么就需要使用JTATransaction,先看一個(gè)例子:
javax.transaction.UserTransaction tx = new InitialContext().lookup("javax.transaction.UserTransaction");
Session s1 = sf.openSession();
...
s1.flush();
s1.close();
...
Session s2 = sf.openSession();
...
s2.flush();
s2.close();
tx.commit();
這是標(biāo)準(zhǔn)的使用JTA的代碼片斷,Transaction是跨Session的,它的生命周期比Session要長。如果你在EJB中使用Hibernate,那么是最簡單不過的了,你什么Transaction代碼統(tǒng)統(tǒng)都不要寫了,直接在EJB的部署描述符上配置某某方法是否使用事務(wù)就可以了。
現(xiàn)在我們來分析一下JTATransaction的源代碼, net.sf.hibernate.transaction.JTATransaction:
public void begin(InitialContext context, ...
...
ut = (UserTransaction) context.lookup(utName);
...
看清楚了嗎? 和我上面寫的代碼 tx = new Initial Context?().lookup("javax.transaction.UserTransaction"); 是不是完全一樣?
public void commit() ...
...
if (newTransaction) ut.commit();
...
JTATransaction的控制稍微復(fù)雜,不過仍然可以很清楚的看出來Hibernate是如何封裝JTA的Transaction代碼的。
但是你現(xiàn)在是否看到了什么問題? 仔細(xì)想一下,Hibernate Transaction是從Session中獲得的,tx = session.beginTransaction(),最后要先提交tx,然后再session.close,這完全符合JDBC的Transaction的操作順序,但是這個(gè)順序是和JTA的Transactioin操作順序徹底矛盾的!!! JTA是先啟動(dòng)Transaction,然后啟動(dòng)Session,關(guān)閉Session,最后提交Transaction,因此當(dāng)你使用JTA的Transaction的時(shí)候,那么就千萬不要使用Hibernate的Transaction,而是應(yīng)該像我上面的JTA的代碼片斷那樣使用才行。
總結(jié):
1、在JDBC上使用Hibernate
必須寫上Hibernate Transaction代碼,否則數(shù)據(jù)庫沒有反應(yīng)。此時(shí)Hibernate的Transaction就是Connection.commit而已
2、在JTA上使用Hibernate
寫JTA的Transaction代碼,不要寫Hibernate的Transaction代碼,否則程序會(huì)報(bào)錯(cuò)
3、在EJB上使用Hibernate
什么Transactioin代碼都不要寫,在EJB的部署描述符里面配置
|---CMT(Container Managed Transaction)
|
|---BMT(Bean Managed Transaction)
|
|----JDBC Transaction
|
|----JTA Transaction
?
?
?
?
關(guān)于session:
1.? servlet的session機(jī)制基于cookies,關(guān)閉瀏覽器的cookies則session失效即不能用網(wǎng)站的登錄功能。
2.? Hibernate Session.
????? 1>. session 清理緩存時(shí),按照以下順序執(zhí)行SQL語句:
??????????? session.save()的實(shí)體insert?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
???????????
實(shí)體的update
??????????? 對
集合的delete
??????????? 集合元素的delete,update,insert
??????????? 集合的insert
????????????session.delete()的先后,執(zhí)行實(shí)體的delete
?????? 2>. 默認(rèn)時(shí),session在以下時(shí)間點(diǎn)清理緩存:
????????????? net.sf.hibernate.Transaction.commit():先清理緩存,再向數(shù)據(jù)庫提交事務(wù)??????????????????????????????????????????????????????????
????????????? Session.find()或iterate()時(shí),若緩存中持久化對象的屬性發(fā)生了變化,就會(huì)先清緩存,以保證查詢結(jié)果正確
?
?????? 3>.? Session的commit()和flush()的區(qū)別:flush()只執(zhí)行SQL語句,不提交事務(wù);commit()先調(diào)用flush(),再提交事務(wù)
?????? 4>.? Session.setFlushMode()用于設(shè)定清理緩存的時(shí)間點(diǎn)。
清理緩存的模式 |
Session的查詢方法 |
Session.commit() |
Session.flush() |
FlushMode.AUTO |
清理 |
清理 |
清理 |
FlushMode.COMMIT |
不清理
|
清理 |
清理 |
FlushMode.NEVER |
不清理
|
不清理
|
清理 |
???????????
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=797399