Hibernate(3)——實例總結Hibernate對象的狀態和ThreadLoacl封閉的session

俗話說,本身寫的代碼,6個月後也是別人的代碼……複習!複習!複習!涉及的知識點總結以下:html

  • Hibernate的內部執行過程(CRUD)
  • 對象的狀態及其轉換圖和例子
  • 使用JUnit測試
  • 使用getCurrentSession代替openSession
  • ThreadLoacl對象
  • 享元模式
  • session.update(obj),爲保證執行更新,推薦使用session.flush()刷新緩存;

 

  1和2大概總結了Hibernate運行的技術原理,那麼如今總結一下它自身的編寫過程:以下:java

  其中本質上主要就是使用了dom4j解析配置文件+反射技術來支撐了整個框架的運行。固然若是是註解的話,還有註解技術。而其中世界級的設計思想和編程技巧,又是另外一個方面的技術內容了。web

 

  Hibernate是如何識別持久化類的?spring

  在Hibernate的hibernate.cfg.xml配置文件中引入了實體關係的映射文件Xxx.hbm.xml,而在映射文件中指明瞭持久化類是哪些,因此Hibernate經過它識別持久化類,Hibernate容器——》hibernate.cfg.xml——》 *.hbm.xml——》class元素的name屬性加在持久化類,經過這種方式識別持久化類。數據庫

 

  內部執行過程編程

  Hibernate的CRUD方法代碼設計模式

 1 /**
 2  * UserDao
 3  *
 4  * @author Wang Yishuai.
 5  * @date 2016/2/2 0002.
 6  * @Copyright(c) 2016 Wang Yishuai,USTC,SSE.
 7  */
 8 public class UserDao {
 9     final Logger LOG = LoggerFactory.getLogger(UserDao.class);
10 
11     private SessionFactory sessionFactory;
12 
13     private UserDao() {
14         // 經過new一個Configuration實例,而後用該實例去調用configure返回一個配置實例
15         Configuration configuration = new Configuration().configure();
16         // 經過 配置實例的buildSessionFactory方法 生成一個 sessionFactory 對象
17         // buildSessionFactory方法會默認的去尋找配置文件hibernate.cfg.xml並解析xml文件
18         // 解析完畢生成sessionFactory,負責鏈接數據庫
19         this.sessionFactory = configuration.buildSessionFactory();
20     }
21 
22     public static UserDao newInstance() {
23         return new UserDao();
24     }
25 
26     public void save(User user) {
27         // 經過 sessionFactory 得到一個數據庫鏈接 session,能夠操做數據庫
28         Session session = sessionFactory.openSession();
29         // 把操做封裝到數據庫的事務,則須要開啓一個事務
30         Transaction transaction = session.beginTransaction();
31 
32         // 通常把對實體類和數據庫的操做,放到try-catch-finally塊
33         try {
34             // 把user對象插入到數據庫
35             session.save(user);
36             // 提交操做事務
37             transaction.commit();
38             LOG.info("transaction.commit(); ok");
39         } catch (Exception e) {
40             // 提交事務失敗,必需要回滾
41             transaction.rollback();
42             // 打印日誌
43             LOG.error("save user error......", e);
44         } finally {
45             // 不能丟這一步,要釋放資源
46             session.close();
47             LOG.info("session.close(); ok");
48         }
49     }
50 
51     public void retriveAll() {
52         Session session = sessionFactory.openSession();
53         List<User> userList = session.createQuery("from User").list();
54         session.close();
55 
56         for (User user : userList) {
57             LOG.info("username = {}", user.getUsername());
58         }
59     }
60 
61     public void delete(User user) {
62         Session session = sessionFactory.openSession();
63         Transaction transaction = session.beginTransaction();
64 
65         user = (User) session.get(user.getClass(), user.getUserId());
66         session.delete(user);
67         transaction.commit();
68         session.close();
69     }
70 
71     public void update(User user) {
72         Session session = sessionFactory.openSession();
73         Transaction transaction = session.beginTransaction();
74         user = (User) session.get(user.getClass(), user.getUserId());
75         user.setUsername("dadad");
76         session.update(user);
77         transaction.commit();
78         session.close();
79     }
80 }
View Code

  2 中咱們知道hibernate經過讀取配置文件和依靠反射去拼接對應的SQL語句,好比當hibernate執行session.get(xxx.class, xL)這個代碼的時候,在hibernate內部會拼接成一個SQL語句:緩存

select
user0_.userId as userId0_0_,
user0_.username as username0_0_,
user0_.password as password0_0_
from
user user0_
where
user0_.userId=?安全

要生成該SQL語句,必須找到數據庫對應的表,以及表中的字段,表中的主鍵。又由於session.get方法的第一個參數爲持久化類的class形式,去sessionFactory中查找該class對應的映射文件,找到該映射文件之後,映射文件中的class元素的namesession

屬性的值就是對應的持久化類,class元素的table屬性就是對應的表。這樣找到的。

 

  Hibernate對象的三種狀態

  • Transient 瞬時狀態:數據庫中沒有數據與之一一對應,id沒有歸入session的管理,沒有持久化標識(至關於主鍵),隨時都有可能被垃圾回收。
  • Persist 持久化狀態數據庫中有數據與之一一對應,id歸入了session的管理。特色:屬性與數據的改變,與數據庫中保持一致
  • Detached 託管狀態/遊離狀態沒有歸入session的管理,但數據在數據庫中存在。

下面是測試代碼:使用的JUnit4作單元測試

 1 package test.java;
 2 
 3 import dashuai.dao.UserDao;
 4 import dashuai.vo.User;
 5 import org.junit.After;
 6 import org.junit.Before;
 7 import org.slf4j.Logger;
 8 import org.slf4j.LoggerFactory;
 9 
10 /**
11  * Test
12  *
13  * @author Wang Yishuai.
14  * @date 2016/3/10 0010.
15  * @Copyright(c) 2016 Wang Yishuai,USTC,SSE.
16  */
17 public class Test {
18     private static final Logger LOG = LoggerFactory.getLogger(Test.class);
19 
20     private UserDao userDao;
21 
22     @Before
23     public void init() {
24         LOG.info("init");
25         this.userDao = UserDao.newInstance();
26     }
27 
28     @After
29     public void clear() {
30         LOG.info("clear");
31     }
32 
33     @org.junit.Test
34     public void testHibernate1() {
35         User user = new User();
36         user.setUsername("niubi");
37         user.setPassword("1");
38         // 此時的user對象是瞬時態的
39 
40         userDao.save(user);
41         // save到數據庫以後,user變爲持久態
42 
43         // 最後在save方法裏,最終執行 session.close();使得user變爲遊離態
44         user.setUsername("liuxiang");// 數據庫中的數據不會變
45     }
46 }
View Code

  小結:

  1. 瞬時狀態的對象沒有和hibernate發生交互,轉換爲持久態對象和hibernate容器發生交互,以後一直到事務提交,當session關閉以後

    ,對象脫離hibernate管理了,變爲遊離態。

  2. 瞬時狀態:

    1.new出來的對象,但沒有進行session.save();

    2.持久化對象調用delete()方法,持久態對象也會變成瞬時對象;

  3. 持久化對象:

    1.在數據庫中經過get(),load(),find()查詢出來的對象數據;

    2.瞬時的對象調用save();方法

    3.遊離態的對象調用update()方法;

  4. 託管/遊離態對象:

    1.手動構建遊離態對象;

    2.持久化對象調用evict()(evict方法能夠把一個對象從hibernate容器中去除掉),clear()(session.clear方法清空hibernate內部的全部對象),close()方法,可變爲遊離對象;

  如圖:

 

 

  使用getCurrentSession建立session

  爲何不推薦使用openSession方法,看一個例子:

  銀行的轉帳操做:一個數據庫的表account,以下:

如今把100塊從一個帳戶轉到另一個帳戶,代碼以下:

    public void delete(String account) {
        Account acc = (Account) session.createQuery("from Account where account = '" + account + "'").uniqueResult();
        Transaction transaction = session.beginTransaction();
        acc.setMoney(acc.getMoney() - 100);
        transaction.commit();
        session.close();
    }

    public void addMoney(String account) {
        Account acc = (Account) session.createQuery("from Account where account = '" + account + "'").uniqueResult();
        Transaction transaction = session.beginTransaction();
        acc.setMoney(acc.getMoney() + 100);
        transaction.commit();
        session.close();
    }
View Code

測試

    @org.junit.Test
    public void testAccount() {
        // 帳戶1
        Account account1 = new Account();
        account1.setAccount("1");
        account1.setAid(1);
        account1.setMoney(100.0);

        // 帳戶2
        Account account2 = new Account();
        account2.setAccount("2");
        account2.setAid(2);
        account2.setMoney(200.0);

//        accountDao.save(account1);
//        accountDao.save(account2);
//
        // 把2的帳戶的錢轉100到1帳戶
        // 先增長1帳戶100元
        accountDao.addMoney("1");
        // 刪除2帳戶100元
        accountDao.delete("2");
    }
View Code

這是轉帳以前的表,轉帳以後以下:

帳戶2的錢沒有少100,這確定不對。並且JUnit還報錯了:org.hibernate.SessionException: Session is closed!,說明當執行sessionFactory.openSession的時候,會建立一個新的session,再執行事務提交的時候,確定是一個新的事務提交了,說明上面的代碼中addMoney中的事務和delete的事務不是同一個事務,可是根據需求分析,這兩個方法必須在同一個事務中,若是在同一個事務中則必須在同一個session中!也就是說openSession每次都會建立一個新的session,這樣很是耗費內存,且有安全隱患。

  

  使用getCurrentSession

  先檢查當前線程中是否有session,若是當前線程中有session,則把session提取出來,直接使用,若是當前線程中沒有session,才用openSession方法建立session,而後把新建立的session放入到threadlocal中,當再次獲得session的時候就是從當前線程中獲取了。其實這很是相似享元設計模式的思想:

減少內存的佔用問題——享元模式和單例模式的對比分析

由於數據庫的crud操做必須在事務的條件下運行,而使用getCurrentSession建立session,當事務提交的時候,session自動關閉, 這種作法至關於把session和事務綁定在一塊兒了。

 

  再補充一下ThreadLoacl類的做用

  《Java併發編程實踐》上面說到:ThreadLoacl是一種線程封閉的實現策略。把變量封閉在線程中,使之變得安全。ThreadLocal並非一個Thread,而是Thread的局部變量,也許把它命名爲ThreadLocalVariable更容易讓人理解一些。使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。ThreadLocal是解決線程安全問題一個很好的思路,它經過爲每一個線程提供一個獨立的變量副本解決了變量併發訪問的衝突問題。在不少狀況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的併發性。

 

  getCurrentSession用法

  在hibernate.cfg.xml文件中增長配置:

 

  說明session從當前線程中獲取。代碼中使用無需關閉session事務,很是方便,推薦使用。

this.session = sessionFactory.getCurrentSession();

  下面比較二者的區別:

  1 getCurrentSession建立的session會綁定到當前線程,而openSession不會。

  2 getCurrentSession建立的線程會在事務回滾或事物提交後自動關閉,而openSession必須手動關閉

    這裏getCurrentSession使用本地事務(本地事務:jdbc)時 要在配置文件裏進行以下設置:

<property name="hibernate.current_session_context_class">thread</property>
  若是使用的是全局事務(jta事務),以下配置:
<property name="hibernate.current_session_context_class">jta</property>

  3.getCurrentSession () 使用當前的session,openSession() 從新創建一個新的session 

 

  使用getCurrentSession的優勢

  1)很是適合web程序,併發管理十分容易,session由線程產生,且可以保證一個線程老是隻有一個session。
  2)資源回收變得輕鬆。session將在commit()或rollback()後自動釋放,無需再手動關閉事務。
  3)事務管理十分直觀,通常來講在業務層或service層的方法先後用三個語句包住:
this.session = sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
transaction.commit();
View Code

就可讓該方法的原子性獲得保證。

  4)因爲3)的方式應用十分廣泛,用spring AOP 對 service 層進行事務控制就更簡單了,上面三行代碼甚至都沒必要寫。
 
注意:永遠不在DAO的方法內作開啓session、打開事務、提交事務、釋放session這些事,通常來講這不是什麼好習慣。通常交給Spring AOP 容器去作事務的管理。

 

  可否不使用事務保存對象

  Hibernate3.3爲了提倡你們使用事務,把默認的setAutoCommit設爲false,因此,不使用事務也能夠實現對象保存,只是Hibenate並不推薦這麼作。
相關文章
相關標籤/搜索