跳轉到內容

ZK/操作指南/整合其他框架/如何處理Hibernate會話

來自華夏公益教科書

初步!

我對HibernateSessionHandling的描述基於享受Tapestry的Web開發中描述的Hibernate/Tapestry配置...... 我已經將此適應ZK,並提供了一組類以幾乎無需配置即可提供Hibernate會話支援。

還有一種Spring/Hibernate解決方案用於處理Hibernate會話,它與servlet過濾器一起使用。Spring還提供了更多好處(我只從傳聞中得知......)用於Hibernate,並且可能是一個更好的解決方案...... 我不知道Spring Hibernate會話處理是否也可以用於ZK Ajax請求/響應迴圈...... 我聽說有人正在編寫描述......


關於Web應用程式中Hibernate會話的主要規則(在我看來...... 請糾正我......)

  • Hibernate sessionfactory是一種昂貴的資源...... 它是執行緒安全的,每個Web應用程式應該只有一個例項
  • Hibernate會話是一種廉價且非執行緒安全的資源。
  • Hibernate會話可以被視為一項工作。每個HttpSession應該只有一個
  • Hibernate會話和事務通常應該在控制權返回給客戶端時關閉
  • 建立資料庫連線是一個耗時的過程...... 使用連線池!
  • 不要在會話關閉後訪問分離的Hibernate POJO(普通舊Java物件),因為相關資料可能不會從資料庫中檢索(延遲初始化)!
  • ...

解決方案的工作原理

[編輯 | 編輯原始碼]

當應用程式第一次需要Hibernate會話時,將使用靜態最終HibernateSessionProvider.getHibernateSession(HttpSession)(@tomyeh...... 如何提供zul快捷方式?)來請求一個Hibernate會話。這會檢查當前HttpSession的屬性以查詢HibernateSessionOwner...... 當不可用時,它會建立一個並將其儲存在當前HTTPSession中。HibernateSessionOwner的建構函式檢查WebApp屬性以查詢屬性HibernateSessionCreator()...... 當不可用時,它會建立一個並將它儲存在WebApp的屬性中...... 因此我們在Web應用程式中只有一個HibernateSessionCreator例項,它是一個用於建立Hibernate會話的Hibernate session factory的包裝器...... 當HibernateSessionOwner和-Creator例項化時,getHibernateSession()會向HibernateSessionOwner請求一個Hibernate會話...... SessionOwner從建立者那裡獲取它,並進一步管理它...... HibernateSessionOwner持有會話,並在正在進行的執行緒中每次需要會話時提供它...... 為了線上程結束時清理Hibernate會話,使用HibernateSessionEventThreadCleanup類(並在web.xml中配置),它實現了zk介面EventThreadCleanup...... 在清理例程中,呼叫當前會話的HibernateSessionOwner的threadDidDiscardService(),停止事務並關閉Hibernate會話......

在Tomcat的server.xml中配置jdbc連線池

...
<Context docBase="TKTestWeb" 
         path="/TZTestWeb" 
         reloadable="true" 
         source="org.eclipse.jst.j2ee.server:ZKTest">
   
    <Resource auth="Container" 
              defaultAutoCommit="false" 
              driverClassName="com.mysql.jdbc.Driver" 
              maxActive="20" name="jdbc/zktest" 
              password="testpw" 
              timeBetweenEvictionRunsMillis="60000" 
              type="javax.sql.DataSource" 
              url="jdbc:mysql://:3306/zktest" 
              username="testuser"/>
</Context>
...

在web.xml中引用jdbc資料來源

...
<resource-ref>
    <res-ref-name>jdbc/infratour</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>
...

在zk.xml中配置EventThreadCleanup

<zk>
   <listener>
     <listener-class>com.potix.hibernate.HibernateSessionEventThreadCleanup</listener-class>
   </listener>
</zk>

在web.xml中配置SessionFilter ??? TODO:提供一個與EventThreadCleanup對普通HttpSession請求/響應迴圈執行相同操作的類

Java 原始碼

[編輯 | 編輯原始碼]

將以下java類新增到您的類路徑(WEB-INF/classes)

HibernateSessionProvider.java

import org.hibernate.Session;
import com.potix.util.logging.Log;
import com.potix.zk.ui.http.SessionImpl; 


public class HibernateSessionProvider {
	private static final Log log = Log.lookup(HibernateSessionProvider.class);
	private static final String HIBERNATE_SESSION_OWNER = "de.test.web.services.DefaultSessionOwner";

	//TODO add some debug logging
	public static final Session getHibernateSession(SessionImpl httpSession){
		
		Session hs;
		HibernateSessionOwner sessionOwner = (HibernateSessionOwner)httpSession.getAttribute(HIBERNATE_SESSION_OWNER);
		
		if(sessionOwner==null){
			
			sessionOwner = new HibernateSessionOwner(httpSession);
			httpSession.setAttribute(HIBERNATE_SESSION_OWNER, sessionOwner );

	      }
 		return hs;
	}
}

HibernateSessionOwner.java

import org.hibernate.Session;
import org.hibernate.Transaction;
import com.potix.util.logging.Log;
import com.potix.zk.ui.http.SessionImpl;

public class HibernateSessionOwner implements SessionOwner {
	private static final String HIBERNATE_SESSION_CREATOR = "de.test.web.services.DefaultSessionCreator";
 	private static final Log log = Log.lookup(HibernateSessionCreator.class);
 
	private SessionCreator creator;
  	private Session hibernateSession;
 	private Transaction tx;
	private boolean isToRollback;
	//TODO add some debug logging
	public HibernateSessionOwner(SessionImpl httpSession) {
		creator = (SessionCreator)httpSession.getWebApp()
		          .getAttribute(HIBERNATE_SESSION_CREATOR);
 		
		if(creator == null){
			httpSession.getWebApp()
			.setAttribute(HIBERNATE_SESSION_CREATOR, new HibernateSessionCreator());
			creator = (SessionCreator)httpSession
					.getWebApp()
	          			.getAttribute(HIBERNATE_SESSION_CREATOR);
			if(creator == null){
				log.error("Could not install SessionCreatorService" );
				throw new RuntimeException("Could not install SessionCreatorService");
			}
		}
	}
	
	public Session getSession() {
		if (hibernateSession == null) {
			hibernateSession = creator.createSession();
			if (tx == null) {
				tx = hibernateSession.beginTransaction();
				isToRollback = false;
			}
		}
		return hibernateSession;
	}

	public void threadDidDiscardService() {
		if (hibernateSession != null) {
			try {
				endTransaction();
			} finally {
				hibernateSession.close();
				hibernateSession = null;
			}
		}
	}

	public void setToRollback() {
		isToRollback = true;
	}

	public void endTransaction() {
		if (tx != null) {
			try {
				if (isToRollback) {
					tx.rollback();
				} else {
					tx.commit();
				}
			} catch (RuntimeException e) {
				tx.rollback();
				throw e;
			} finally {
				tx = null;
			}
		}
	}

	public void setSessionCreator(SessionCreator creator) {
		this.creator = creator;
	}
}


HibernateSessionCreator.java

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.potix.util.logging.Log;

public class HibernateSessionCreator implements SessionCreator {
	private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";
	/** A session attribute. */
	
 	private static final Log log = Log.lookup(HibernateSessionCreator.class); 
	
	private SessionFactory sessionFactory;

	public HibernateSessionCreator() {
		Configuration cfg = new Configuration();
		cfg.configure(CONFIG_FILE_LOCATION);
		sessionFactory = cfg.buildSessionFactory();

		if (sessionFactory == null){
			log.error("Could not crate SessionFactory" );
			throw new RuntimeException("Could not crate SessionFactory");
		}
	}

	public Session createSession() {
		return sessionFactory.openSession();
	}
}

HibernateSessionEventThreadCleanup.java

import com.potix.zk.ui.Component;
import com.potix.zk.ui.event.Event;
import com.potix.zk.ui.event.EventThreadCleanup;

public class HibernateSessionEventThreadCleanup implements EventThreadCleanup{
	private static final String HIBERNATE_SESSION_OWNER = "de.infratour.web.services.DefaultSessionOwner";
 
	public void cleanup(Component comp, Event event) {
		if(event.getPage().getSession().getAttribute(HIBERNATE_SESSION_OWNER)==null)
			return;

		((HibernateSessionOwner)event.getPage().getSession()
					.getAttribute(HIBERNATE_SESSION_OWNER))
					.threadDidDiscardService();
	}
}


HibernateSessionHttpRequestCleanup.java

TODO!!!

外部庫

[編輯 | 編輯原始碼]

將Hibernate發行版中的以下jar新增到您的類路徑(WEB-INF/lib)

  • hibernate3.jar
  • ...

使用Hibernate會話

[編輯 | 編輯原始碼]

透過上述步驟,ZK啟用了Hibernate...... 現在您可以插入您的Hibernate配置並在您的*.zul模板中使用它......

將您的Hibernate POJO類與Hibernate對映和配置檔案一起新增到類路徑

WEB-INF/lib
+-*myhibernate.jar
  +-*package
  | +-*Person.class
  | +-*Person.hbm.xml
  *hibernate.cfg.xml

登錄檔單示例

<window title="Registration" border="normal">

	
 	<zscript>{
	    
	    import de.infratour.hbm.*;
	    import de.infratour.web.services.HibernateSessionProvider;
	    import org.hibernate.Session;
	    
		void submit() {
		
			Person person = new Person();

			person.setNickname(nickname.value);
			person.setEmail(email.value);
			person.setForename(forename.value);
			person.setSurename(surename.value);
			
			if(password1.value!=password2.value){
					alert("The repeat password is different from password! please enter again!");
					password1.value="";
					password2.value="";
					return;
					//TODO ...mark textbox red ???
			}
			person.setPasswd(password1.value);
			
			try {
				org.hibernate.Session hs = (org.hibernate.Session)HibernateSessionProvider.getHibernateSession(session);
				hs.persist(person);
			}catch (Exception e) {
				alert(e.getMessage());
			}
		}		
	}
	</zscript>
	
	<grid>
	<rows>
		<row>Name : <textbox id="forename" /></row>
		<row>Surename : <textbox id="surename"  /></row>
		<row>Login Name : <textbox id="nickname" /></row>
		<row>Email: <textbox id="email"  /></row>
		<row>Password : <textbox id="password1" type="password"/></row>
		<row>Repeat Password : <textbox id="password2" type="password"/></row>

		<row><button label="submit" onClick="submit()"/></row>
 	</rows>
	</grid>
</window>

開放點

[編輯 | 編輯原始碼]
  • 提供對HibernateSessionProvider的靜態最終例程的快捷方式(@Tom... 如何做?)
  • 每個桌面只有一個執行緒...... 多個瀏覽器視窗但同一個HttpSession的Hibernate會話怎麼辦?
  • 不要在發生異常後重新整理會話...... 希望處理源於資料庫約束的異常(例如唯一......)“暱稱已分配......”。
  • ...
華夏公益教科書