學習Hibernate原始碼三_Hibernate中的配置檔案解析

bsr1983發表於2013-09-02

本節要學習一下Hibernate的配置檔案的具體載入、解析的過程,以及涉及到的相關程式碼,思路是建立一個簡單的java專案,配置一個hbm檔案,啟動後,跟蹤除錯載入解析hbm的過程,學習相關的程式碼。

      搭建專案後,將所需jar放入java專案的lib目錄,在Hibernate的手冊中說明此處也可以使用Maven來設定依賴jar,我這裡還是使用比較原始的方式。直接建立一個lib目錄放置所需要的jar包,然後設定classpath即可。

      參考Hibernate手冊中所說的,解釋一下.hbm中幾個具體配置項的相關注意事項。

      1)關於property元素

      property中包含nametypecolumn 3個常用的屬性。

   如:

 

<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
   name屬性用來設定訪問對應的對映類中對應屬性值的gettersetter方法,有時候可以只配置一個name屬性,typecolumn可以省略。上述例子中的type並不是java的資料型別,也不是SQL資料庫的型別,而是被稱為hibernate對映型別,它是用來在Java資料型別和SQL資料型別之間做轉換的。如果type屬性未定義,則Hibernate會嘗試確定正確的對映型別來進行轉換。在某些情況下,這種使用java類檔案反射的自動檢測可能沒有你所期望和需要的型別,例如上述的date屬性,Hibernate不能確定這裡的java.util.Date應該對映為SQL的哪種型別,是datetimestamp還是time?因此此處使用了一個timestamp來指定對應的是一個包含日期和時間資訊的屬性。

注意:Hibernate在處理對映檔案時會根據反射來設定對應的對映型別,這將會耗費一定的時間和資源,如果你的應用對啟動的效能非常在意,那麼你就要考慮精確的定義要使用的型別。

      此處我使用的是mysql資料庫,並不是手冊中的HSQLDB,我建立了一個UserInfo表,具體的配置檔案如下:

         

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/hibernatecode</property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <mapping resource="com/ibsrapp/hibernatecode/domain/UserInfo.hbm.xml" />
    </session-factory>
</hibernate-configuration>

 

 

接著是資料庫表UserInfo的對映檔案

 

<?xml version="1.0"?>
<!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.ibsrapp.hibernatecode.domain">
    <class name="UserInfo" table="userinfo">
         <id name="id" type="java.lang.Integer">
            <column name="id" />
            <generator class="identity" />
        </id>
        <property name="name" type="java.lang.String" length="255" column="name"/>
        <property name="password" type="java.lang.String" length="255" column="password"/>
        <property name="birthday" type="java.util.Date" column="birthday" />
    </class>

 

 

最後是執行所需的類的主方法:

 

public static void main(String[] args) {
      // TODO Auto-generated method stub
      //建立配置物件
      Configuration configuration = new Configuration();
      //呼叫預設配置方法
      configuration.configure();
      //註冊服務
      ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
            .applySettings(configuration.getProperties())
            .buildServiceRegistry();
      //根據所註冊的服務建立sessionFactory
      SessionFactory sessionFactory = configuration
            .buildSessionFactory(serviceRegistry);
      UserInfo user = new UserInfo();
      user.setName("ibsrapp");
      user.setPassword("ibsrapp");
      user.setBirthday(new Date());
      //獲取一個session
      Session session = sessionFactory.openSession();
      Transaction trans = session.beginTransaction();
      session.save(user);
      trans.commit();
      session.close();
      sessionFactory.close();
      System.out.print("save Success");
  }

 

 

接著按照main方法中的程式碼進行debug,看看將一個物件儲存到資料庫中所需的步驟和涉及的相關程式碼。

首先是

 

//建立配置物件
Configuration configuration = new Configuration();
//呼叫預設配置方法
configuration.configure();

 

 

檢視Configurationconfigure()方法,程式碼如下:

 

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

 

 

通過這個方法,我們就能明白為什麼配置檔案的名稱預設是hibernate.cfg.xml而且預設是放在src目錄下了。

接著看一下configure( "/hibernate.cfg.xml" );所對應的方法

 

public Configuration configure(String resource) throws HibernateException {
      LOG.configuringFromResource( resource );
      InputStream stream = getConfigurationInputStream( resource );
      return doConfigure( stream, resource );
  }

 

 

此處最終還是呼叫了doConfigure( stream, resource );程式碼如下

核心程式碼為:

 

Document document = xmlHelper.createSAXReader( errorLogger,  entityResolver )
                .read( new InputSource( stream ) );
         if ( errorLogger.hasErrors() ) {
            throw new MappingException( "invalid configuration", errorLogger.getErrors().get( 0 ) );
         }
         doConfigure( document );

 

 

即將所傳遞的流轉換為一個org.dom4j.Document物件,然後呼叫

 

protected Configuration doConfigure(Document doc) throws HibernateException {
//獲取根元素下(hibernate-configuration)的session-factory子節點      
      Element sfNode = doc.getRootElement().element( "session-factory" );
      String name = sfNode.attributeValue( "name" );
      if ( name != null ) {
         properties.setProperty( Environment.SESSION_FACTORY_NAME, name );
      }
      addProperties( sfNode );
      //處理session-factory子節點
      parseSessionFactory( sfNode, name );
 
      Element secNode = doc.getRootElement().element( "security" );
      if ( secNode != null ) {
         parseSecurity( secNode );
      }
 
      LOG.configuredSessionFactory( name );
      LOG.debugf( "Properties: %s", properties );
 
      return this;
   }

 

 

其中用於處理session-factory子節點的方法如下:

可以看到mapping屬性所指明的對映檔案,以及class-cache之門的類粒度級別的快取以及collection-cache指明的集合粒度級別的快取都有對應的處理方法。

 

private void parseSessionFactory(Element sfNode, String name) {
      Iterator elements = sfNode.elementIterator();
      while ( elements.hasNext() ) {
         Element subelement = (Element) elements.next();
         String subelementName = subelement.getName();
         if ( "mapping".equals( subelementName ) ) {
            parseMappingElement( subelement, name );
         }
         else if ( "class-cache".equals( subelementName ) ) {
            String className = subelement.attributeValue( "class" );
            Attribute regionNode = subelement.attribute( "region" );
            final String region = ( regionNode == null ) ? className : regionNode.getValue();
            boolean includeLazy = !"non-lazy".equals( subelement.attributeValue( "include" ) );
            setCacheConcurrencyStrategy( className, subelement.attributeValue( "usage" ), region, includeLazy );
         }
         else if ( "collection-cache".equals( subelementName ) ) {
            String role = subelement.attributeValue( "collection" );
            Attribute regionNode = subelement.attribute( "region" );
            final String region = ( regionNode == null ) ? role : regionNode.getValue();
            setCollectionCacheConcurrencyStrategy( role, subelement.attributeValue( "usage" ), region );
         }
      }
  }

 

 

用於處理mapping對映檔案的方法如下:

 

private void parseMappingElement(Element mappingElement, String name) {
      final Attribute resourceAttribute = mappingElement.attribute( "resource" );
      final Attribute fileAttribute = mappingElement.attribute( "file" );
      final Attribute jarAttribute = mappingElement.attribute( "jar" );
      final Attribute packageAttribute = mappingElement.attribute( "package" );
      final Attribute classAttribute = mappingElement.attribute( "class" );
 
      if ( resourceAttribute != null ) {
         final String resourceName = resourceAttribute.getValue();
         LOG.debugf( "Session-factory config [%s] named resource [%s] for mapping", name, resourceName );
         addResource( resourceName );
      }
      else if ( fileAttribute != null ) {
         final String fileName = fileAttribute.getValue();
         LOG.debugf( "Session-factory config [%s] named file [%s] for mapping", name, fileName );
         addFile( fileName );
      }
      else if ( jarAttribute != null ) {
         final String jarFileName = jarAttribute.getValue();
         LOG.debugf( "Session-factory config [%s] named jar file [%s] for mapping", name, jarFileName );
         addJar( new File( jarFileName ) );
      }
      else if ( packageAttribute != null ) {
         final String packageName = packageAttribute.getValue();
        LOG.debugf( "Session-factory config [%s] named package [%s] for mapping", name, packageName );
         addPackage( packageName );
      }
      else if ( classAttribute != null ) {
         final String className = classAttribute.getValue();
         LOG.debugf( "Session-factory config [%s] named class [%s] for mapping", name, className );
         try {
            addAnnotatedClass( ReflectHelper.classForName( className ) );
         }
         catch ( Exception e ) {
            throw new MappingException(
                   "Unable to load class [ " + className + "] declared in Hibernate configuration <mapping/> entry",
                   e
            );
         }
      }
      else {
         throw new MappingException( "<mapping> element in configuration specifies no known attributes" );
      }
  }

 

 

可以看到這裡的資源可以是以下5種型別中的一種resourcefilejarpackageclass對於每種資源這裡都有不同的載入方式,

檢視每一類資源對應的載入方法,最終會發現他們還是會以一種輸入流的方式載入到一個XmlDocument物件中,然後呼叫下面的方法,將對應的類和資料表進行對映,並將其新增到metadataSourceQueue這個佇列之中。

 

public void add(XmlDocument metadataXml) {
      if ( inSecondPass || !isOrmXml( metadataXml ) ) {
         metadataSourceQueue.add( metadataXml );
      }
      else {
         final MetadataProvider metadataProvider = ( (MetadataProviderInjector) reflectionManager ).getMetadataProvider();
         JPAMetadataProvider jpaMetadataProvider = ( JPAMetadataProvider ) metadataProvider;
         List<String> classNames = jpaMetadataProvider.getXMLContext().addDocument( metadataXml.getDocumentTree() );
         for ( String className : classNames ) {
            try {
                metadataSourceQueue.add( reflectionManager.classForName( className, this.getClass() ) );
            }
            catch ( ClassNotFoundException e ) {
                throw new AnnotationException( "Unable to load class defined in XML: " + className, e );
            }
         }
      }
  }

 

通過呼叫JPAMetadataProvidergetXMLContext()方法獲取到一個XMLContext,呼叫XMLContextpublic List<String> addDocument(Document doc)來將doc中所配置的相關class全部條件到一個List中,然後通過reflectionManager通過類名稱將對應的配置載入為org.hibernate.annotations.common.reflection.XClass介面的一個實現。

然後將其加入到MetadataSourceQueue中。MetadataSourceQueue中包含一個宣告為transient List<XClass> annotatedClasses,即annotatedClasses不需要進行序列化。

  Hibernate的手冊中是通過

new Configuration().configure().buildSessionFactory();的方式來獲取一個SessionFactory物件的,但是當前的程式碼中該方法以及被廢棄,建議使用的方法是buildSessionFactory(ServiceRegistry)

因此我們的主方法中使用的是推薦的方法。

 

 

相關文章