您的位置:首页 > 其它

Mybatis源码分析-配置模块

2017-07-02 11:29 169 查看
楼主比较菜,肯定有很多说的不对的地方,主要还是写给自己看的!!

比起spring来说,mybatis实在是简单,所以就先来聊聊mybatis!

先来张mybatis整体的结构图 瞧瞧



从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:

1 SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能

2 Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护

3 StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。

4 ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,

5 ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;

6 TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换

7 MappedStatement MappedStatement维护了一条select|update|delete|insert节点的封装

8 SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

9 BoundSql 表示动态生成的SQL语句以及相应的参数信息

10 Configuration MyBatis所有的配置信息都维持在Configuration对象之中。

我们用一个列子来看下mybatis 的配置模块

String resource = "configs/mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
SqlSession session = sqlSessionFactory.openSession();


1 根据 配置文件或者注解,生成和数据库交互的必要的数据,存储于Map中,以供后续使用;

1.1 我们来看下 Reader reader = Resources.getResourceAsReader(resource);

resource 地址我是直接用的resrouces文件夹中的相对地址,作用就是根据地址加载配置文件信息,输出Reader;

ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}


类加载器为上述5种,可以看出,以传入的classLoader和默认的defaultClassLoader为主,下面三个大家都很熟悉了,那么一般我们用第三个;这里用到的两个为 org.apache.ibatis.io.Resources(主要用来解析文件,还可以返回Class)和org.apache.ibatis.io.ClassLoaderWrapper(主要是涉及到类加载器的用途,无非是加载文件和加载类),

这里主要涉及到org.apache.ibatis.io包,看包名就知道 ,该包下面都是关于io的类



包中就这么几个类,其中vfs类(虚拟文件系统,用来读取服务器里的资源),提供了2个实现 JBoss6VFS 和 DefaultVFS,并提供了用户扩展点,可定义VFS实现;加载顺序: 自定义VFS实现 > 默认VFS实现 取第一个加载成功的

1.2 根据Reader解析成mybatis必须数据

SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);


1.2.1 好了,来看下SqlSessionFactoryBuilder 这个类,主要集中在build方法中,该方法提供了很多的重载方法,不一一说,重点说下下面的方法


public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}


看出,不仅仅可以传reader,还可以自定义environment(jdbc连接条件)和properties(变量)这两个配置信息,可以看出 重点又在XMLConfigBuilder这个类中了

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}


XPathParser 解析类,主要封装 通过javax.xml.xpath.XPath来生成Document,具体怎么解析就不说了,不是本文重点;

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}


出现Configuration 这个类,上面讲到过 , MyBatis所有的配置信息都维持在Configuration对象之中。从这里才真正的开始解析配置文件至mybatis中;Configuration 默认构造函数中就注册了很多别名,其实就是放进map中,这种方式在很多地方都会见到;

1.2.2 parser.parse()

来看看 ,parse方法中发生了什么;

public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}


重点是parseConfiguration这个方法,可以看出,先从configuration开始解析了;


private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}


很明显,所有的配置信息全部在改方法中生成,我们挑些讲讲;

1.2.2.1 propertiesElement(root.evalNode(“properties”));

Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");


由上可知,属性变量有三种方式可以加载进来,节点子集、resource和url节点属性,其中resource和url只能存在一个,最终合并放进配置文件中

parser.setVariables(defaults);
configuration.setVariables(defaults);


1.2.2.2
typeAliasesElement(root.evalNode("typeAliases"));
注册别名,简单点说就是以别名为key,class为value存放于对应的map中,以便后续用
1.2.2.3 pluginElement(root.evalNode("plugins"));
注册插件;先注册进别名,然后放入插件链中
1.2.2.4 objectFactoryElement(root.evalNode("objectFactory"));
类创建工程,该类作用仅仅是生成实例,默认是DefaultObjectFactory;我们可以实现我们自定义的工厂,实现ObjectFactory接口即可,可以用于类初始化的作用
1.2.2.5 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
动态获取和设置属性的值,默认使用DefaultObjectWrapperFactory,mybatis基本考虑会很全,自定义的很少使用
对象包装工程
1.2.2.6 reflectorFactoryElement(root.evalNode("reflectorFactory"));
反射工厂,功能很简单,就是生成一个反射配置数据,存储Reflector类(里面包含了该类涉及到方法、构造函数、字段、类型很全的一套反射信息,赋值、取值都可以通过他来操作。我们以后自己项目也可以直接拿来使用)数据,默认使用DefaultReflectorFactory类
1.2.2.7 environmentsElement
主要配置连接执行环境,里面包含了事务(JdbcTransactionFactory还有ManagedTransactionFactory,一般使用前者)及数据源(POOL、UNPOOL,JNDI之分,一般肯定选择池)的配置信息的生成;最终生成Environment;Environment这个类比较奇怪,里面实现了内部类Builder,但是内部类和外部类区别不大 ,何必呢。。
1.2.2.8   typeHandlerElement(root.evalNode("typeHandlers"));
类型转换器,该配置可以根据包名,进而解析整个包获取,也可以指定转换类,因为typeHandlerRegistry类中可以对包进行注册
1.2.2.9    mapperElement(root.evalNode("mappers"));
xml配置文件解析,同.1.2.2.8 可以对包(只能在mapper类同包名下才行),也可以其他方式


String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");


由上可以看出是三种方式,其中数resource和url复杂,class是很简单的,

我们这里举resource的例子,这里涉及到XMLMapperBuilder类

InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();


好嘛,mapper.xml解析入口在这里了。好复杂。。

XMLMapperBuilder(解析mapper.xml配置信息类)和XmlConfigBuilder类似,都继承BaseBuilder,同样都有parse 解析方法,只是XMLMapperBuilder更复杂些,因为mapper.xml中的节点更多更复杂。

public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}


private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}


从configurationElement方法可以看出,xml直接关联唯一mapper类,那么可以以此作为key,进而为后续调用获取配置信息打下基础。

MapperBuilderAssistant用于缓存、sql参数、查询返回的结果集处理。

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

cache – 给定命名空间的缓存配置。可以配置

映射语句文件中的所有 select 语句将会被缓存。

映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。

缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。

根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。

缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。

缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

cache-ref – 其他命名空间缓存配置的引用。共用一个namespace缓存

resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。

这里解析的resultMap中的数据,其中涉及到ResultMapping类,主要记录对应的表及实体类相关配置数据,存进resultMappings 中

parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。

sql – 可被其他语句引用的可重用语句块。

这块仅仅是将配置信息存入,而没有进一步去解析

insert – 映射插入语句

update – 映射更新语句

delete – 映射删除语句

select – 映射查询语句

上面四种都是一样处理,仅仅是类型不一样而已;

其中涉及到XMLStatementBuilder类,该类主要记录Statement相关的配置信息MappedStatement

好了 最麻烦的也处理完成了!!返回Configuration,所有的配置信息全在里面了

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}


返回DefaultSqlSessionFactory

接下来是下面这段代码

SqlSession session = sqlSessionFactory.openSession();


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}


开启会话,来看看到底干什么了!

看代码,初始化了事务Transaction,根据例子,其实这里真实的对象应该是JdbcTransaction

也初始化了Executor ,mybatis的执行器

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}


看代码,executor 最终被CachingExecutor装饰了,执行时,先执行CachingExecutor,再执行SimpleExecutor(我们例子里是simple)

public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}


额,构造函数里又把CachingExecutor传递给SimpleExecutor了,这是要干啥,没看到什么用途,先不管,继续继续!

executor = (Executor) interceptorChain.pluginAll(executor);

将执行器放入插件链中,判断是否符合自定义插件类型,符合 则生成代理,则以后凡是到了执行器这里,则优先进入自定义插件执行!

这就是制作插件的原理,使用代理!

最终返回DefaultSqlSession,又是个Default

好了,会话成功开启!

总结:

mybatis配置阶段,使用了共享模式、装饰模式、代理模式、工厂模式、模板模式、外观模式。

插件的原理是使用代理(jdk代理(必须要有接口)或者cglib代理(类),两者性能都很高),用jdk的动态代理实现了拦截器和Mapper接口。这种动态代理和注解的运用也是非常值得学习的。

好吧!写的很烂,连图都没画!

下一篇准备mybatis执行部分解析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mybatis