1. 程式人生 > >Hibernate使用及原始碼分析(一)

Hibernate使用及原始碼分析(一)

Hibernate使用及原始碼分析(一)

本篇文章主要通過hibernate初級使用分析一下原始碼,只是給初學者一點小小的建議,不喜勿噴,謝謝!

  • hibernate環境搭建
  • 簡單使用
  • 原始碼走讀

一 hibernate環境搭建

這裡直接介紹使用maven搭建

首先引入maven相關依賴

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId
>
junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- 新增Hibernate依賴 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId
>
<version>3.6.10.Final</version> </dependency> <!-- 新增Log4J依賴 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency
>
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.6.4</version> </dependency> <!-- 新增javassist --> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.0.GA</version> </dependency> <!-- mysql資料庫的驅動包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> </dependencies>

二 簡單使用

配置hibernate主配置檔案

<?xml version='1.0' encoding='utf-8'?>

<!--表明解析本XML檔案的DTD文件位置,DTD是DocumentType Definition 的縮寫,

即文件型別的定義,XML解析器使用DTD文件來檢查XML檔案的合法性。

hibernate.sourceforge.net/hibernate-configuration-3.0dtd可以在

Hibernate3.1.3軟體包中的src\org\hibernate目錄中找到此檔案-->

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <!--表明以下的配置是針對session-factory配置的,SessionFactory是  

    Hibernate中的一個類,這個類主要負責儲存HIbernate的配置資訊,以及對Session的

    操作-->

    <session-factory>

        <!--配置資料庫的驅動程式,Hibernate在連線資料庫時,需要用到資料庫的驅  
         動程式-->
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

        <!--設定資料庫的連線url:jdbc:mysql://localhost/hibernate,其中localhost表示mysql伺服器名稱,此處為本機,hibernate是資料庫名-->

        <property name="hibernate.connection.url">jdbc:mysql://localhost/hibernate</property>


        <!--連線資料庫是使用者名稱-->

        <property name="hibernate.connection.username">root</property>

        <!--連線資料庫是密碼-->

        <property name="hibernate.connection.password">root</property>

        <!--資料庫連線池的大小-->

        <property name="hibernate.connection.pool.size">20</property>

        <!--是否在後臺顯示Hibernate用到的SQL語句,開發時設定為true,便於差錯,程式執行時可以在Eclipse的控制檯顯示Hibernate的執行Sql語句。專案部署後可

         以設定為false,提高執行效率-->

        <property name="show_sql">true</property>


        <!--jdbc.fetch_size是指Hibernate每次從資料庫中取出並放到JDBC的Statement中的記錄條數。FetchSize設的越大,讀資料庫的次數越少,
        速度越快,Fetch Size越小,讀資料庫的次數越多,速度越慢-->

        <property name="jdbc.fetch_size">50</property>


        <!--jdbc.batch_size是指Hibernate批量插入,刪除和更新時每次操作的記錄數。BatchSize越大,
        批量操作的向資料庫傳送Sql的次數越少,速度就越快,同樣耗用記憶體就越大-->

        <property name="jdbc.batch_size">23</property>


        <!--jdbc.use_scrollable_resultset是否允許Hibernate用JDBC的可滾動的結果集。對分頁的結果集。對分頁時的設定非常有幫助-->

        <property name="jdbc.use_scrollable_resultset">false</property>


        <!--connection.useUnicode連線資料庫時是否使用Unicode編碼-->

        <!--<property name="Connection.useUnicode">true</property>-->


        <!--connection.characterEncoding連線資料庫時資料的傳輸字符集編碼方式,最好設定為gbk,用gb2312有的字元不全-->

        <!--<property name="connection.characterEncoding">gbk</property>-->


        <!--hibernate.dialect 只是Hibernate使用的資料庫方言,就是要用Hibernate連線那種型別的資料庫伺服器。-->

        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>

        <!--是否自動建立資料庫表 他主要有一下幾個值:

        validate:當sessionFactory建立時,自動驗證或者schema定義匯入資料庫。

        create:每次啟動都drop掉原來的schema,建立新的。

        create-drop:當sessionFactory明確關閉時,drop掉schema。

        update(常用):如果沒有schema就建立,有就更新。

        -->

        <property name="hbm2ddl.auto">update</property>


        <!--配置此處 sessionFactory.getCurrentSession()可以完成一系列的工作,當呼叫時,
        hibernate將session繫結到當前執行緒,事務結束後,hibernate
        將session從當前執行緒中釋放,並且關閉session。當再次呼叫getCurrentSession
        ()時,將得到一個新的session,並重新開始這一系列工作。-->
        <property name="current_session_context_class">thread</property>

        <!--<property name="hibernate.generate_statistics">true</property>-->

        <!--<property name="hibernate.connection.autocommit">true</property>-->

        <mapping resource="hibernate-hbm/User.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

建立一個實體類

public class User {
    public static final String TABLE_NAME = "t_user";
    private Long id;
    private String name;
    private Integer age;
    public User() {
    }
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

然後給實體類寫一個ORM對映檔案

<!-- 一樣,這個都可以在hibernate-core包裡面找到dtd頭部約束 -->
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.test.entity">
    <class name="User" table="t_user">
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name" column="t_name" type="java.lang.String"/>
        <property name="age" column="t_age" type="java.lang.Integer"/>
    </class>
</hibernate-mapping>

最後開始測試,這裡就使用了最簡單的save方法來方便閱讀原始碼

    @Test
    public void testSave() throws Exception {
        Configuration configuration = new Configuration().configure();
        SessionFactory factory = configuration.buildSessionFactory();
        Session session = factory.openSession();
        Transaction transaction = session.beginTransaction();
        session.save(new User("里斯", 18));
        transaction.commit();
        session.close();
        factory.close();
    }

三 根據測試方法閱讀原始碼。

1,建立Configuration物件

其中new Configuration()方法主要是初始化Configuration物件中的一些成員變數。

這裡寫圖片描述

這上面一個很重要的類Environment裡面都是我們在主配置檔案裡面配置的屬性的key值。
然後通過configure來讀取配置檔案。
這裡寫圖片描述
這裡寫圖片描述
sax解析配置檔案。
這裡寫圖片描述
把session-factory節點裡面的屬性新增給Configuration
這裡寫圖片描述

通過addProperties()方法將配置檔案中的property節點的屬性都載入到properties集合中。
從這個方法也可以看出來我們即使不加hibernate字首也沒有問題。
這裡寫圖片描述
通過parseSessionFactory()方法來讀取配置檔案中的mapping對映檔案等等。
當然還有監聽器,event等等,後面再分析。
這裡寫圖片描述
從讀取對映檔案的方法可以看出,我們是有多種配置對映檔案的方式的。
這裡寫圖片描述

2.buildSessionFactory()建立session工廠
這裡主要是建立了一個SessionFactoryImpl物件。
在他的構造方法裡面會進行建表。

Map<String,ClassMetadata> classMeta = new HashMap<String,ClassMetadata>();
classes = cfg.getClassMappings();
while ( classes.hasNext() ) {
   final PersistentClass model = (PersistentClass) classes.next();
   //根據mapping拼接建表語句
   model.prepareTemporaryTables( mapping, settings.getDialect() );
   final String cacheRegionName = cacheRegionPrefix + model.getRootClass().getCacheRegionName();
   // cache region is defined by the root-class in the hierarchy...
   EntityRegionAccessStrategy accessStrategy = ( EntityRegionAccessStrategy ) entityAccessStrategies.get( cacheRegionName );
   if ( accessStrategy == null && settings.isSecondLevelCacheEnabled() ) {
      final AccessType accessType = AccessType.parse( model.getCacheConcurrencyStrategy() );
      if ( accessType != null ) {
         log.trace( "Building cache for entity data [" + model.getEntityName() + "]" );
         EntityRegion entityRegion = settings.getRegionFactory().buildEntityRegion( cacheRegionName, properties, CacheDataDescriptionImpl.decode( model ) );
         accessStrategy = entityRegion.buildAccessStrategy( accessType );
         entityAccessStrategies.put( cacheRegionName, accessStrategy );
         allCacheRegions.put( cacheRegionName, entityRegion );
      }
   }
   。。。。
   //這些就是對應我們在配置檔案中配置的建表方式
   if ( settings.isAutoCreateSchema() ) {
   new SchemaExport( cfg, settings ).create( false, true );
}
//建立表
if ( settings.isAutoUpdateSchema() ) {
   new SchemaUpdate( cfg, settings ).execute( false, true );
}
if ( settings.isAutoValidateSchema() ) {
   new SchemaValidator( cfg, settings ).validate();
}
if ( settings.isAutoDropSchema() ) {
   schemaExport = new SchemaExport( cfg, settings );
}

3 開啟事務

public Transaction beginTransaction() throws HibernateException {
   errorIfClosed();
   if ( rootSession != null ) {
      // todo : should seriously consider not allowing a txn to begin from a child session
      //      can always route the request to the root session...
      log.warn( "Transaction started on non-root session" );
   }
   Transaction result = getTransaction();
   result.begin();
   return result;
}

4 save()方法
這裡寫圖片描述

save方法會建立一個saveorupdate物件,通過saveeventlistener回撥執行儲存操作。
也就是呼叫DefaultSaveOrUpdateEventListener的onSaveOrUpdate()方法。
public void onSaveOrUpdate(SaveOrUpdateEvent event) {
   final SessionImplementor source = event.getSession();
   final Object object = event.getObject();
   final Serializable requestedId = event.getRequestedId();
   if ( reassociateIfUninitializedProxy( object, source ) ) {
      log.trace( "reassociated uninitialized proxy" );
      // an uninitialized proxy, noop, don't even need to
      // return an id, since it is never a save()
   }
   else {
      //initialize properties of the event:
      final Object entity = source.getPersistenceContext().unproxyAndReassociate( object );
      event.setEntity( entity );
      event.setEntry( source.getPersistenceContext().getEntry( entity ) );
      //return the id in the event object
      event.setResultId( performSaveOrUpdate( event ) );
   }
}
最後呼叫到AbstractSaveEventListener.java的performSaveOrReplicate方法進行insert語句
if ( useIdentityColumn ) {
   EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
         values, entity, persister, source, shouldDelayIdentityInserts
   );

   if ( !shouldDelayIdentityInserts ) {
      log.debug( "executing identity-insert immediately" );
      source.getActionQueue().execute( insert );
      id = insert.getGeneratedId();
      key = new EntityKey( id, persister, source.getEntityMode() );
      source.getPersistenceContext().checkUniqueness( key, entity );
   }
   else {
      log.debug( "delaying identity-insert due to no transaction in progress" );

      source.getActionQueue().addAction( insert );
      key = insert.getDelayedEntityKey();
   }
}

5 最後提交事務

6 關於hibernate不開啟事務提交就無法儲存等操作的原因分析
還是使用上面save的案例。當我們執行save的時候,AbstractSaveEventListener在呼叫source.getActionQueue().execute( insert )方法的時候最終呼叫到AbstractBatcher的prepareStatement方法。

return getPreparedStatement(
                connectionManager.getConnection(),
                sql,
                false,
                getGeneratedKeys,
                null,
                null,
                false
        );

這個裡面會通過ConnectionManager獲取connection。

    /**
     * Pysically opens a JDBC Connection.
     *
     * @throws HibernateException
     */
    private void openConnection() throws HibernateException {
        if ( connection != null ) {
            return;
        }

        log.debug("opening JDBC connection");
        try {
            connection = factory.getConnectionProvider().getConnection();
        }
        catch (SQLException sqle) {
            throw JDBCExceptionHelper.convert(
                    factory.getSQLExceptionConverter(),
                    sqle,
                    "Cannot open connection"
                );
        }

        callback.connectionOpened(); // register synch; stats.connect()
    }

上面最終會呼叫到DriverManagerConnectionProvider裡面來獲取Connection物件。

public Connection getConnection() throws SQLException {

        if ( log.isTraceEnabled() ) log.trace( "total checked-out connections: " + checkedOut );

        synchronized (pool) {
            if ( !pool.isEmpty() ) {
                int last = pool.size() - 1;
                if ( log.isTraceEnabled() ) {
                    log.trace("using pooled JDBC connection, pool size: " + last);
                    checkedOut++;
                }
                Connection pooled = (Connection) pool.remove(last);
                if (isolation!=null) pooled.setTransactionIsolation( isolation.intValue() );
                if ( pooled.getAutoCommit()!=autocommit ) pooled.setAutoCommit(autocommit);
                return pooled;
            }
        }

        log.debug("opening new JDBC connection");
        Connection conn = DriverManager.getConnection(url, connectionProps);
        if (isolation!=null) conn.setTransactionIsolation( isolation.intValue() );
        if ( conn.getAutoCommit()!=autocommit ) conn.setAutoCommit(autocommit);

        if ( log.isDebugEnabled() ) {
            log.debug( "created connection to: " + url + ", Isolation Level: " + conn.getTransactionIsolation() );
        }
        if ( log.isTraceEnabled() ) checkedOut++;
        return conn;
    }

我們可以看到這裡面會通過setAutoCommint()方法設定事務提交。所以我們需要檢視這個autocommit是哪裡控制的。最後我們發現是在DriverManagerConnectionProvider的configure(Properties props)方法裡面進行賦值的

 autocommit = PropertiesHelper.getBoolean(Environment.AUTOCOMMIT, props);

    去Environment裡面檢視這個常量
     /**
     * JDBC autocommit mode
     */
    public static final String AUTOCOMMIT ="hibernate.connection.autocommit";

發現只有配置了hibernate.connection.autocommit為true,hibernate才會自動提交,否則預設是不會提交事務的,必須要我們自己手動提交事務。

好了,今天就到這裡,謝謝大家!!!