本節要學習一下Hibernate的配置檔案的具體載入、解析的過程,以及涉及到的相關程式碼,思路是建立一個簡單的java專案,配置一個hbm檔案,啟動後,跟蹤除錯載入解析hbm的過程,學習相關的程式碼。
搭建專案後,將所需jar放入java專案的lib目錄,在Hibernate的手冊中說明此處也可以使用Maven來設定依賴jar,我這裡還是使用比較原始的方式。直接建立一個lib目錄放置所需要的jar包,然後設定classpath即可。
參考Hibernate手冊中所說的,解釋一下.hbm中幾個具體配置項的相關注意事項。
property中包含name、type、column 這3個常用的屬性。
如:
<property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"/>name屬性用來設定訪問對應的對映類中對應屬性值的getter、setter方法,有時候可以只配置一個name屬性,type和column可以省略。上述例子中的type並不是java的資料型別,也不是SQL資料庫的型別,而是被稱為hibernate對映型別,它是用來在Java資料型別和SQL資料型別之間做轉換的。如果type屬性未定義,則Hibernate會嘗試確定正確的對映型別來進行轉換。在某些情況下,這種使用java類檔案反射的自動檢測可能沒有你所期望和需要的型別,例如上述的date屬性,Hibernate不能確定這裡的java.util.Date應該對映為SQL的哪種型別,是date、timestamp還是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();
檢視Configuration的configure()方法,程式碼如下:
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種型別中的一種resource、file、jar、package、class。對於每種資源這裡都有不同的載入方式,
檢視每一類資源對應的載入方法,最終會發現他們還是會以一種輸入流的方式載入到一個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 );
}
}
}
}
通過呼叫JPAMetadataProvider的getXMLContext()方法獲取到一個XMLContext,呼叫XMLContext的public 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)。
因此我們的主方法中使用的是推薦的方法。