Mybatis技术原理与实践——读书笔记(六)

原创
2017/02/17 21:47
阅读数 346

本文是读 杨开振老师的《深入浅出 Mybatis技术原理与实践》第六章所记录的笔记,本文绝对没有推荐书的意思,只是看着记录学习,同时分享自己的观点而已。

 

 

1、Mybatis 运行机制

其运行分为两部分,第一次部分是读取配置文件缓存到 Configuration 对象,用以创建SqlSessionFactory,第二部分是 SqlSession 的执行过程。

 

2、反射与动态代理

1)反射的使用

Java的反射堪称经典,尤其是适合热插拔的程序,因为程序只要读取配置文件,就可以运行想要的代码。配置文件刚好是运行期可调的,这样配置文件可以配置程序中任何类名,Java读取类名后动态加载这些类,通过反射的机制生成实例、调用方法。

 

2)JDK动态代理

代理模式在GoF设计模式尤为突出,Spring AOP 就是代理模式的一个例子,而且它使用的也是JDK的动态代理实现。MyBatis同样在Mapper接口执行时也是使用这个,当你第一次使用Mybatis的Mapper接口时肯定和我一样非常惊讶,为什么主要定义接口,不需要实现,就可以使用了呢?

说说JDK动态代理,主要是三点

A:target-interface // 定义接口

B:target-proxy implements InvocationHandler // 代理实现动态代理接口

C:Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),

new Class[]{target-interface.class},

new target-proxy()); // 生成代理对象

 

只要完成以上三步,生成的代理对象调用接口的任何方法都会去调用 invoke 方法。其中 invoke 方法就是代理对象实现 InvocationHandler 的抽象方法。

 

 

3)CGLIB动态代理

待学习

 

3、构建SqlSessionFactory过程

主要的类:

SqlSessionFactoryBean

Configuration

XmlConfigBuilder

SpringManagedTransactionFactory

XMLMapperBuilder

DefaultSqlSessionFactory

 

1)首先在 spring-mybatis.xml 文件配置如下

<!-- mybatis文件配置,扫描所有mapper文件 -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="dataSource"/>

<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>

<property name="mapperLocations" value="classpath:mybatis/mapper/**/*.xml"/>

</bean>

2)SqlSessionFactoryBean 源代码 buildSqlSessionFactory 构建 SqlSessionFactory

第一部分:Configuration xml 配置文件

Configuration configuration;

XMLConfigBuilder xmlConfigBuilder = null;

if (this.configuration != null) {

configuration = this.configuration;

-- 省略部分代码

} else if (this.configLocation != null) {

xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(),

null, this.configurationProperties);

configuration = xmlConfigBuilder.getConfiguration();

} else {

-- 省略部分代码

configuration = new Configuration();

configuration.setVariables(this.configurationProperties);

}

 

第二部分:主配置文件配置信息

// 判断是否在 spring-mybatis 配置了信息

if (this.objectFactory != null)

if (this.objectWrapperFactory != null)

if (this.vfs != null)

if (hasLength(this.typeAliasesPackage))

if (!isEmpty(this.typeAliases))

if (!isEmpty(this.plugins))

if (hasLength(this.typeHandlersPackage))

if (!isEmpty(this.typeHandlers))

if (this.databaseIdProvider != null)

if (this.cache != null)

// 如果配置了信息就设置到 configuration

configuration.setXXX();

 

第三部分:通过xml配置信息解析 configuration,从这里可以了解到,如果一个对象构建非常复杂,则没有必要在一个类构造方法里写那么多的逻辑,而是把这个类的创建交给另一个类 XXXBuilder,然后调用XXXBuilder的build或parse方法创建复制对象。

xmlConfigBuilder.parse();



看看 XMLConfigBuilder 的 parseConfiguration 方法,这里才是真正的构建 configuration 属性

private void parseConfiguration(XNode root) {

try {

Properties settings = settingsAsPropertiess(root.evalNode("settings"));

propertiesElement(root.evalNode("properties"));

loadCustomVfs(settings);

typeAliasesElement(root.evalNode("typeAliases"));

pluginElement(root.evalNode("plugins"));

objectFactoryElement(root.evalNode("objectFactory"));

objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

reflectorFactoryElement(root.evalNode("reflectorFactory"));

settingsElement(settings);

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);

}

}

第四部分: 创建Spring事务管理以及重新设置configuration的连接配置信息

if (this.transactionFactory == null) {

this.transactionFactory = new SpringManagedTransactionFactory();

}

// 重新设置数据源以及事务管理实例

configuration.setEnvironment(new Environment(this.environment,

this.transactionFactory, this.dataSource));

第五部分:扫描、加载 Mapper 文件,构建 Statement

if (!isEmpty(this.mapperLocations)) {

for (Resource mapperLocation : this.mapperLocations) {

try {

XMLMapperBuilder xmlMapperBuilder =

new XMLMapperBuilder(mapperLocation.getInputStream(),

configuration, mapperLocation.toString(), configuration.getSqlFragments());

xmlMapperBuilder.parse();

} catch (Exception e) {

throw new NestedIOException("Failed to parse mapping resource");

}

}

}

 

第六部分:创建 sqlSessionFactory 对象并且返回,同时Spring创建的是 DefaultSqlSessionFactory

return this.sqlSessionFactoryBuilder.build(configuration);

// 创建的是 DefaultSqlSessionFactory

public SqlSessionFactory build(Configuration config) {

return new DefaultSqlSessionFactory(config);

}

 

 

4、映射器的动态代理(JDK动态代理)

原理是:Mybatis 提供了 Mapper接口的代理对象,在执行 Mapper接口方法时,实际执行的是Mybatis的代理对象,代理对象在 invoke 方法内获取 Mapper接口类全名+方法全名 作为statement的ID,然后通过ID去Statement匹配注册的SQL,然后使用 SqlSession 执行这个 SQL。

所以,这也解释了为什么Mybatis映射文件需要 namespace 和 id , 前者是类全名,后者是方法名。

主要的类有:

SqlSessionFactoryBean

SqlSessionFactory

XMLConfigBuilder

Configuration

MapperRegistry

MapperProxyFactory

MapperProxy

MapperMethod

SqlCommand

MethodSignature

 

从源码出发:

1)Spring 配置文件配置 SqlSessionFactoryBean

2)SqlSessionFactoryBean 调用 buildSqlSessionFactory() 创建 SqlSessionFactory,此时会调用

3)XMLConfigBuilder.parse() ,目的就是构建 Configuration

4)Configuration 主要存储 Mybatis 所有的配置信息,当然也会有Mapper代理对象

5)XMLConfigBuilder.parseConfiguration 里面就注册了 Configuration 的部分配置,含 Mapper

6)执行重点方法 XMLConfigBuilder 的 mapperElement

private void mapperElement(XNode parent) throws Exception {

-- 省略部分代码

if (resource == null && url == null && mapperClass != null) {

// mapperInterface 就是目标对象,Mybatis 将为他生成代理

Class<?> mapperInterface = Resources.classForName(mapperClass);

configuration.addMapper(mapperInterface);

} else {

throw new BuilderException("A mapper element may only specify a url,

resource or class, but not more than one.");

}

-- 省略部分代码

}

7)继续跟踪 configuration.addMapper(mapperInterface);调用的是 MapperRegistry 的方法

8)看看 MapperRegistry 的 addMapper 方法,很明显一个缓存式代码

public <T> void addMapper(Class<T> type) {

if (type.isInterface()) {

-- 省略部分代码

try {

// 终于看到代理的影子

knownMappers.put(type, new MapperProxyFactory<T>(type));

MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);

// 这里主要是把我们写的 SQL 语句加载到 Configuration 中,因为执行SQL时需要

parser.parse();

}

-- 省略部分代码

}

9)MapperProxyFactory 就非常简单了,通过参数构建出代理对象,就如其名。

// 使用JDK动态代理对象 Proxy 创建代理对象 mapperProxy

protected T newInstance(MapperProxy<T> mapperProxy) {

return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),

new Class[] { mapperInterface },

mapperProxy);

}

 

到这里就结束了Mapper接口是如何加载的,那么下面就正式进入到 Mapper 接口是如何被Mybatis代理的,从上面可以看出重点就是 MapperProxy 类,没错,想要被 Proxy调用,这个类必须实现 InvocationHandler接口,并且实现 invoke 方法,这样他才能在执行接口方法的时候会执行 invoke。那么既然有了 MapperProxy 对象,只要在这个对象的invoke方法里调用执行 SQL 语句就达到目的了,因此也就不需要接口实现类了。如下继续走第10步

 

10)MapperProxy<T> implements InvocationHandler 同时实现 invoke 方法

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 如果不是接口,那么就调用方法本身,什么都没处理

if (Object.class.equals(method.getDeclaringClass())) {

try {

return method.invoke(this, args);

} catch (Throwable t) {

throw ExceptionUtil.unwrapThrowable(t);

}

}

// 如果是接口,先缓存,后执行 SQL

final MapperMethod mapperMethod = cachedMapperMethod(method);

return mapperMethod.execute(sqlSession, args);

}

 

11)MapperMethod 有两个重点,其一就是初始化,其二就是执行SQL,为什么说初始化很重要呢?因为初始化的时候需要通过 接口名全称+方法全称 去 Configuration 找我们之前加载的SQL,这也就是为什么接口方法定义和SQL的ID必须保持一致的原因;其二执行SQL。

12)MapperMethod 初始化时实例 command 和 method

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {

// 找到方法执行类型以及SQL注册的ID

this.command = new SqlCommand(config, mapperInterface, method);

// 方法的返回值映射

this.method = new MethodSignature(config, mapperInterface, method);

}

13)MapperMethod 调用 execute 执行 SQL,代码很简单如下

// command 对象包含了执行类型和执行SQL的ID,都是初始化时构建好

switch (command.getType()) {

case INSERT: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.insert(command.getName(), param));

break;

}

-- 省略部分代码

case SELECT:

if (method.returnsMany()) {

result = executeForMany(sqlSession, args);

} else if (method.returnsMap()) {

result = executeForMap(sqlSession, args);

} else if (method.returnsCursor()) {

result = executeForCursor(sqlSession, args);

} else {

Object param = method.convertArgsToSqlCommandParam(args);

result = sqlSession.selectOne(command.getName(), param);

}

break;

case FLUSH:

result = sqlSession.flushStatements();

break;

default:

throw new BindingException("Unknown execution method for: " + command.getName());

}

 

到这里就获取到运行结果 Result 了,可以直接返回,对于如何执行 SQL的,那就要跟踪 SqlSession 的实现类了,比如 DefaultSqlSession,里面会涉及到SQL参数的注入,结果集如何映射到返回实例,一级缓存和二级缓存以及分页处理等。

 

 

 

 

展开阅读全文
打赏
0
5 收藏
分享
加载中
更多评论
打赏
0 评论
5 收藏
0
分享
返回顶部
顶部