1、Hibernate之生成SessionFactory源码追踪

Hibernate的所有session都是由sessionFactory来生成的,那么,sessionFactory是怎么得来的呢?它与我们配置的xxx.cfg.xml文件以及xxx.hbm.xml文件之间又有着怎么样的联系呢?

先看一小段生成sessionFactory的代码:

code_1:

public class HibernateTest {

    @Test
    public void test() {

        System.out.println("test...");

        //1. 创建一个 SessionFactory 对象
        SessionFactory sessionFactory = null;

            //1). 创建 Configuration 对象: 对应 hibernate 的基本配置信息和 对象关系映射信息
            Configuration configuration = new Configuration().configure();

            //4.0 之前这样创建
            //sessionFactory = configuration.buildSessionFactory();

            //2). 创建一个 ServiceRegistry 对象: hibernate 4.x 新添加的对象
            //hibernate 的任何配置和服务都需要在该对象中注册后才能有效.
            ServiceRegistry serviceRegistry =
                new ServiceRegistryBuilder().applySettings(configuration.getProperties())
                                            .buildServiceRegistry();

            //3). 利用serviceRegistry来创建sessionFactory实例
            sessionFactory = configuration.buildSessionFactory(serviceRegistry);

        //2. 创建一个 Session 对象
        Session session = sessionFactory.openSession();

        //3. 开启事务
        Transaction transaction = session.beginTransaction();

        //4. 执行保存操作
        News news = new News("Java12345", "ATGUIGU", new Date(new java.util.Date().getTime()));
        session.save(news);

        //5. 提交事务
        transaction.commit();

        //6. 关闭 Session
        session.close();

        //7. 关闭 SessionFactory 对象
        sessionFactory.close();
    }

}

从上面的代码很清晰的可以看见,这一切的源头都在 Configuration configuration = new Configuration().configure() 这条语句上:创建ServiceRegistry 需要用到configuration,生成sessionFactory同样需要用到configuration。

Configuration的生成过程

从源代码中可以看到,Configuration的configure()方法共有5中重载方式:

code_2:

public Configuration configure();        //无参
public Configuration configure(String resource)
public Configuration configure(URL url)
public Configuration configure(File configFile)
public Configuration configure(org.w3c.dom.Document document)

现在从无参的configure()方法开始分析,它代表了一种默认的行为,默认读取类路径下的hibernate.cfg.xml文件作为hibernate的配置文件:

code_3:

public Configuration configure() throws HibernateException {
    configure( "/hibernate.cfg.xml" );    //默认读取classpath路径下的hibernate.cfg.xml文件
    return this;
}

继续追踪configure( "/hibernate.cfg.xml" )方法:

code_4:

1 public Configuration configure(String resource) throws HibernateException {
2     InputStream stream = getConfigurationInputStream( resource );    //通过传入的资源路径获取一个输入流
3     return doConfigure( stream, resource );    //这个方法会完成解析的第一步:将输入流转换成Document对象
4 }

继续追踪 return doConfigure( stream, resource ) 语句,可以发现底层会通过SAX解析工具将输入流转换成Document对象。然后调用然后调用doConfigure(Document doc)来继续解析这个文档:

code_5:

 1 protected Configuration doConfigure(InputStream stream, String resourceName) throws HibernateException {
 2
 3     ErrorLogger errorLogger = new ErrorLogger( resourceName );  //将输入流转换成Document对象
 5     Document document = xmlHelper.createSAXReader( errorLogger,  entityResolver )
 6                 .read( new InputSource( stream ) );
 7         //具体解析document树,并将结果以键值对的形式存放到properties中
 8     doConfigure( document );
 9
10     return this;
11 }

doConfigure(Document doc)是实际解析文档的方法,前面configure()的5种重载方法最后都要调用这个方法来完成实际的解析。我们看看它的解析思路:

code_6:

 1 protected Configuration doConfigure(Document doc) throws HibernateException {
 2     Element sfNode = doc.getRootElement().element( "session-factory" );
 3     String name = sfNode.attributeValue( "name" );
 4     if ( name != null ) {    //session-factory根节点是可以有name属性值的
 5         properties.setProperty( Environment.SESSION_FACTORY_NAME, name );
 6     }
 7     //遍历文档中所有的property节点,读取器name属性值以及节点的文本,
 8     //以name-value的形式放入到properties中
 9     addProperties( sfNode );
10     //解析除了property之外的节点:mapping、class-cache、collection-cache
11     parseSessionFactory( sfNode, name );
12
13     Element secNode = doc.getRootElement().element( "security" );
14     if ( secNode != null ) {
15         parseSecurity( secNode );
16     }
17
18     LOG.configuredSessionFactory( name );
19     LOG.debugf( "Properties: %s", properties );
20
21     return this;
22 }

doConfigure(Document doc)方法中对Document的解析主要分为两个步骤进行:①解析xxx.cfg.xml配置文档中所有的property节点;②解析xxx.cfg.xml配置文档中除了property节点之外的其它5种节点。

先来看看第①步,它在addProperties(...)方法中完成(code_6代码段的第9行)思路很清晰,用一个迭代器来遍历文档中所有的property节点,并将name-value存放到Configuration的properties属性中:

code_7:

 1 private void addProperties(Element parent) {
 2     //指定,只会遍历property节点
 3     Iterator itr = parent.elementIterator( "property" );
 4     while ( itr.hasNext() ) {//循环遍历
 5         Element node = (Element) itr.next();
 6         //读取节点的name属性值
 7         String name = node.attributeValue( "name" );
 8         //读取节点的文本值
 9         String value = node.getText().trim();
10         LOG.debugf( "%s=%s", name, value );
11         //将name-value值存放如properties中
12         properties.setProperty( name, value );
13         //待研究...
14         if ( !name.startsWith( "hibernate" ) ) {
15             properties.setProperty( "hibernate." + name, value );
16         }
17     }
18         Environment.verifyProperties( properties );
19 }

再来看看第②步,它在parseSessionFactory( ...)方法中进行(code_6代码段的第10行),它主要解析3类标签:mapping、class-cache、collection-cache:

code_8:

 1 private void parseSessionFactory(Element sfNode, String name) {
 2     Iterator elements = sfNode.elementIterator();
 3     while ( elements.hasNext() ) {
 4         Element subelement = (Element) elements.next();
 5         String subelementName = subelement.getName();
 6         //解析mapping节点,mapping可以指定hibernate的映射文件位置
 7         if ( "mapping".equals( subelementName ) ) {
 8             //具体解析mapping节点
 9             parseMappingElement( subelement, name );
10         }
11         //下面两个是和hibernate的二级缓存相关的配置,不做深入探讨
12         else if ( "class-cache".equals( subelementName ) ) {
13             String className = subelement.attributeValue( "class" );
14             Attribute regionNode = subelement.attribute( "region" );
15             final String region = ( regionNode == null ) ? className : regionNode.getValue();
16             boolean includeLazy = !"non-lazy".equals( subelement.attributeValue( "include" ) );
17             setCacheConcurrencyStrategy( className, subelement.attributeValue( "usage" ), region, includeLazy );
18         }
19         else if ( "collection-cache".equals( subelementName ) ) {
20             String role = subelement.attributeValue( "collection" );
21             Attribute regionNode = subelement.attribute( "region" );
22             final String region = ( regionNode == null ) ? role : regionNode.getValue();
23             setCollectionCacheConcurrencyStrategy( role, subelement.attributeValue( "usage" ), region );
24         }
25     }
26 }

后面两个标签class-cache和collection-cache是和hibernate的二级缓存相关,不作深入探讨。主要看看解析mapping的方法:

code_9:

 1 private void parseMappingElement(Element mappingElement, String name) {
 2     //从源代码可以看出,mapping节点支持的属性值有5个
 3     final Attribute resourceAttribute = mappingElement.attribute( "resource" );
 4     final Attribute fileAttribute = mappingElement.attribute( "file" );
 5     final Attribute jarAttribute = mappingElement.attribute( "jar" );
 6     final Attribute packageAttribute = mappingElement.attribute( "package" );
 7     final Attribute classAttribute = mappingElement.attribute( "class" );
 8
 9     if ( resourceAttribute != null ) {
10         final String resourceName = resourceAttribute.getValue();
11         LOG.debugf( "Session-factory config [%s] named resource [%s] for mapping", name, resourceName );
12         //将hibernate的映射文件作进一步的解析
13         addResource( resourceName );
14     }
15     else if ( fileAttribute != null ) {
16         final String fileName = fileAttribute.getValue();
17         LOG.debugf( "Session-factory config [%s] named file [%s] for mapping", name, fileName );
18         addFile( fileName );
19     }
20     else if ( jarAttribute != null ) {
21         final String jarFileName = jarAttribute.getValue();
22         LOG.debugf( "Session-factory config [%s] named jar file [%s] for mapping", name, jarFileName );
23         addJar( new File( jarFileName ) );
24     }
25     else if ( packageAttribute != null ) {
26         final String packageName = packageAttribute.getValue();
27         LOG.debugf( "Session-factory config [%s] named package [%s] for mapping", name, packageName );
28         addPackage( packageName );
29     }
30     else if ( classAttribute != null ) {
31         final String className = classAttribute.getValue();
32         LOG.debugf( "Session-factory config [%s] named class [%s] for mapping", name, className );
33         try {
34             addAnnotatedClass( ReflectHelper.classForName( className ) );
35         }
36         catch ( Exception e ) {
37             throw new MappingException(
38                     "Unable to load class [ " + className + "] declared in Hibernate configuration <mapping/> entry",
39                     e
40             );
41         }
42     }
43     else {
44         throw new MappingException( "<mapping> element in configuration specifies no known attributes" );
45     }
46 }

addResource( resourceName )是如何解析的呢?那么它是如何工作的呢?这里不再一步一步追踪源代码,因为嵌套太深,直接给出一个感性的认识即可:
在Configuration中定义了一个名为MetadataSourceQueue的内部内,同时Configuration中还有一个该队列的属性值:metadataSourceQueue。
addResource( resourceName )方法嵌套到最后会调用metadataSourceQueue.add(...)方法来将映射的元数据存储到metadataSourceQueue队列中。要使用的时候,从该队列中取就可以了。
metadataSourceQueue的底层存储是一个Map类型...

到现在为止,Configuration对象就得到了,总结一下,其重要的几个点:
1、configure()方法默认读取/hibernate.cfg.xml作为hibernate的配置文件。当然,configure()方法还有其它重载形式可用。
2、doConfigure(Document document)方法会调用两个重要的方法:addProperties( sfNode )和parseSessionFactory( sfNode, name );
3、addProperties( sfNode )方法会解析配置文件中的property节点,并将解析到的name-value放入到properties中
4、parseSessionFactory( sfNode, name )方法会解析配置文件中除了property节点外的其它3个类型的节点(4.2版本):mapping、class-cache和collection-cache
5、mapping配置是和映射相关的,class-cache和collection-cache是与二级缓存相关的。
6、mapping解析的结果会存放到metadataSourceQueue对象中。
7、所以,整个过程得到Configuration中两个重要的属性值:properties和metadataSourceQueue

时间: 2024-08-08 05:38:51

1、Hibernate之生成SessionFactory源码追踪的相关文章

Hibernate 5.x 生成 SessionFactory 源码跟踪分析

我们要使用 Hibernate 的功能,首先需要读取 Hibernate 的配置文件,根据配置启动 Hibernate ,然后创建 SessionFactory. 创建 SessionFactory 的代码很简单,这也是我们要分析的代码: Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); 接下来,就针对这两行代码进行分析. 1.初始化 C

CAS源码追踪系列三:cas-server端对请求的处理

目录 InitialFlowSetupAction ServiceAuthorizationCheck AuthenticationViaFormAction SendTicketGrantingTicketAction GenerateServiceTicketAction 第一次访问接入cas的另一个应用系统 总结 系列: CAS源码追踪系列一:Filter的初始化 CAS源码追踪系列二:AuthenticationFilter对于请求的处理 上一篇,我们了解了AuthenticationF

mybatis源码追踪1——Mapper方法用法解析

Mapper中的方法执行时会构造为org.apache.ibatis.binding.MapperMethod$MethodSignature对象,从该类源码中可以了解如何使用Mapper方法. [支持的特殊参数类型] RowBounds.ResultHandler.普通参数 (作为sql执行时使用的变量) 其中普通参数可以是单一的model.查询条件的map或直接将一到多个查询条件作为参数(多个条件在框架中最终将封装为map使用) 另外普通参数支持添加@Param注解以修改参数名,如不修改则参

源码追踪,解决Could not locate executable null\bin\winutils.exe in the Hadoop binaries.问题

在windows系统本地运行spark的wordcount程序,会出现一个异常,但不影响现有程序运行. >>提君博客原创  http://www.cnblogs.com/tijun/  << 总归是一个异常,老是爆红,看着心烦,下面是异常信息 提君博客原创 让我们源码追踪一下,看看到底是什么原因导致,点击第一行爆红的异常信息提示,就是(shell.java:355) 看到我的截图提示,大概了解什么原因了.发现HADOOP_HOME_DIR为null,右边outline里面找到一个私

Google Protocol Buffers 快速入门(带生成C#源码的方法)

Google Protocol Buffers是google出品的一个协议生成工具,特点就是跨平台,效率高,速度快,对我们自己的程序定义和使用私有协议很有帮助. Protocol Buffers入门:1.去 http://code.google.com/p/protobuf/downloads/list 下载一个源代码包和一个已编译好的二进制包2.找一个Proto示例代码,使用命令 protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbo

CAS源码追踪系列二:AuthenticationFilter对于请求的处理

上一篇我们说了在web项目中了和spring整合之后,如何进行对应Filter的初始化,如果你还没看过,请点击 <CAS源码追踪系列一:Filter的初始化>. 本篇我们来看看在初始化完成以后,cas-client是如何处理请求的. 源码地址:https://github.com/apereo/java-cas-client 如何你还不太清楚sso的原理,你可以看看这篇文章<单点登录原理与简单实现>. 当访问系统受保护的资源时,cas的过滤器AuthenticationFilter

【python】13位随机序列号生成工具 源码分析

By Dolphin,BeiJing,20150712 0X00  背景 最近在学习python 这门语言,刚学完for循环,对于很多语句语法都不太熟悉.就在今天,看到有某个网站的活动,需要输入一个13位的序列号来判断你是否中奖,但是这个13位序列号是需要购买他们家的产品才能获得,得耗费一定的金钱,于是我就在想,是不是能自己写一个序列号生成器来碰碰运气,所以决定运用刚学的python的初级知识进行编写. 0X01  知识点准备 这个工具主要的功能是生成随机字母做序列号,python中的rando

从使用Handler致内存泄漏角度源码追踪Handler工作机制

使用Handler时内存泄漏分析 在Android中,处理完异步任务后常常会在主线程进行一些操作,所以我们可能会使用到Handler,下面是Handler的常见使用方法: public class MainActivity extends AppCompatActivity { private Handler mHanlder = new Handler() { @Override public void handleMessage(Message msg) { //TODO } }; } 但是

源码追踪经验谈(侯捷)

1. 前提:在深入框架底层以前,先学会使用它做一个大概认识2. 书籍:找正确的书籍,其实网上就有很多3. 工具:grep, windiff, IDE debugger(CallStack窗口等等), Spy++, TDump, Source Navigator4. 方法:动手5. 状态:搞清文件命名方式6. 线头:找到入口7. 笔记:必须战战兢兢.心无旁骛,有心得的时候做一些笔记,最好还能截图,配以少量文字,20年之内可复习一小时就明白了.8. 瓶颈:知识水平与阅读对象差距太远,就必须补基础9.