當我們在使用Hibernate作為資料庫操作的類庫時,我們一般在DAO層裡與資料庫相關的操作,把商務邏輯寫在service層裡。但是如果我們的項目比較小,那麼直接在dao層裡寫事務也是可以的,這個就是看個人了,沒有什麼特別的規定。但是如果項目比較大,那麼DAO應該只做單純的資料庫的操作,service寫事務的操作,即整個商務邏輯。
例如:商務邏輯要求向資料庫中的使用者表增加一個使用者,同時向日誌表中加入一條日誌,而這需要調用DAO的兩個方法(UserDao的saveUser和LogDao的saveLog)。這顯然是一個事務,也就是如果一個操作出現了問題,就要復原到初始的狀態。那麼如何在Service層控制事務呢,本文就以此例的代碼說明。
在DAO進行Session事務出現的問題
我們先看看在DAO層裡寫Hibernate的session的事務。
package com.xxg;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
建立使用者表T_user(id,username)和日誌表T_log(id,content),以及它們對應的實體類User、Log及對應檔,這裡就不一一貼出代碼。
public class UserDao {
public void saveUser(User user){
SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //擷取SessionFactory
Session session = sessionFactory.openSession();// openSession
session.beginTransaction(); //開始事務
session.save(user);
session.getTransaction().commit(); //事務提交
session.close(); //關閉session
}
}
public class LogDao {
public void saveLog(Log log){
SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //擷取SessionFactory
Session session = sessionFactory.openSession();// openSession
session.beginTransaction(); //開始事務
session.save(log);
session.getTransaction().commit(); //事務提交
session.close(); //關閉session
}
}
接下來我們看看在service中寫一個商務邏輯
public class TestService {
public void save(User user){
UserDao userDao = new UserDao();
userDao.saveUser(user);
LogDao logDao = new LogDao();
Log log = new Log();
log.setContent("插入一個使用者");
logDao.saveLog(log);
}
}
可以看到,我們在兩個DAO裡寫了資料庫的事務,代碼中高亮顯示了,session.beginTransaction()顯示聲明事務的開始。
這樣寫是不對的,因為這兩個事情作為一個事務來進行的,會出現一個事務成功提交,而另外一個可能提交失敗,導致不一致的情況,這樣這兩個操作不算是一個事務transaction,所以這麼寫就是一個失敗的交易。
因此,我們要將事務在service中進行聲明。
在service層寫session的資料庫事務
為了將事務放在service中,我們需要更改HibernateUtil的代碼才能實現。否則使用上面的那個不能達到我們的需求。在這個新的HibernateUtil代碼中,利用了ThreadLocal的線程內的局部變數來儲存hibernate的session對象。這樣就可以在不同的class中使用同一個session對象,而不用傳遞參數。
public class HibernateUtil {
public static final ThreadLocal session = new ThreadLocal();
public static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch ( Throwable ex ) {
throw new ExceptionInInitializerError( ex );
}
}
public static Session currentSession() throws HibernateException
{
Session s = session.get();
if ( s == null )
{
s = sessionFactory.openSession();
session.set( s );
}
return(s);
}
public static void closeSession() throws HibernateException
{
Session s = session.get();
if ( s != null )
{
s.close();
}
session.set( null );
}
}
接下來,我們將事務放在service中。看代碼:
public class TestService {
public void save(User user){
SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //擷取SessionFactory
Session session = sessionFactory.getCurrentSession();//getCurrentSession
session.beginTransaction();//事務開始
UserDao userDao = new UserDao();
userDao.saveUser(user);
LogDao logDao = new LogDao();
Log log = new Log();
log.setContent("插入一個使用者");
logDao.saveLog(log);
session.getTransaction().commit();//事務提交
}
}
public class LogDao {
public void saveLog(Log log) throws RuntimeException{
SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //擷取SessionFactory
Session session = sessionFactory.getCurrentSession(); //getCurrentSession
session.save(log);
throw new RuntimeException();
}
}
public class UserDao {
public void saveUser(User user){
SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //擷取SessionFactory
Session session = sessionFactory.getCurrentSession();//getCurrentSession
session.save(user);
}
}
通過getCurrentSession()可以獲得當前線程的session對象,通過它來進行共用session。這樣事務就從service開始,然後再service結束。
getCurrentSession()與openSession()區別
getCurrentSession建立的session會和綁定到當前線程,而openSession不會。
getCurrentSession建立的線程會在交易回復或事物提交後自動關閉,而openSession必須手動關閉
採用getCurrentSession()建立的session會綁定到當前線程中,而採用openSession()
建立的session則不會
* 採用getCurrentSession()建立的session在commit或rollback時會自動關閉,而採用openSession()
建立的session必須手動關閉
2、使用getCurrentSession()需要在hibernate.cfg.xml檔案中加入如下配置:
* 如果使用的是本地事務(jdbc事務)
<property name=”hibernate.current_session_context_class”>thread</property>
* 如果使用的是全域事務(jta事務)
<property name=”hibernate.current_session_context_class”>jta</property>
getCurrentSession()與openSession()關聯
在 SessionFactory 啟動的時候, Hibernate 會根據配置建立相應的 CurrentSessionContext ,在 getCurrentSession() 被調用的時候,實際被執行的方法是 CurrentSessionContext.currentSession() 。在 currentSession() 執行時,如果當前 Session 為空白, currentSession 會調用 SessionFactory 的 openSession 。所以 getCurrentSession() 對於 Java EE 來說是更好的擷取 Session 的方法。