如果我们要使用MyBatis进行数据库操作的话,大致要做两件事情:

1. 定义DAO接口 
在DAO接口中定义需要进行的数据库操作。

2. 创建映射文件 
当有了DAO接口后,还需要为该接口创建映射文件。映射文件中定义了一系列SQL语句,这些SQL语句和DAO接口一一对应。

  MyBatis在初始化的时候会将映射文件与DAO接口一一对应,并根据映射文件的内容为每个函数创建相应的数据库操作能力。而我们作为MyBatis使用者,只需将DAO接口注入给Service层使用即可。 
  那么MyBatis是如何根据映射文件为每个DAO接口创建具体实现的?答案是——动态代理。 

  首先来回顾一项MyBatis在初始化过程中所做的事情。
  MyBatis在初始化过程中,首先会读取我们的配置文件流程,并使用XMLConfigBuilder来解析配置文件。XMLConfigBuilder会依次解析配置文件中的各个子节点,如:<settings>、<typeAliases>、<mappers>等。这些子节点在解析完成后都会被注册进configuration对象。然后configuration对象将作为参数,创建SqlSessionFactory对象。至此,初始化过程完毕!
下面我们重点分析<mapper>节点解析的过程。

<mapper>节点解析过程

ProductMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="team.njupt.mapper.ProductMapper">
<select id="selectProductList" resultType="com.jp.entity.Product">
select * from product
</select>
</mapper>
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

由上述代码可知,解析 mapper 节点的解析是由 XMLMapperBuilder 类的parse()函数来完成的,下面我们就详细看一下parse()函数。

public void parse() {
// 若当前Mapper.xml尚未加载,则加载
if (!configuration.isResourceLoaded(resource)) {
// 解析<mapper>节点
configurationElement(parser.evalNode("/mapper"));
// 将当前Mapper.xml标注为『已加载』(下回就不用再加载了)
configuration.addLoadedResource(resource);
// 【关键】将Mapper Class添加至Configuration中
bindMapperForNamespace();
} parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

这个函数主要做了两件事:

  1. 解析<mapper>节点,并将解析结果注册进configuration中;
  2. 将当前映射文件所对应的DAO接口的Class对象注册进configuration中
    这一步极为关键!是为了给DAO接口创建代理对象,下文会详细介绍。

下面再进入bindMapperForNamespace()函数,看一看它做了什么:

private void bindMapperForNamespace() {
// 获取当前映射文件对应的DAO接口的全限定名
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
// 将全限定名解析成Class对象
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// 将当前Mapper.xml标注为『已加载』(下回就不用再加载了)
configuration.addLoadedResource("namespace:" + namespace);
// 将DAO接口的Class对象注册进configuration中
configuration.addMapper(boundType);
}
}
}
}

这个函数主要做了两件事:

  1. 将<mapper>节点上定义的 namespace 属性(即:当前映射文件所对应的DAO接口的权限定名)解析成Class对象
  2. 将该Class对象存储在configuration对象的MapperRegistry容器中。

可以看一下MapperRegistry:

public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
}
MapperRegistry有且仅有两个属性:Configuration和knownMappers。
其中,knownMappers的类型为Map<Class<?>, MapperProxyFactory<?>>,由此可见,它是一个Map,key为DAO接口的Class对象,而Value为该DAO接口代理对象的工厂。
那么,这个代理对象工厂是何许人也?它又是如何产生的呢?我们先来看一下MapperRegistry的addMapper()函数。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 创建MapperProxyFactory对象,并put进knownMappers中
knownMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

从这个函数可知,MapperProxyFactory是在这里创建,并put进knownMappers中的。
下面我们就来看一下MapperProxyFactory这个类究竟有些啥:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public Class<T> getMapperInterface() {
return mapperInterface;
} public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
} @SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}

这个类有三个重要成员:

  1. mapperInterface属性
    这个属性就是DAO接口的Class对象,当创建MapperProxyFactory对象的时候需要传入
  2. methodCache属性
    这个属性用于存储当前DAO接口中所有的方法。
  3. newInstance函数
    这个函数用于创建DAO接口的代理对象,它需要传入一个MapperProxy对象作为参数。而MapperProxy类实现了InvocationHandler接口,由此可知它是动态代理中的处理类,所有对目标函数的调用请求都会先被这个处理类截获,所以可以在这个处理类中添加目标函数调用前、调用后的逻辑。

DAO函数调用过程

当MyBatis初始化完毕后,configuration对象中存储了所有DAO接口的Class对象和相应的MapperProxyFactory对象(用于创建DAO接口的代理对象)。接下来,就到了使用DAO接口中函数的阶段了。

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
ProductMapper productMapper = sqlSession.getMapper(ProductMapper.class);
List<Product> productList = productMapper.selectProductList();
for (Product product : productList) {
System.out.printf(product.toString());
}
} finally {
sqlSession.close();
}

我们首先需要从sqlSessionFactory对象中创建一个SqlSession对象,然后调用sqlSession.getMapper(ProductMapper.class)来获取代理对象。
我们先来看一下sqlSession.getMapper()是如何创建代理对象的

public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}

sqlSession.getMapper()调用了configuration.getMapper(),那我们再看一下configuration.getMapper():

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

configuration.getMapper()又调用了mapperRegistry.getMapper(),那好,我们再深入看一下mapperRegistry.getMapper():

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

看到这里我们就恍然大悟了,原来它根据上游传递进来DAO接口的Class对象,从configuration中取出了该DAO接口对应的代理对象生成工厂:MapperProxyFactory;
在有了这个工厂后,再通过newInstance函数创建该DAO接口的代理对象,并返回给上游。

OK,此时我们已经获取了代理对象,接下来就可以使用这个代理对象调用相应的函数了。

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
ProductMapper productMapper = sqlSession.getMapper(ProductMapper.class);
List<Product> productList = productMapper.selectProductList();
for (Product product : productList) {
System.out.printf(product.toString());
}
} finally {
sqlSession.close();
}

以上述代码为例,当我们获取到ProductMapper的代理对象后,我们调用了它的selectProductList()函数。
下面我们就来分析下代理函数调用过程。

当调用了代理对象的某一个代理函数后,这个调用请求首先会被发送给代理对象处理类MapperProxy的invoke()函数:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 【核心代理在这里】
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

先来解释下invoke函数的几个参数:

  1. Object proxy:代理对象
  2. Method method:当前正在被调用的代理对象的函数对象
  3. Object[] args:调用函数的所有入参

然后,直接看invoke函数最核心的两行代码:

  • cachedMapperMethod(method):从当前代理对象处理类MapperProxy的methodCache属性中获取method方法的详细信息(即:MapperMethod对象)。如果methodCache中没有就创建并加进去。
  • 有了MapperMethod对象后执行它的execute()方法,该方法就会调用JDBC执行相应的SQL语句,并将结果返回给上游调用者。至此,代理对象函数的调用过程结束!
    那么execute()函数究竟做了什么?它是如何执行SQL语句的?

转自:https://www.jianshu.com/p/46c6e56d9774

Mybatis动态代理实现函数调用的更多相关文章

  1. 通过模拟Mybatis动态代理生成Mapper代理类,讲解Mybatis核心原理

    本文将通过模拟Mybatis动态代理生成Mapper代理类,讲解Mybatis原理 1.平常我们是如何使用Mapper的 先写一个简单的UserMapper,它包含一个全表查询的方法,代码如下 pub ...

  2. MyBatis 动态代理开发

    MyBatis 动态代理开发 §  Mapper.xml文件中的namespace与mapper接口的类路径相同. §  Mapper接口方法名和Mapper.xml中定义的每个statement的i ...

  3. Java EE开发平台随手记5——Mybatis动态代理接口方式的原生用法

    为了说明后续的Mybatis扩展,插播一篇广告,先来简要说明一下Mybatis的一种原生用法,不过先声明:下面说的只是Mybatis的其中一种用法,如需要更深入了解Mybatis,请参考官方文档,或者 ...

  4. MyBatis动态代理执行原理

    前言 大家使用MyBatis都知道,不管是单独使用还是和Spring集成,我们都是使用接口定义的方式声明数据库的增删改查方法.那么我们只声明一个接口,MyBatis是如何帮我们来实现SQL呢,对吗,我 ...

  5. Spring系列之Mybatis动态代理实现全过程?回答正确率不到1%

    面试中,可能会问到Spring怎么绑定Mapper接口和SQL语句的.一般的答案是Spring会为Mapper生成一个代理类,调用的时候实际调用的是代理类的实现.但是如果被追问代理类实现的细节,很多同 ...

  6. MyBatis动态代理

    一.项目结构 二.代码实现 import java.util.List; import java.util.Map; import com.jmu.bean.Student; public inter ...

  7. MyBatis动态代理查询出错

     org.apache.ibatis.exceptions.PersistenceException: ### Error querying database.  Cause: org.apache. ...

  8. MyBatis总结三:使用动态代理实现dao接口

    由于我们上一篇实现MyBatis的增删改查的接口实现类的方法都是通过sqlsession调用方法,参数也都类似,所以我们使用动态代理的方式来完善这一点 MyBatis动态代理生成dao的步骤: 编写数 ...

  9. Mybatis mapper动态代理的原理详解

    在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出my ...

随机推荐

  1. Mark一下:成为CSDN博客专家

    距第一篇博客(发表于2015.08.13)已有差不多7个月,还记得当时受一个基友的启发,觉得要总结写作些什么,于是磕磕碰碰写出第一篇博客,坚持写作至今,穿梭于CSDN.简书.知乎和作业部落等门户网站, ...

  2. powershell远程访问

    在服务器上打开powershell 1.winrm quickconfig 2.Enable-PSRemoting -Force 在客户端上打开powershell 1.Enter-PSSession ...

  3. Flume 读取实时更新的日志文件

    http://blog.csdn.net/bright60/article/details/50728306 我用了第一种方法. 1. 日志文件每天roate一个新文件 a)  方案一 There i ...

  4. [React] Reference a node using createRef() in React 16.3

    In this lesson, we look at where we came from with refs in React. Starting with the deprecated strin ...

  5. uva 10003 Cutting Sticks 【区间dp】

    题目:uva 10003 Cutting Sticks 题意:给出一根长度 l 的木棍,要截断从某些点,然后截断的花费是当前木棍的长度,求总的最小花费? 分析:典型的区间dp,事实上和石子归并是一样的 ...

  6. 利用机器学习进行DNS隐蔽通道检测——数据收集,利用iodine进行DNS隐蔽通道样本收集

    我们在使用机器学习做DNS隐蔽通道检测的过程中,不得不面临样本收集的问题,没办法,机器学习没有样本真是“巧妇难为无米之炊”啊! 本文简单介绍了DNS隐蔽通道传输工具iodine,并介绍如何从iodin ...

  7. 机器学习 LR中的参数迭代公式推导——极大似然和梯度下降

    Logistic本质上是一个基于条件概率的判别模型(DiscriminativeModel). 函数图像为: 通过sigma函数计算出最终结果,以0.5为分界线,最终结果大于0.5则属于正类(类别值为 ...

  8. 利用Spring Hibernate注解packagesToScan的简化自动扫描方式

    转自:https://blog.csdn.net/wzygis/article/details/28256045

  9. JavaScript学习笔记——对象的创建

    对象是JavaScript基本数据类型,在JavaScript中除了Undefined.Null.布尔型(ture.false).字符串和数字之外,其他的都属于对象. 在JavaScript中,一个对 ...

  10. 下压栈(LIFO)详解

    写在前面的话: 一枚自学Java和算法的工科妹子. 算法学习书目:算法(第四版) Robert Sedgewick 算法视频教程:Coursera  Algorithms Part1&2 本文 ...