Hibernate(2)——Hibernate的實現原理總結和對其模仿的demo

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

  • 開源框架的學習思路(我的總結)
  • Hibernate的運行原理總結
  • Hibernate實現原理中的兩個主要技術
  • Java的反射技術的原理
  • 反射的應用和例子
  • 反射的缺點
  • 編寫一個模擬Hibernate的demo(V1.0版本)
  • 後續的模擬ORM框架的設計思路

 

  開源框架的學習思路(我的經驗,歡迎提出意見)git

  框架是爲了解決開發中遇到的一個個問題而誕生的,程序員是爲了解決問題而學習框架的,這纔是正確的學習之道!一個框架的好與壞徹底取決於其對問題解決程度和解決方式。我的的學習過程:程序員

  • 瞭解框架能解決的問題(爲何使用這個框架,使用先後的差別),並先瞭解其實現原理(看悟性和基礎)
  • 閱讀開源框架自帶的幫助文檔(結合一些參考書和網絡的搜索去理解本身不懂的地方,實在不行再問高手,不然學不會思考)
  • 很重要的一步:必須親手搭建環境,能夠運行開源框架自帶的示例項目,也能夠本身寫一個demo,讓它跑起來!不能紙上談兵!
  • 親自寫實際的Demo體驗,帶着問題研究開源框架的源代碼,並總結收穫。

 

   介紹一下Hibernate的實現原理?
  上一篇文章說了,使用Hibernate的通常步驟是:
  1. 建立配置文件,和實體類,並使用Configuration的對象去生成SessionFactory對象,(默認)加載hibernate.cfg.xml這個核心配置文件,該核心文件裏包含了對數據源的鏈接和一些屬性的設置等,固然還包含有映射實體關係配置文件
1 // 經過new一個Configuration實例,而後用該實例去調用configure返回一個配置實例
2         Configuration configuration = new Configuration().configure();
3         // 經過 配置實例的buildSessionFactory方法 生成一個 sessionFactory 對象
4         // buildSessionFactory方法會默認的去尋找配置文件hibernate.cfg.xml並解析xml文件
5         // 解析完畢生成sessionFactory,負責鏈接數據庫
6         SessionFactory sessionFactory = configuration.buildSessionFactory();
View Code

  2.  生成的SessionFactory,等因而能夠獲取數據庫的鏈接(能建立session),從而能夠操做數據庫github

  3.  由於核心配置Hibernate.cfg.xml裏引入了實體關係映射配置文件,故該文件也會自動被解析——加載對象-關係映射文件:vo類.hbm.xmlweb

  四、而後是建立session對象,經過SessionFactory建立session。session能夠操做數據庫sql

// 經過 sessionFactory 得到一個數據庫鏈接 session,能夠操做數據庫
        Session session = sessionFactory.openSession();
View Code

  5.  開啓事務,也是經過session開啓數據庫

// 把操做封裝到數據庫的事務,則須要開啓一個事務
        Transaction transaction = session.beginTransaction();
View Code

  6.  調用session API,CRUD 對象設計模式

// 通常把對實體類和數據庫的操做,放到try-catch-finally塊
        try {
            User user = new User();
            user.setUserId(22);
            user.setUsername("dashuai");
            user.setPassword("123456");
            // 把user對象插入到數據庫
            session.save(user);
            // 提交操做事務
            transaction.commit();
            LOG.info("transaction.commit(); ok");
        } catch (Exception e) {
            // 提交事務失敗,必需要回滾
            transaction.rollback();
            // 打印日誌
            LOG.error("save user error......", e);
        } finally {
            // 不能丟這一步,要釋放資源
            if (session != null) {
                session.close();
                LOG.info("session.close(); ok");
            }
        }
View Code

  7.  根據Dialect(以前在覈心配置文件配置的數據庫方言)生成和底層數據庫平臺相關的sql代碼數組

  8.  對JDBC封裝,執行sql腳本。
 
  從本質上而言,Hibernate最終仍是經過JDBC去操做數據庫。只是對JDBC進行了封裝。

  

  Hibernate實現原理中使用的技術有什麼?緩存

  針對主流的XML文件配置方式,Hibernate實現原理中使用的關鍵技術主要有兩個。一是對XML文檔的解析——使用DOM(文檔對象模型)/SAX解析,Hibernate使用了常見的開源解析工具——dom4j(使用Java編寫,很流行),二是Java的反射技術,好比我能夠經過一個Java類的對象,經過反射機制來獲取這個對象的類的屬性,方法……簡單說,就相似我本身照鏡子,經過鏡子,我能夠看清楚我本身身體的各個部位。

  固然了,還有基於註解的方式,那麼就還要使用Java的註解技術,本質上大同小異,熟能生巧。

 

  Java反射技術淺析

  先知道一個概念——動態語言:程序運行時,容許改變程序結構或變量類型—這種語言稱爲動態語言,從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C#不是動態語言,可是Java有着一個很是突出的動態相關機制:Reflection,也就是反射機制。使用Java的反射機制,能夠在Java程序運行狀態中,對於任意一個類,都可以知道其全部屬性和方法,對於任意一個對象,都可以調用其任意一個方法,咱們管這種動態獲取信息以及動態調用對象的方法的功能叫作java語言的反射機制。在1982年由Smith正式提出。
 
  Java反射的功能:
  •在運行時判斷任意一個對象所屬的類
  •在運行時構造任意一個類的對象  
  •在運行時判斷任意一個類所具備的成員變量和方法
  •在運行時調用任意一個對象的方法
  •生成動態代理

  大白話就是:Java反射機制可讓程序員在程序的運行期(Runtime)檢查類,接口,變量以及方法的信息,而檢查Java類的信息每每是在使用Java反射機制的時候所作的第一件事情,經過獲取類的信息能夠獲取如下相關的內容:Class對象,類名,修飾符,包信息,父類,實現的接口,構造器,方法,變量,註解……除了這些內容,還有不少的信息能夠經過反射機制得到(查閱API便可)。進一步反射還可讓程序員在運行期實例化對象,調用類的方法,經過調用get/set方法獲取變量的值等,因此,Java的反射機制功能很是強大並且很是實用。舉個例子,我能夠用反射機制把Java對象映射到數據庫表(Hibernate的實現機制之一),或者把腳本中的一段語句在運行期映射到相應的對象調用方法上,就如解析配置腳本時所作的那樣。

 

  Java反射機制的原理

    這涉及到了Java的類加載機制和原理,稍後會專題總結。這裏簡單說下,在說原理以前,必須先知道Java中通常常常用Class.forName(classname)來反射類。在以前的幾篇學習JVM總結隨筆中也有部分說到:JVM裝載某類時,類裝載器會定位相應的class文件,而後將其讀入到虛擬機中,並提取class中的類型信息,而Java中類的信息通常咱們認爲是存儲到JVM的方法區中了。

  Java反射機制中涉及的類:

  • Class:類的實例,表示正在運行的 Java 應用程序中的類和接口,這個是反射機制中最關鍵的一點,可使用Object類的getClass()方法,Class類的getSuperClass()方法,Class類的靜態forName()方法,對於包裝器類型,經過類名.TYPE屬性獲得類的Class類型信息。由於Class 類十分特殊,其實例用以表達Java程序運行時的類和接口。
  • Field:提供有關類或接口的屬性的信息,以及對它的動態訪問權限
  • Constructor:提供關於類的單個構造方法的信息以及對它的訪問權限
  • Method:提供關於類或接口上單獨某個方法的信息

  看個demo,新建一個類:dashuai.generics.Dog。該類作爲咱們的實驗類,經過反射機制建立該類的對象,並經過反射機制調用該類中的speak方法。

1 public class Dog{
2     public void speak(String str) {
3         System.out.println("Dog speak! 汪汪" + str);
4     }
5 }
View Code

  main方法裏經過反射機制建立Dog類的對象,並調用其方法

public class Main {
    public static void main(String[] args) {
        Object obj = null;

        try {
            Class clazz = Class.forName("dashuai.generics.Dog");
            obj = clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        Dog dog = (Dog) obj;
        dog.speak(」Hi」);
    }
}
View Code

  上面代碼中,dog對象是經過Class類的forName方法建立的,再調用該對象的speak方法,在控制檯打印一行字符串。註釋上面代碼最後一行:dog.speak(),我使用反射來調用dog對象的speak()方法。該類完整代碼以下:

 1 public class Main {
 2     public static void main(String[] args) {
 3         Object obj = null;
 4 
 5         try {
 6             Class clazz = Class.forName("dashuai.generics.Dog");
 7             obj = clazz.newInstance();
 8         } catch (ClassNotFoundException e) {
 9             e.printStackTrace();
10         } catch (InstantiationException e) {
11             e.printStackTrace();
12         } catch (IllegalAccessException e) {
13             e.printStackTrace();
14         }
15 
16         Dog dog = (Dog) obj;

17         Class<?>[] parameterTypes = new Class<?>[1];
18         parameterTypes[0] = String.class;
19 
20         try {
21             Method method = dog.getClass().getDeclaredMethod("speak", parameterTypes);
22             method.invoke(dog, new Object[] { "Hi" });
23         } catch (SecurityException e1) {
24             e1.printStackTrace();
25         } catch (NoSuchMethodException e1) {
26             e1.printStackTrace();
27         } catch (IllegalArgumentException e) {
28             e.printStackTrace();
29         } catch (IllegalAccessException e) {
30             e.printStackTrace();
31         } catch (InvocationTargetException e) {
32             e.printStackTrace();
33         }
34     }
35 }
View Code

  經過java.lang.reflect.Method類來構建方法,再經過invoke方法執行dog對象的speak方法。

 

  簡單說說反射的執行過程

  JVM裝載類的目的就是把Java字節代碼轉換成JVM中的java.lang.Class類的對象。這樣Java就能夠對該對象進行一系列操做,而上面的例子:Class.forName(classname)方法,其實是調用了Class類中的 Class.forName(classname, true, currentLoader)方法。參數:name - 所需類的徹底限定名;initialize - 是否必須初始化類;loader - 用於加載類的類加載器。currentLoader則是經過調用ClassLoader.getCallerClassLoader()獲取當前類加載器的。類要想使用,必須用類加載器加載,因此須要加載器。

  還有一點:反射機制不是每次都去從新反射,而是提供了緩存,每次都須要類加載器去本身的緩存中查找,若是能夠查到,則直接返回該類。Java類加載器大致分兩類:前三者是一類,分爲BootStrap Class Loader(引導類加載器),Extensions Class Loader (擴展類加載器),App ClassLoader(或System Class Loader系統類加載器),最後一個是另外一類叫用戶自定義類加載器。

  類的加載過程有兩個比較重要的特徵:層次組織結構和代理模式

  層次組織結構指的是每一個類加載器都有一個父類加載器(除了引導類加載器以外),經過getParent()方法能夠獲取到。類加載器經過這種父親-後代的方式組織在一塊兒,造成樹狀層次結構。系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器,對於開發人員編寫的類加載器來講,其父類加載器是加載此類加載器 Java 類的類加載器。由於類加載器 Java 類如同其它的 Java 類同樣,也是要由類加載器來加載的。通常來講,開發人員編寫的類加載器的父類加載器是系統類加載器。類加載器經過這種方式組織起來,造成樹狀結構。樹的根節點就是引導類加載器。如圖:

  代理模式則指的是一個類加載器既能夠本身完成Java類的定義工做,也能夠代理給其它的類加載器來完成。因爲代理模式的存在,啓動一個類的加載過程的類加載器和最終定義這個類的類加載器可能並非一個。

 

  Java類的加載過程:

  1.經過類的全名產生對應類的二進制數據流。(若是沒找到對應類文件,只有在類實際使用時才拋出錯誤。)

  2.分析並將這些二進制數據流轉換爲方法區特定的數據結構(這些數據結構是實現有關的,不一樣 JVM 有不一樣實現)。這裏處理了部分檢驗,好比類文件的魔數的驗證,檢查文件是否過長或者太短,肯定是否有父類(除了 Obecjt 類)。

  3.建立對應類的 java.lang.Class 實例(注意,有了對應的 Class 實例,並不意味着這個類已經完成了加載!)。

  而JVM在整個加載過程當中,會先檢查類是否被已加載,檢查順序是自底向上,從系統類加載器到引導類加載器逐層檢查,只要某個classloader已加載就視爲已加載此類,保證此類只被全部ClassLoader加載一次。

  可是加載的順序是自頂向下和檢測順序反着,屬於父類優先的順序),也就是由上層來逐層嘗試加載此類。類加載器的詳細介紹後續專題總結。

  只說一點,ClassLoader的加載類過程主要使用loadClass方法,該方法中封裝了加載機制:雙親委派模式在forName方法中,就是調用了ClassLoader.loadClass方法來完成類的反射的,正如前面說的,JVM先檢查本身是否已經加載過該類,若是加載過,則直接返回該類,若沒有則調用父類的loadClass方法,若是父類中沒有,則執行findClass方法去嘗試加載此類,也就是咱們一般所理解的片面的"反射"了。

  這個過程主要經過ClassLoader.defineClass方法來完成。defineClass 方法將一個字節數組轉換爲 Class 類的實例(任何類都是Class類的對象,在Java中,每一個class都有一個相應的Class對象,也就是說,當咱們編寫一個類.java文件,編譯完成後,在生成的.class文件中,就會產生一個Class對象,用於表示這個類的類型信息,既一切皆是對象)。這種新定義的類的實例須要使用 Class.newInstance 來建立,而不能使用new來實例化。

  大白話:運行期間,若是咱們要產生某個類的對象,JVM 會檢查該類型的Class對象是否已被加載。若是沒有被加載,JVM會根據類的名稱找到.class文件並加載它。一旦某個類型的Class對象已被加載到內存,就能夠用它來產生該類型的全部對象。
  

  再ps點:類加載器的用途

  類加載器除了加載類信息,獲取類信息以外,還有一個重要用途是在JVM中爲相同名稱的Java類建立隔離空間。在JVM中,判斷兩個類是否相同,不只是根據該類的二進制名稱,還須要根據兩個類的定義類加載器。只有二者徹底同樣,才認爲兩個類的是相同的。所以,即使是一樣的Java字節代碼,被兩個不一樣的類加載器定義以後,所獲得的Java類也是不一樣的。若是試圖在兩個類的對象之間進行賦值操做,會拋出java.lang.ClassCastException。這個特性爲一樣名稱的Java類在JVM中共存創造了條件。在實際的應用中,可能會要求同一名稱的Java類的不一樣版本在JVM中能夠同時存在。經過類加載器就能夠知足這種需求。這種技術在OSGi中獲得了普遍的應用。

 

  反射的應用

  • 操做數據庫,動態建立SQL語句
  • 解析XML,properties配置文件,動態生成對象
  • Java的動態代理
  • 框架中使用的最多:Struts框架、Spring框架、Hibernate框架、MyBatis框架……

  反射的缺點

  主要的缺點是對性能有影響,說白了就是速度執行慢!由於使用反射基本上是一種解釋操做,能夠告訴JVM我但願作什麼而且讓它知足個人要求。這類操做老是慢於直接執行相同的操做。
 
   小結:在Java中,萬事萬物皆對象,好比其實方法也有類型(java.lang.reflect.Method),也是對象。
 
 
   實現Hibernate的demo程序(V1.0版本)
   很是簡陋,初版的編寫目的就是學習原理用的。主要模擬實現的是程序中的對象在關係數據庫中的保存過程。很簡單的邏輯,編碼以前必要的一步是把Dom4j這個jar包引入,固然還有數據庫鏈接等一些經常使用jar包。
  1. 仿照Hibernate,也在項目根下定義個xml配置文件,個人叫Students.xml文件
  2. 再定義一個實體類Students,對照數據庫生成對應的字段和set,get方法。
  3. 建立一個Session接口,其中使用dom4j解析xml配置文件,解析的數據保存到變量中,核心是解析讀取類的對象的屬性和二維表的字段的對應關係,我使用map對象保存。以後讀取map對象,經過字符串的拼接等技巧,拼接一個插入數據到表的SQL語句—— insert into students(sname,sid) values (?,?)
  4. 編寫一個save方法,參數傳入須要保存的對象,好比student,這裏面使用反射技術獲得對象的類信息,再經過以前解析配置文件而獲得的get方法名集合,利用反射得出get的返回類型,以後經過返回類型的判斷結果,得出咱們須要插入到數據庫的數據是什麼,再利用反射調用get方法,獲得恰當的數據,依靠jdbc把數據保存到數據庫。這裏本質是利用反射對jdbc進行封裝,這裏固然是比較基礎的封裝,沒有涉及複雜的功能和其餘可能的映射情景。
  5. 使用了log4j記錄日誌
 
代碼運行ok的結果:

2016-03-08 22:28:51,424 | INFO | main | dao.Session.save(Session.java:188) | save
2016-03-08 22:28:51,428 | INFO | main | dao.Session.save(Session.java:190) | SQL: insert into students(sname,sid) values (?,?)

Process finished with exit code 0

 

V1.0版本的代碼已經上傳到Github,地址: https://github.com/dashuai888/ORMV1.0
 
 
   ORM框架的設計思路

  打算一步步在總結框架的時候完善和重構一個能用的ORM框架。 

  設計思路:

  第一點:鏈接數據庫。第一種是JDBC鏈接,第二種是採用數據源來鏈接(採用數據源鏈接的時候,能夠採用任何的數據源,c3p0,dbcp。)。

  第二點:操做數據:添加數據,刪除數據,修改數據。

  第三點:查詢數據。

 

  開發思路

  • 當JVM啓動的時候,首先讀取配置文件(讀取是一個效率很低的過程,須要考慮性能,避免重複讀取)。解析的數據變量是一個Map,Map中包含的數據類型,不是簡單的數據類型,而是對配置信息進行了封裝之後的對象。當讀取了配置文件以後,我就會知道是否配置了數據源,若是配置了數據源,那麼就把配置的參數獲取到,而後實例化,在獲得數據庫鏈接的時候,採用數據源來獲取鏈接。若是沒有配置數據源,那麼就採用jdbc來鏈接。當兩種配置都存在的時候,確定是採用數據源來鏈接。 
  • save(Object o):插入數據,insert into 表名(字段1,字段2,……) values(值1,值2,……),save方法當中,必須拼接一條SQL語句,而後放到數據庫中執行。

  問題:拼接SQL語句的時候,表名從哪裏來?字段從哪裏來?值從哪裏來?

  <class name=」User」 table=」user」>,name就表明了user這張表。table標籤對應的值,就是表名。咱們在配置中,會對每個字段進行配置,那麼我固然能夠取到字段的名字。最重要的是,值是怎麼來的。前面說了,利用Java所提供的反射機制來獲取

  • update,del更新,刪除,這兩個方法與上面的save方法相似。update方法的SQL語句是:update 表名 set 字段1=值1, 字段2=值2 where 條件,del對應的SQL語句:delete from 表名 where 條件。

  問題:若是一個表中有4個字段,我只需修改1個字段,那麼在修改的時候,只是針對於這一個字段給實體對象賦值,這個對象的其餘的字段屬性,都是null,這個時候,怎麼樣纔可以只修改對應的字段?

  • 查詢數據:查詢是最困難的!

  拼接一條SQL語句,好比說:form User。可是,這條語句數據庫是不認識的。數據庫認識的是這樣的:select * from 表名。固然,我也能夠添加一些條件。把拼接好的SQL語句,放到關係數據庫中取執行,獲得的是結果集:ResultSet。這個方法,返回給用戶的是一個List,是一個直接可使用的列表,可是這個列表中會有不少不少的對象,每個對象,又都有對應的值。當拿到結果集之後,遍歷結果集,而後根據上下文(好比表),把查詢出來的值,利用反射的方法設置到對象中,再把對象添加到列表中,最後返回列表。

  這個方法中有不少的細節須要處理:好比說,當數據庫中的表,不是單一的表,是有鏈接關係的時候,拼接SQL語句會比較麻煩,並且在添加數據到列表中的時候,須要進行的處理也會特別的多。還有條件查詢的情景……

  • 分頁的處理。
  • 查詢的默認配置選擇(無鏈接的查詢),懶加載的設置。
  • 緩存的設計(也是一個難點):想到可使用靜態的map變量來模擬緩存功能,利用多線程控制緩存,還想到Java的軟引用是否是也能實現ORM的緩存?
  • 要複合OOP的原則,儘可能的抽象,抽象的層次越高,複用性就越強。
  • 對一些設計模式的應用,好比單例模式,動態代理,享元模式,工廠模式,命令模式等。
相關文章
相關標籤/搜索