先整体看一下Mybatis是怎么工作的,再针对细节进行分析
<configuration>
<properties/>
<settings>
<setting name="" value=""/>
settings>
<typeAliases/>
<typeHandlers/>
<objectFactory type=""/>
<plugins>
<plugin interceptor=""/>
plugins>
<environments default="">
<environment id="">
<transactionManager type=""/>
<dataSource type=""/>
environment>
environments>
<databaseIdProvider type=""/>
<mappers/>
configuration>
properties是一个配置属性的元素,让我们能在配置文件的上下文中使用它
,有三种配置方式
定义properties属性
<properties>
<property name="username" value="root"/>
<property name="password" value="root"/>
properties>
在下面的配置中直接使用
<dataSource type="POOLED">
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
<properties resource="db.properties"/>
db.properties
username=root
password=root
同理在mybatis-config3.xml下面的配置中直接使用
<dataSource type="POOLED">
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
Mybatis的各种配置
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
在使用select,insert等标签时,需要指定resultType属性,表明返回的是啥类型,或者parameterType,表明插入的是啥类型,有如下三种配置方式
<typeAliases>
<typeAlias alias="role" type="com.makenv.part3.po.Product"/>
typeAliases>
通过自动扫描包自定义别名,将类名的第一个字母变成小写,作为MyBatis的别名
<typeAliases>
<package name="com.makenv.part3.po"/>
typeAliases>
在定义了自动扫描包之后,如果想更改别名,可以使用注解
@Alias("productTest")
public class Product
将Role的别名定义为roleTest,这样可以在resultType,parameterType中直接使用别名
当然系统已经帮助我们把常用类都定义了别名,这样设置resultType=java.lang.String时,可以直接写为resultType=string,举几个例子
别名 | 映射的类型 |
---|---|
string | String |
int | Integer |
_int | int |
typeHandler常用的配置为Java类型(javaType),JDBC类型(jdbcType)。typeHandler的作用就是将参数从javaType转化为jdbcType,或者从数据库取出结果时把jdbcType转化为javaType,系统为我们定义了常用的typeHandler,举几个例子
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean, boolean | 数据库兼容的 BOOLEAN |
IntegerTypeHandler | java.lang.Integer, int | 数据库兼容的 NUMERIC 或 INTEGER |
StringTypeHandler | java.lang.String | CHAR, VARCHAR |
我们可以自定义typeHandler,表明javaType和jdbcType如何互相转换
MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现
实现Interceptor接口来定义插件
<plugins>
<plugin interceptor="com.plugins.interceptors.LogPlugin" />
plugins>
配置环境可以注册多个数据源,每个数据源分为两大部分,一个是数据库源(dataSource)的配置,另外一个是数据库事务(transactionManager)的配置
environments中的属性default,标明在缺省的情况下,我们将启用哪个数据源配置,environment元素是配置一个数据源的开始,属性id是设置这个数据源的标志,以便Mybatis上下文使用它
支持不同的数据库,很少用,不再介绍
使用相对于类路径的资源引用
<mappers>
<mapper resource="com/makenv/part3/mapper/ProductMapper.xml"/>
mappers>
使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="com.makenv.part3.mapper.ProductMapper"/>
mappers>
将包内的映射器接口实现全部注册为映射器
<mappers>
<package name="com.makenv.part3.mapper"/>
mappers>
使用完全限定资源定位符(URL)
<mappers>
<mapper url="file:///var/mappers/ProductMapper.xml"/>
mappers>
从mybatis解析配置文件开始分析
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
budild的实现如下
// SqlSessionFactoryBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
XMLConfigBuilder对象会生成Configuration对象,这是Mybatis的一个全局配置类,Mybatis的所有配置信息都存在这一个类中,parser.parse()里面主要执行解析XML中各个节点的配置信息
// XMLConfigBuilder
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
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);
// 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);
}
}
通过 XPathParser 解析 configuration节点下的properties,settings,typeAliases,plugins,objectFactory,objectWrapperFactory,reflectionFactory,environments,databaseIdProvider,typeHandlers,mappers 这些节点,解析过程大体相同,都是通过 XPathParser 解析相关属性、子节点,然后创建相关对象,并保存到 configuration 对象中
(1)解析 properties
解析properties,并设置到configuration对象下的variables属性
// Configuration
protected Properties variables = new Properties();
(2)解析settings
解析settings配置,设置到configuration对象的对应属性如
cacheEnabled (全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认true),lazyLoadingEnabled( 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态,默认false)
// Configuration
protected boolean cacheEnabled = true;
protected boolean lazyLoadingEnabled = false;
(3)解析typeAliases
别名是通过Configuration中的TypeAliasRegistry这个对象来存储的,而TypeAliasRegistry是通过Map来保存别名的
// Configuration
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// TypeAliasRegistry
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
前面我们已经说到可以在resultType和parameterType直接用string来代替java.lang.String,这是因为TypeAliasRegistry在构造函数中提前注册了一下别名
// TypeAliasRegistry
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
// 余下部分省略
}
(4)解析plugins
解析插件,然后设置Configuration的InterceptorChain
// Configuration
protected final InterceptorChain interceptorChain = new InterceptorChain();
// InterceptorChain
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
在创建的时候构造了拦截器链,在执行的时候也会经过拦截器链,此处为典型的责任链模式
(5)解析objectFactory
// Configuration
protected ObjectFactory objectFactory = new DefaultObjectFactory();
可以自定义ObjectFactory,对象工厂,默认为DefaultObjectFactory
(9)解析typeHandlers
// Configuration
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// TypeHandlerRegistry
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();
和TypeAliasRegistry一样,系统会设置一下默认的register
// TypeHandlerRegistry
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
// 余下部分省略
}
(10)解析mappers
读取指定的配置文件,然后key为接口对象,value为映射器代理工厂放入map中
// Configuration
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// MapperRegistry
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
解析mapper的入口为XmlMapperBuilder.parse方法,在解析的时候会解析cache-ref,cache,parameterMap,resultMap,sql,select|insert|update|delete。cache-ref,cache和缓存相关,parameterMap目前已很少使用,这里就不再说明了。
// XmlMapperBuilder
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);
}
}
(1)创建SqlSession
(2)创建mapper代理类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
这行代码执行完毕,sqlSessionFactory 指向的是DefaultSqlSessionFactory对象
,所以我们分析DefaultSqlSession.getMapper方法
// DefaultSqlSession
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
可以看到最终是会通过mapperProxyFactory来创建MapperProxy代理类,通过jdk动态代理来创建最终的Proxy代理类
// MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
(3)调用mapper方法
// MapperProxy
@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);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
如果执行的是Object类的方法,那么直接执行方法即可;其它方法的话通过MapperMethod来执行。实现如下:
适配器模式
[1]http://www.cnblogs.com/liuling/archive/2013/04/12/adapter.html
源码中文注释
[2]https://github.com/tuguangquan/mybatis
优秀博文
[3]https://blog.csdn.net/nmgrd/article/details/54608702
MyBatis源码解析(一)——MyBatis初始化过程解析(好文)
[4]https://www.jianshu.com/p/7bc6d3b7fb45
MyBatis源码解析(二)——动态代理实现函数调用(好文)
[5]https://www.jianshu.com/p/46c6e56d9774
MyBatis 源码解析:通过源码深入理解 SQL 的执行过程
[6]https://gitbook.cn/books/5a37b6b66eec7c4f044a75d0/index.html#undefined
Mybatis源码分析(一)–Mapper的动态代理
[7]https://zhuanlan.zhihu.com/p/31228047
建造者模式
[8]http://www.hollischuang.com/archives/1477
Mybatis缓存详解
[9]http://blog.51cto.com/zero01/2103911