MyBatis源码剖析之Mapper代理方式详解

目录
  • 源码剖析-getmapper()
  • 源码剖析-invoke()

具体代码如下:

//前三步都相同
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//这⾥不再调⽤SqlSession的api,⽽是获得了接⼝对象,调⽤接⼝中的⽅法。
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//模拟ids的数据
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
List<User> userList = mapper.findByIds(ids);

思考⼀个问题,通常的Mapper接⼝我们都没有实现的⽅法却可以使⽤,是为什么呢? 答案很简单:动态代理

public class Configuration {
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
}

public class MapperRegistry {
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
}

开始之前介绍⼀下MyBatis初始化时对接⼝的处理:MapperRegistry是Configuration中的⼀个属性,它内部维护⼀个HashMap⽤于存放mapper接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。mappers中可以配置接⼝的包路径,或者某个具体的接⼝类。

<mappers>
  <mapper class="com.zjq.mapper.UserMapper"/>
  <package name="com.zjq.mapper"/>
</mappers>

当解析mappers标签时,它会判断解析到的是mapper配置⽂件时,会再将对应配置⽂件中的增删改查标签 封装成MappedStatement对象,存⼊mappedStatements中。(上⽂介绍了)当判断解析到接⼝时,会建此接⼝对应的MapperProxyFactory对象,存⼊HashMap中,key =接⼝的字节码对象,value =此接⼝对应MapperProxyFactory对象。

源码剖析-getmapper()

进⼊ sqlSession.getMapper(UserMapper.class )中

public interface SqlSession extends Closeable {
      //调用configuration中的getMapper
      <T> T getMapper(Class<T> type);
}

public class DefaultSqlSession implements SqlSession {
  @Override
  public <T> T getMapper(Class<T> type) {
    //调用configuration 中的getMapper
    return configuration.<T>getMapper(type, this);
  }
}

public class Configuration {
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //调用MapperRegistry 中的getMapper
    return mapperRegistry.getMapper(type, sqlSession);
  }
}

public class MapperRegistry {
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //调用MapperProxyFactory的newInstance通过动态代理⼯⼚⽣成实例
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
}

public class MapperProxyFactory<T> {
  public T newInstance(SqlSession sqlSession) {
    //创建了 JDK动态代理的Handler类
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //调⽤了重载⽅法
    return newInstance(mapperProxy);
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
}

//MapperProxy 类,实现了 InvocationHandler 接⼝
public class MapperProxy<T> implements InvocationHandler, Serializable {
  //省略部分源码
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  //构造,传⼊了 SqlSession,说明每个session中的代理对象的不同的!
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

   //省略部分源码
}

源码剖析-invoke()

在动态代理返回了示例后,我们就可以直接调⽤mapper类中的⽅法了,但代理对象调⽤⽅法,执⾏是在MapperProxy中的invoke⽅法中。

public class MapperProxy<T> implements InvocationHandler, Serializable {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //如果是Object定义的⽅法,直接调⽤
      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);
    }
    // 获得 MapperMethod 对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //重点在这:最终调⽤了MapperMethod执⾏的⽅法
    return mapperMethod.execute(sqlSession, args);
  }
}

进⼊execute⽅法:

public class MapperMethod {
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断mapper中的⽅法类型,最终调⽤的还是SqlSession中的⽅法 switch(command.getType())
    switch (command.getType()) {
      case INSERT: {
        //转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        //执⾏INSERT操作
        //转换rowCount
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        //转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 转换 rowCount
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        //转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 转换 rowCount
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        //⽆返回,并且有ResultHandler⽅法参数,则将查询的结果,提交给 ResultHandler 进⾏处理
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        //执⾏查询,返回列表
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        //执⾏查询,返回Map
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        //执⾏查询,返回Cursor
        } 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());
    }
    //返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    //返回结果
    return result;
  }
}

到此这篇关于MyBatis源码剖析之Mapper代理方式详解的文章就介绍到这了,更多相关MyBatis Mapper代理方式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • mybatis实现mapper代理模式的方式

    今晚继续复习mybtis 以根据id值查询单条数据为例 编写SqlMapConfig.xml文件 <configuration> <!-- 使用mybatis需要的数据源和事务配置,后续如果整合spring之后,将不再需要 --> <environments default="development"> <!-- 配置数据源和事务 --> <environment id="development"> <

  • Mybatis mapper接口动态代理开发步骤解析

    一.必须遵守的四项原则 1:接口 方法名==xx.xml中的id名 2:方法返回值类型与Mapper.xml文件中返回值类型一致 3:方法的入参类型与Mapper.xml文件中入参值类型一致 4:命名空间绑定接口 二.代码 public class UserMapperTest { private SqlSession sqlSession; private InputStream in; @Before public void before() throws IOException { //1

  • Mybatis之Mapper动态代理实例解析

    一.什么是Mapper的动态代理 采用Mapper动态代理方法只需要编写相应的Mapper接口(相当于Dao接口),那么Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同Dao接口实现类方法. Mapper接口开发需要遵循以下规范: 1.Mapper.xml文件中的namespace与mapper接口的全类名相同. 2.Mapper接口方法名和Mapper.xml中定义的每个statement的id相同. 3.Mapper接口方法的输入参数类型和mapper.xml中定义的

  • Mybatis通过Mapper代理连接数据库的方法

    1.在数据库中创建表和相应字段,如下图我创建了三个字段分别为fromname,message,toname,类型为varchar 2.创建对应的pojo实体类,注意类型要和数据库创建类型一致,如varchar()对应的是java.lang.String 3.在resource路径下配置config.xml,配置Mybatis的运行环境3306/后面加上自己的数据库schema名字,数据库username和password输入自己的账号和密码,而在下方mapper则是用于注册我们待会要写的xml文

  • Mybatis mapper动态代理的原理解析

    前言 在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出mybatis动态代理替我们所做的工作,有利于我们理解动态代理的过程,讲解完以后我们再进行动态代理的原理解析,此讲解基于mybatis的环境已经搭建完成,并且已经实现了基本的用户类编写以及用户类的Dao接口的声明,下面是Dao层的接口代码 public interface UserDao { /*

  • Mybatis实现Mapper动态代理方式详解

    一.实现原理 Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法. Mapper接口开发需要遵循以下规范: 1.Mapper.xml文件中的namespace与mapper接口的类路径相同. 2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同 3.Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的para

  • MyBatis Mapper代理使用方法详解

    MyBatis介绍 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录. 下文重点给大家介绍mapper代理使用方法. 一.开发人员需要完成的任务: mapper.xml映射文件和mapper.java 二.开发规范

  • MyBatis源码剖析之Mapper代理方式详解

    目录 源码剖析-getmapper() 源码剖析-invoke() 具体代码如下: //前三步都相同 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSess

  • CentOS7.4 源码安装MySQL8.0的教程详解

    MySQL 8 正式版 8.0.11 已发布,官方表示 MySQL 8 要比 MySQL 5.7 快 2 倍,还带来了大量的改进和更快的性能! 以下为本人2018.4.23日安装过程的记录.整个过程大概需要一个小时,make && make install过程需要的时间较长. 一.环境 CentOS7.4   64位  最小化安装 二.准备工作 1.安装依赖 yum -y install wget cmake gcc gcc-c++ ncurses ncurses-devel libaio

  • OpenJDK源码解析之System.out.println详解

    一.前戏 可能不少小伙伴习惯在代码中使用sout打印一些信息,就像这样: System.out.println("hello world!") 做为一位资深干码人,本着弘扬党求真务实的精神,必须得来看看这个sout有何玄机~~ 首先看调用就知道,out是System类的一个公共静态成员变量,进入System.java中: public final static PrintStream out = null; 嗯,不止是public,还是final的.不管,来找找out是在哪里赋值的..

  • Python源码学习之PyType_Type和PyBaseObject_Type详解

    PyType_Type和PyBaseObject_Type PyObject和PyTypeObject内容的最后指出下图中对实例对象和类型对象的理解是不完全正确的, 浮点类型对象全局唯一,Python在C语言层面实现过程中将其定义为一个全局静态变量,定义于Object/floatobject.c中,命名为PyFloat_Type. PyTypeObject PyFloat_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "float&quo

  • Vue 2源码阅读 Provide Inject 依赖注入详解

    目录 Provide/Inject 初始化 1. initInjections 依赖初始化 2. initProvide 注入数据初始化 总结 Provide/Inject 初始化 1. initInjections 依赖初始化 该步骤其实发生在 initState 之前,但是由于 provide/inject 一般是配合使用,所以这里调整了一下顺序. 该函数的定义与过程都比较简单: export function initInjections(vm: Component) { const re

  • Vue源码之rollup环境搭建步骤详解

    目录 搭建环境 建立rollup配置文件 创建入口文件 打包前准备 打包 测试一下 搭建环境 第一步 进行初始化,在终端输入npm init -y生成package.json文件,可以记住所有开发相关的依赖. 第二步 --在终端输,入安装依赖 npm install rollup rollup-plugin-babel @babel/core @babel/preset-env --save-dev 注: 安装rollup打包工具,可能需要编译高级语法所以需要安装babel,安装babel需要在

  • 修改Nginx源码实现worker进程隔离实现详解

    目录 背景 APISIX 不同种类请求的互相影响 修改 Nginx 源码实现进程隔离 效果验证 后记 背景 最近我们线上网关替换为了 APISIX,也遇到了一些问题,有一个比较难解决的问题是 APISIX 的进程隔离问题. APISIX 不同种类请求的互相影响 首先我们遇到的就是 APISIX Prometheus 插件在监控数据过多时影响正常业务接口响应的问题.当启用 Prometheus 插件以后,可以通过 HTTP 接口获取 APISIX 内部采集的监控信息然后展示到特定的看板中. cur

  • elementui源码学习仿写el-link示例详解

    目录 正文 组件思考 组件的需求 组件的效果图 组件实现分析 给link组件加上链接样式 给link组件加上鼠标悬浮时下划线 通过传参控制是否加上下划线(即:是否加上这个下划线类名) 使用v-bind="$attrs"兜底a标签的其他的未在props中声明的参数 代码 使用代码 封装组件代码 正文 本篇文章记录仿写一个el-link组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节.本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件.源

  • Hadoop源码分析二安装配置过程详解

    目录 1. 创建用户 2. 安装jdk 3. 修改hosts 4. 配置ssh免密登录 5. 安装zookeeper 解压: 修改配置文件 修改内容如下: 配置环境变量 启动 6. 安装hadoop 对于三台节点的配置安排如下: 解压: 修改配置文件: 修改core-site.xml 配置hdfs-site.xml 配置mapred-site.xml 配置yarn-site.xml 配置slaves 7. 初始化 在初始化前需要将所有机器都配置好hadoop (1) 启动zookeeper (2

随机推荐