Mybatis源码学习

前言

先整体看一下Mybatis是怎么工作的,再针对细节进行分析

  1. SqlSessionFactoryBuilder(构造器):它会根据配置信息或者代码来生成SqlSessionFactory(工厂接口)
  2. SqlSessionFactory:依靠工厂来生成SqlSession(会话)
  3. SqlSession:是一个既可以发送SQL去执行返回结果,也可以获取 Mapper的接口
  4. SQL Mapper:它是Mybatis新设计的组件,它是由是一个Java接口和XML文件(或注解)构成的,需要给出对应的SQL和映射规则。它负责发送SQL 去执行,并返回结果。

Mybatis源码学习_第1张图片

使用

配置文件详解



<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属性

<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>
  1. properties配置文件
<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>
  1. 程序参数传递
    很少用,不再解释,三种配置方式的优先级是,properties元素体内的属性

setting

Mybatis的各种配置

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
settings>

typeAliases

在使用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

typeHandlers

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如何互相转换

objectFactory

MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现

plugins

实现Interceptor接口来定义插件

<plugins>
	<plugin interceptor="com.plugins.interceptors.LogPlugin" />    
plugins>

environments

配置环境可以注册多个数据源,每个数据源分为两大部分,一个是数据库源(dataSource)的配置,另外一个是数据库事务(transactionManager)的配置

environments中的属性default,标明在缺省的情况下,我们将启用哪个数据源配置,environment元素是配置一个数据源的开始,属性id是设置这个数据源的标志,以便Mybatis上下文使用它

databaseIdProvider

支持不同的数据库,很少用,不再介绍

mappers

使用相对于类路径的资源引用

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

配置文件解析

解析configuration

从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

解析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);
  }
}

解析resultMap

SQL执行

(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

你可能感兴趣的:(Java,EE)