在研究MyBatis源码之前,先来看下单独使用MyBatis来查询数据库时是怎么做的:
1 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); 2 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 3 SqlSession sqlSession = sqlSessionFactory.openSession(); 4 BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); 5 Blog blog = blogMapper.selectByBlogId("1"); 6 System.out.println(blog.toString());
第一步:把全局配置文件读取成流;
第二步:SqlSessionFactoryBuilder根据配置文件构建SqlSessionFactory;
第三步:SqlSessionFactory通过调用openSession()方法创建会话SqlSession;
第四步:SqlSession调用getMapper方法获取Mapper接口对象;
第五步:Mapper接口对象执行查询。
此处,我们把文件读取成流的步骤就省略了,直接从第二步构建SqlSessionFactory工厂开始解析源码。
MyBatis通过建造者模式创建一个工厂,配置文件的解析就是在这一步完成的,包括mybatis-config.xml和Mapper.xml文件。
从上面的代码中可以看到:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
首先new了一个SqlSessionFactoryBuilder,非常明显的建造者模式,它里面定义了很多个build方法的重载,最终返回的是一个SqlSessionFactory对象(单例模式)。
我们看下build方法源码:
1 public SqlSessionFactory build(InputStream inputStream) { 2 return build(inputStream, null, null); 3 } 4 5 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 6 try { 7 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 8 return build(parser.parse()); 9 } catch (Exception e) { 10 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 11 } finally { 12 ErrorContext.instance().reset(); 13 try { 14 inputStream.close(); 15 } catch (IOException e) { 16 // Intentionally ignore. Prefer previous error. 17 } 18 } 19 }
这里面创建了一个XMLConfigBuilder对象(Configuration对象也是这个时候创建的)。
XMLConfigBuilder是抽象类BaseBuilder的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类,比如:XMLMapperBuilder用来解析Mapper映射器,XMLStatementBuilder用来解析增删改查标签。
XMLConfigBuilder先执行parse方法解析xml文件,返回一个Configuration对象:
1 public Configuration parse() { 2 if (parsed) { 3 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 4 } 5 parsed = true; 6 parseConfiguration(parser.evalNode("/configuration")); 7 return configuration; 8 }
配置文件里面所有的信息都会放在Configuration里面。Configuration类里面有很多属性,有很多是跟config里面的标签直接对应的。
然后执行SqlSessionFactoryBuilder建造者的build方法,把根据配置文件生成的Configuration对象传进去:
1 public SqlSessionFactory build(Configuration config) { 2 return new DefaultSqlSessionFactory(config); 3 }
可以看到,直接返回一个默认工厂DefaultSqlSessionFactory对象。
我们重点关注解析配置文件到Configuration对象中的parse方法:
看源码知道,parse方法会先检查是不是已经解析过,也就是说在应用的生命周期里面,config配置文件只需要解析一次,生成的Configuration对象也会存在应用的整个声明周期。接下来就是parseConfiguration方法:
1 private void parseConfiguration(XNode root) { 2 try { 3 //issue #117 read properties first 4 propertiesElement(root.evalNode("properties")); 5 Properties settings = settingsAsProperties(root.evalNode("settings")); 6 loadCustomVfs(settings); 7 loadCustomLogImpl(settings); 8 typeAliasesElement(root.evalNode("typeAliases")); 9 pluginElement(root.evalNode("plugins")); 10 objectFactoryElement(root.evalNode("objectFactory")); 11 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 12 reflectorFactoryElement(root.evalNode("reflectorFactory")); 13 settingsElement(settings); 14 // read it after objectFactory and objectWrapperFactory issue #631 15 environmentsElement(root.evalNode("environments")); 16 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 17 typeHandlerElement(root.evalNode("typeHandlers")); 18 mapperElement(root.evalNode("mappers")); 19 } catch (Exception e) { 20 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 21 } 22 }
这里面有十几个方法,对应着config文件里面的所有一级标签。下面对这些方法做一下简要说明:
解析properties标签,读取我们引入的外部配置文件。这里有两种类型,一种是放在resource目录下的,是相对路径,一种是写的绝对路径。解析的最终结果就是我们会把所有的配置信息放到名为defaults的Properties对象里面,最后把XPathParser和Configuration的Properties属性都设置成我们填充后的Properties对象。
把settings标签也解析成了Properties对象,对于<settings>的子标签的处理在后面。之所以这里先将settings标签解析成Properties对象,是因为下面两个方法要用。
loadCustomVfs是获取Vitual File System的自定义实现类,比如我们要读取本地文件,或者FTP远程文件的时候,就可以用到自定义的VFS类。我们根据<settings>标签里面的<vfsImpl>标签,生成一个抽象类VFS的子类,并且赋值到Configuration中。
loadCustomLogImpl是根据<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括log4j、log4j2、slf4j等,这里生成一个Log接口的实现类,并且赋值到Configuration中。
接下来,解析<typeAliases>标签,它有两种定义方式,一种是直接定义一个类的别名,一种就是指定一个包,那么这个package下面所有的类的名字就会成为这个类全路径的别名。类的别名和类的关系,放在一个TypeAliasRegistry对象里面。
接下来解析的是<plugins>标签,比如pageHelper的翻页插件,或者我们自定义的插件。<plugins>标签里面只有<plugin>标签,<plugin>标签里面只有<property>标签。标签解析完以后,会生成一个Interceptor对象,并且添加到Configuration的InterceptorChain属性里面,它是一个List。
这两个标签是用来实例化对象用的,<objectFactory>和<objectWrapperFactory>这两个标签,分别生成ObjectFactory、ObjectWrapperFactory对象,同样设置到Configuration属性里面。
解析<reflectorFactory>标签,生成ReflectorFactory对象。
这里就是对<settings>标签里面所有子标签的处理了,前面已经把子标签全部转换成了Properties对象,所以在这里处理Properties对象就可以了。二级标签里面有很多的配置,比如二级缓存,延迟加载,自动生成主键这些。需要注意的是,这些标签的所有的默认值都是在这里赋值的。所有的值都会赋值到Configuration对象里去。
这一步解析<environments>标签。我们知道,一个environment对应一个数据源,所以在这里我们会根据配置的<transactionManager>创建一个事务工厂,根据<dataSource>标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到Configuration里面。
解析<databaseIdProvider>标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。
跟typeAlias一样,TypeHandler有两种配置方式,一种是单独配置一个类,一种是指定一个package。最后我们得到的是JavaType和JdbcType,以及用来做相互映射的TypeHandler之间的映射关系。最后放在TypeHandlerRegistry对象里面。
问题:这种三个对象(Java类型,JDBC类型,Handler)的关系怎么映射?(Map里面再放一个Map)
(1)判断
最后就是Mapper标签的解析,<mapper>标签的扫描类型:
| 扫描类型 | 含义 |
| resource | 相对路径 |
| url | 绝对路径 |
| class | 单个Mapper接口 |
| package | 包 |
(2)注册
XMLMapperBuilder.parse()方法是对Mapper映射器的解析。里面有两个方法:
configurationElement()--解析所有的子标签,其中buildStatementFromContext()最终获得MappedStatement对象。
bindMapperForNamespace--把namespace(接口类型)和工厂类绑定起来。
无论是按package扫描还是按接口扫描,最后都会用到MapperRegistry的addMapper()方法。MapperRegistry里面维护的其实是一个Map,key是接口类型,value是一个MapperProxyFactory对象。
(3)处理注解
除了映射器文件,在这里也会去解析Mapper接口方法上的注解,在addMapper()方法里面创建了一个MapperAnnotationBuilder,进去看一下它的parse()方法:
1 public void parse() { 2 String resource = type.toString(); 3 if (!configuration.isResourceLoaded(resource)) { 4 loadXmlResource(); 5 configuration.addLoadedResource(resource); 6 assistant.setCurrentNamespace(type.getName()); 7 parseCache(); 8 parseCacheRef(); 9 Method[] methods = type.getMethods(); 10 for (Method method : methods) { 11 try { 12 // issue #237 13 if (!method.isBridge()) { 14 parseStatement(method); 15 } 16 } catch (IncompleteElementException e) { 17 configuration.addIncompleteMethod(new MethodResolver(this, method)); 18 } 19 } 20 } 21 parsePendingMethods(); 22 }
parseCache()和parseCacheRef()方法其实是对@CacheNamespace和@CacheNamespaceRef这两个注解的处理。parseStatement()方法里面的各种getAnnotation(),都是对注解的解析,比如@Options,@SelectKey,@ResultMap等。最后同样会解析成MappedStatement对象,也就是说在XML中配置,和使用注解配置,最后起到的效果是一样的。
(4)收尾
如果注册没有完成,还要从Map里面remove掉:
1 finally { 2 if (!loadCompleted) { 3 knownMappers.remove(type); 4 } 5 }
最后,MapperRegistry也会放到Configuration中去。
最后调用SqlSessionFactoryBuilder自己的build(configuration)方法,返回DefaultSqlSessionFactory。
我们跟数据库的每一次连接,都需要创建一个会话,我们用openSession()方法来创建:
1 public SqlSession openSession() { 2 return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); 3 } 4 5 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 6 Transaction tx = null; 7 try { 8 final Environment environment = configuration.getEnvironment(); 9 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); 10 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); 11 final Executor executor = configuration.newExecutor(tx, execType); 12 return new DefaultSqlSession(configuration, executor, autoCommit); 13 } catch (Exception e) { 14 closeTransaction(tx); // may have fetched a connection so lets call close() 15 throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); 16 } finally { 17 ErrorContext.instance().reset(); 18 } 19 }
DefaultSqlSessionFactory.openSessionFromDataSource()这个会话里面,需要包含一个Executor用来执行SQL。Executor又要指定事务类型和执行器的类型。所以我们会先从Configuration里面拿到Environment,Environment里面就有事务工厂。
| 属性 | 产生工厂类 | 产生事务 |
| JDBC | JdbcTransactionFactory | JdbcTransaction |
| MANAGED | ManagedTransactionFactory | ManagedTransaction |
如果配置的是JDBC,则会使用Connection对象的commit()、rollback()、close()管理事务。
如果配置成MANAGED,会把事务交给容器来管理,比如JBOSS、Weblogic。因为我们跑的是本地程序,如果配置成MANAGED不会有任何事务。
如果是Spring+MyBatis,则没有必要配置,因为我们会直接在applicationContext.xml里面配置数据源和事务管理器,覆盖MyBatis的配置。
Executor的基本类型有三种:SIMPLE、BATCH、REUSE,默认是SIMPLE(settingsElement读取默认值),它们都继承了抽象类BaseExecutor。
为什么要让抽象类实现Executor接口,然后让具体实现类继承抽象类?(模板方法模式,父类定义算法骨架,子类可以重新定义算法)
三种类型的区别:
| 类型 | 说明 |
| SimpleExecutor | 每执行一次select或update,就开启一个Statement对象,用完立刻关闭Statement对象。 |
| ReuseExecutor | 执行select或update,以sql作为key查找Statement对象, 存在就是用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。就是重复使用Statement |
| BatchExecutor | 执行update,将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理,与JDBC批处理相同。 |
如果配置了cacheEnabled=true,会用装饰器模式对Executor进行包装:new CachingExecutor(executor)。包装完毕后,会执行:executor = (Executor) interceptorChain.pluginAll(executor);此处会对executor进行包装。
最终返回DefaultSqlSession,属性包括Configuration、Executor对象。
原文:https://www.cnblogs.com/yybinger/p/11959426.html