Hibernate核心原理解析

1.核心類加載配置文件

1.建立SessionFactory

 Configuration接口的做用是對Hibernate進行配置 以及對他進行啓動 在Hibernate的啓動過程當中 Configuration類的實例首先定位映射文檔的位置 讀取這些配置 而後建立一個SessionFactory對象java

    一個org.hibernate.cfg.Configuration實例表明了一個應用程序中Java類型到SQL數據庫映射的完整集合。Configuration被用來構建一個不可變的SessionFactory,映射定義則由不一樣的XML映射定義文件編譯而來。node

   Configuration有如下幾個方面的操做函數數據庫

1  爲Configuration指定映射文件編程

你能夠直接實例化Configuration來獲取一個實例 併爲他指定XML映射定義文件 若是映射定義文件在類路徑中 請使用addResource()設計模式

Configuration cfg = new Configuration().addResource("com/demo/hibernate/beans/User.hbm.xml");

2  爲Configuration指定持久化類緩存

一個替代的方法是指定被映射的類 讓hibernate幫你尋找映射定義文件安全

Configuration cfg = new Configuration().addClass(com.demo.hibernate.beans.User.class);

Hibernate將會在類路徑中需找名字爲 /com/demo/hibernate/beans/User.hbm.xml 映射定義文件 消除了任何對文件名的硬編譯服務器

3  爲Configuration指定配置屬性   Configuration也容許指定配置屬性session

Configuration cfg =new Configuration().addClass(com.demo.hibernate.beans.User.class)
.setProperty("hibernate.dialect","org.hibernate.dialect.MySQLInnoDBDialect")
.setProperty("hibernate.connection.datasource","java:comp/env/jdbc/test")
.setProperty("hibernate.order_update","true");

4  Configuration的三種加載方式app

在Hibernate的啓動與開發流程中 要使用一個Configuration 須要爲他設置三個方面的內容

數據庫鏈接屬性

hbm.xml文件 / POJO類

其中 第二個和第三個只須要設置一個 就會自動需找另外一個 由於這二者只需一個

第一種方式是使用hibernate.cfg.xml 該文件設置了數據庫鏈接的屬性和hbm.xml映射文件配置 hibernate會自動加載該配置屬性 並自動找到POJO 所以要取得Configuration對象 只須要簡單的建立改對象便可

Configuration cfg = new Configuration();
cfg.configuration("hibernate.cfg.xml");

第二種方式是經過hibernate.properties  省略 

第三種方式是徹底在構造時進行硬編碼設置 設置過程以下所示

Configuration cfg =new Configuration()
.addClass(com.demo.hibernate.beans.User.class)
.setProperty("hibernate.dialect","org.hibernate.dialect.MySQLInnoDBDialect")
.setProperty("hibernate.connection.datasource","java:comp/env/jdbc/test")
.setProperty("hibernate.order_update","true");

第一種方式是咱們最經常使用的方式

2.Hibernate加載基本的配置信息源碼淺析

咱們在獲取SessionFactory的時候,第一個語句就是:

Configuration configuration = new Configuration();

查看源碼可知,Configuration類的公共構造方法只有一個,而且是無參數的:

public Configuration() {  
    this( new SettingsFactory() );  
}

這個構造方法調用了一個受保護的構造方法:

protected Configuration(SettingsFactory settingsFactory) {  
    this.settingsFactory = settingsFactory;  
    reset();  
}

受保護的構造方法裏調用了私有方法reset,reset方法裏有一句這樣的代碼:

properties = Environment.getProperties();

這句代碼會從Hibernate環境裏去獲取hibernate配置,查看Environment類的方法getProperties

public static Properties getProperties() {  
    Properties copy = new Properties();  
    copy.putAll(GLOBAL_PROPERTIES);  
    return copy;  
}

這個方法獲得的是全局配置屬性的副本。再看看全局屬性GLOBAL_PROPERTIES是如何初始化的。Environment的靜態代碼塊裏有以下代碼:

InputStream stream = ConfigHelper.getResourceAsStream("/hibernate.properties");  
    try {  
        GLOBAL_PROPERTIES.load(stream);  
        log.info( "loaded properties from resource hibernate.properties: "   
                        + PropertiesHelper.maskOut(GLOBAL_PROPERTIES, PASS) );  
}

代碼跟蹤到這裏,咱們能夠知道hibernate首先必定會加載屬性配置文件hibernate.properties,並且此文件的路徑是寫死的。
若是用戶想用XML配置hibernate,就須要編寫以下代碼:

properties 是直接用構造方法 new Configuration(),這裏是生成後調用configure()

configuration.configure();

configure有五個重載方法:

看一下缺省的重載方法:

public Configuration configure() throws HibernateException {  
    configure( "/hibernate.cfg.xml" );  
    return this;  
}

它調用的是另外一個重載方法,加載的配置文件是寫死的。這五個方法最後調用同一個方法doConfigure(Document doc):

protected Configuration doConfigure(Document doc) throws HibernateException {  
        Element sfNode = doc.getRootElement().element( "session-factory" );  
        String name = sfNode.attributeValue( "name" );  
        if ( name != null ) {  
            properties.setProperty( Environment.SESSION_FACTORY_NAME, name );  
        }  
        addProperties( sfNode );  
        parseSessionFactory( sfNode, name );  
  
        Element secNode = doc.getRootElement().element( "security" );  
        if ( secNode != null ) {  
            parseSecurity( secNode );  
        }  
  
        log.info( "Configured SessionFactory: " + name );  
        log.debug( "properties: " + properties );  
  
        return this;  
}

看一下方法addProperties:

private void addProperties(Element parent) {  
        Iterator itr = parent.elementIterator( "property" );  
        while ( itr.hasNext() ) {  
            Element node = (Element) itr.next();  
            String name = node.attributeValue( "name" );  
            String value = node.getText().trim();  
            log.debug( name + "=" + value );  
            properties.setProperty( name, value );  
            if ( !name.startsWith( "hibernate" ) ) {  
                properties.setProperty( "hibernate." + name, value );  
            }  
        }  
        Environment.verifyProperties( properties );

能夠看到,若是配置屬性名不是以「hibernate」開頭會自動加上「hibernate.」,這就是爲何,咱們在用XML配置hibernate的時候,屬性名「hibernate.」能夠省去,可是在使用屬性文件或者編程方式配置時,「hibernate.」是不能省掉的。

hibernate加載配置的順序是:properties配置——》XML配置或者編程方式配置。至因而先加載XML配置仍是編程方式的配置,就要看用戶的語句順序了,可是有一點是肯定的:後加載的配置會覆蓋先加載的配置。

最後咱們也能夠看到,hibernate將基本的配置信息(不包括實體映射信息)保存到了configuration的properties成員屬性中。

獲取sessionFactory:

SessionFactory sessionFactory = configuration.buildSessionFactory();

3.SessionFactory建立Session

 

SessionFactory在Hibernate中實際上起到了一個緩衝區的做用 他緩衝了HIbernate自動生成SQL語句和其餘的映射數據 還緩衝了一些未來有可能重複利用的數據

    爲了能建立一個SessionFactory對象 應該在Hibernate初始化的時候建立一個Configuration類的實例 並將已經寫好的映射文件交給他處理 這樣Configuration對象就能夠建立一個SessionFactory對象 當SessionFactory對象建立成功後 Configuration對象就沒用用了 就能夠簡單的拋棄他

示例代碼:

Configuration cfg = new Configuration();
cfg.addResource("com/demo/hibernate/beans/User.hbm.xml");
cfg.setProperty(System.getProperties());
SessionFactory sessionFactory = cfg.buildSessionFactory();

SessionFactory用到了一個設計模式 工廠模式 用戶程序從工程類SessionFactory取得Session實例 設計者的意圖就是讓它能在整個應用中共享 典型的來講 一個項目一般只須要一個SessionFactory就夠了 所以咱們就設計了HibernateSessionFactory.Java這個輔助類 定義了一個靜態的Configuration和SessionFactory對象

private static final Configuration cfg = new Configuration();
private static org.hibernate.SessionFactory sessionFactory;

這兩個對象對整個應用來講只有一個實例存在 所以爲用戶的訪問定義一個本地線程變量

  1. private static final ThreadLocal threadLocal = new ThreadLocal();

該線程變量是靜態的 對每個訪問該線程的用戶產生一個實例 這樣在要取得Session對象時 首先從當前用戶的線程中取得Session對象 若是尚未建立 則從SessionFactory中建立一個Session 此時會判斷SessionFactory對象是否已經建立 該對象對這個應用來講 只有一個 所以 只有第一次訪問該變量的用戶纔會建立該對象

HibernateSessionFactory.java 取得Session對象的過程以下表示

public static Session currentSession() throws HibernateException {
        Session session = (Session) threadLocal.get();

        if (session == null) {
            if (sessionFactory == null) {
                try {
                    cfg.configure(CONFIG_FILE_LOCATION);
                    sessionFactory = cfg.buildSessionFactory();
                }
                catch (Exception e) {
                    System.err.println("%%%% Error Creating SessionFactory %%%%");
                    e.printStackTrace();
                }
            }
            session = sessionFactory.openSession();
            threadLocal.set(session);
        }

        return session;
    }

首先判斷threadLocal中是否存在Session對象 若是不存在 則建立Session對象 在建立Session對象時 首先要判斷系統是否已經加載Configuration 若是沒有sessionFactory 則須要先建立該對象 建立完成的Session對象 須要保存在threadLocal中以供本次訪問線程的下一次調用

在關閉Session對象是 只須要從當前線程中取得Session對象 關閉該對象 並置空本地線程變量便可

public static void closeSession() throws HibernateException {
        Session session = (Session) threadLocal.get();
        threadLocal.set(null);

        if (session != null) {
            session.close();
        }
    }

4.Hibernate核心接口

Hibernate做爲持久成中間件,它的具體實現對與上層調用是透明的,即上層經過接口來調用Hibernate的具體實現,因此對於入門級別的討論來講,天然應該先從接口開始了。

全部的Hibernate應用都會訪問它的5個核心接口,分別以下:

Configuration接口:

SessionFactory接口:

Session接口:

Transaction接口:

Query和Criteria接口:

----------------------------------

分別簡單介紹一下:

一、Configuration接口

Configuration用於配置並啓動Hibernate。Hibernate應用經過Configuration的實例來指定對象-關係映射文件,或經過Configuration動態配置Hibernate的屬性,而後經過Configuration來建立相應的SessionFactory實例。

 

二、SessionFactory接口

一個SessionFactory對應一個數據源,它是個重量級對象,不可隨意生成多個實例。對於通常的單數據庫應用來講,只須要一個SessionFactory就足夠了。固然若是有多個數據庫的話,仍是須要爲每一個數據庫生成對應的SessionFactory。它是線程安全的,同一個實例能夠被應用中的多個線程共享。

也許你會很好奇,SessionFactory爲何是重量級對象呢?我也一樣好奇,經過查看Hibernate的源碼,發現SessionFactory存放了大量預約義的SQL語句以及映射元數據,因此天然須要很大的緩存了,同時須要必定的CPU時間來計算生成。想一想Hibernate的這個設計是頗有意義的,由於有了Mapping文件,不少SQL語句就已經肯定了,只須要動態生成一次就能夠了,這個設計也是爲了提升持久化的效率。

 

三、Session接口

從SessionFactory中能夠得到Session實例。

Session接口是Hibernate應用中使用最普遍的接口了,它是持久化管理器,提供添加、更新、刪除、加載、查詢對象。Session不是線程安全的,因此應避免多個線程共享同一個Session實例。Session是輕量級對象,它的建立和銷燬不須要太多資源,這意味着在應用中能夠常常建立和銷燬Session對象。

Session有一個緩存,稱之爲Hibernate的一級緩存,它存放當前工做單元加載的持久化對象,每一個Session都有本身的緩存,緩存中的對象只能被當前工做單元訪問。

 

四、Transaction接口

Transaction是Hibernate的數據庫事務接口,它對底層道德事務接口進行了封裝,底層事務接口包括:

    JDBC API

    JTA(Java Transaction API)

    CORBA(Common Object Requet Broker Architecture) API

Hibernate應用能夠經過一致Transaction接口來聲明事務邊界,這有助於應用能夠在不一樣的環境或容器中移植。具體的事務實現使用在Hibernate.properties中進行指定。

 

五、Query和Criteria接口

這兩個是Hibernate的查詢接口,用於向數據庫查詢對象,以及控制執行查詢的過程。Query實例包裝了一個HQL(Hibernate Query Language)來查詢。Criteria接口徹底封裝了基於字符串形式的查詢語句,比Query更面向對象,Criteria更擅長執行動態查詢。

2.Hibernate鏈接池ConnectionProvider的初始化

Hibernate是如何初始化鏈接池的呢?先看下圖:

vider的初始化

 

Hibernate是如何初始化鏈接池的呢?先看下圖:

1.configuration實例化的時候,hibernate會去讀取配置信息,而且將基本的配置信息(不包括實體映射信息)保存到configuration的字段properties中

2.調用configuration的buildSessionFactory()方法,buildSessionFactory()方法又會調用SettingFactory的buildSettings(Properties props)方法。

3.buildSettings方法調用ConnectionProviderFactory的newConnectionProvider(Properties props)方法產生一個ConnectionProvider對象。

4.buildSettings方法實例化一個Setting對象,並將ConnectionProvider傳遞給Setting。

5.buildSettings返回一個Setting對象,而後buildSessionFactory()方法會實例化一個SessionFactoryImpl,而且將Setting對象傳給SessionFactoryImpl,最後返回SessionFactoryImpl。

configuration的buildSessionFactory()方法其實是實例化了一個鏈接池,而且把這個鏈接池交給SessionFactory管理。

ConnctionProvider是鏈接提供者,hibernate的數據庫鏈接都來自它。當咱們調用SessionFactoryImpl的openSession()方法時,就很容易得到數據庫鏈接了。

Hibernate爲ConnctionProvider提供瞭如下幾個實現類:

1.C3P0ConnectionProvider。從C3P0鏈接池中獲取數據庫鏈接。

2.ProxoolConnectionProvider。從Proxool鏈接池中獲取數據庫鏈接。

3.DatasourceConnectionProvider。通常是經過JNDI從應用服務器(如JBoss)中獲取數據源。

4.DriverManagerConnectionProvider。Hibernate自帶的鏈接池。

5.UserSuppliedConnectionProvider。沒有任何實現,只是拋出了異常。

到底使用哪個呢?看看ConnectionProviderFactory.newConnectionProvider(Properties props)方法就知道了:

public static ConnectionProvider newConnectionProvider(Properties properties, Map connectionProviderInjectionData)  
            throws HibernateException {  
        ConnectionProvider connections;  
        String providerClass = properties.getProperty( Environment.CONNECTION_PROVIDER );//hibernate.connection.provider_class  
        if ( providerClass != null ) {  
            connections = initializeConnectionProviderFromConfig( providerClass );  
        }  
        else if ( c3p0ConfigDefined( properties ) && c3p0ProviderPresent() ) {  
            connections = initializeConnectionProviderFromConfig("org.hibernate.connection.C3P0ConnectionProvider");  
        }  
        else if ( properties.getProperty( Environment.DATASOURCE ) != null ) {//hibernate.connection.datasource  
            connections = new DatasourceConnectionProvider();  
        }  
        else if ( properties.getProperty( Environment.URL ) != null ) {//hibernate.connection.url  
            connections = new DriverManagerConnectionProvider();  
        }  
        else {  
            connections = new UserSuppliedConnectionProvider();  
        }  
  
        ......  
        connections.configure( properties );  
        return connections;  
}

上面是核心代碼。

1.若是配置了hibernate.connection.provider_class屬性,就會根據指定的類去實例化。屬性值爲C3P0ConnectionProvider或者ProxoolConnectionProvider的全限定名。

2.若是配置了hibernate.connection.datasource屬性,則會實例化一個DatasourceConnectionProvider。屬性值爲JNDI名稱。

3.若是上面兩個都沒配置,則會去判斷是否配置hibernate.connection.url屬性。若是配置了,會實例化一個DriverManagerConnectionProvider。

4.若是沒有配置hibernate.connection.url屬性,則實例化一個UserSuppliedConnectionProvider,其實是拋出了異常。

咱們也能夠本身寫一個ConnctionProvider的實現,而後配置hibernate.connection.provider_class屬性,屬性值爲咱們本身的實現類的全限定名。

從上圖能夠看出,session有兩種方法獲取數據庫鏈接:

1.session.getJDBCContext().getConnectionManager().getConnection();

2.((SessionFactoryImplementor)session.getSessionFactory()).getConnectionProvider().getConnection();

不管是使用哪種方法,最終都是從sessionFactory的Settings中獲取ConnectionProvider,而後再從ConnectionProvider獲取connection。

相關文章
相關標籤/搜索