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。

相关文章
相关标签/搜索