MyBatis是如何为Dao接口创建实现类的

news2025/1/21 1:22:13

本文是我的MyBatis源码分析专栏中第三节的一小部分,作为试读部分,详细讲述了MyBatis是如何通过动态代理创建Dao接口的实现类的。
专栏地址:MyBatis源码分析

专栏字数:14w+
专栏目录:在这里插入图片描述

文章目录

      • SqlSession.getMapper如何设计的?
        • 动态自己码技术如何创建的实现类呢?
        • 实现类的逻辑
        • 手写Mapper接口的实现类代理
        • 源码探究
          • MapperProxyFactory核心代码分析:
          • MapperProxy
            • MapperMethod
            • invoke

SqlSession.getMapper如何设计的?

 UserDAO userDAO =  SqlSession.getMapper(UserDAO.class);
    //UserDAO接口的实现类的对象 
    //疑问? UserDAO接口实现类 在哪里? 我们写的是xml文件呀? --> 这是因为动态字节码技术(Spring 的 AOP 也是用了动态字节码技术)
    //动态字节码技术 ---> 类 在JVM 运行时创建 ,JVM运行结束后,消失了 

image-20221119115140118

动态自己码技术如何创建的实现类呢?

1. 如何 创建 UserDAO XXXDAO接口的实现类 
             代理 (动态代理)应用场景:
             a. 为原始对象(目标)增加【额外功能】 
             b. 远程代理 1.网络通信 2.输出传输 (RPCDubbo 
             c. 接口实现类,我们看不见实实在在的类文件,但是运行时却能体现出来。无中生有
            
Proxy.newProxyIntance(ClassLoader,Interface,InvocationHandler)

SqlSession.getMapper应用了代理模式:

debugSqlSession.getMappe方法可以发现雀氏是个代理对象:

image-20221119113858499

实现类的逻辑

interface UserDAO{
        List<User> queryAllUsers();         
        save(User user);
 }
             

UserDAOImpl implements UserDAO{
 queryAllUsers(){
      sqlSession.select("namespace.id",参数)
                        |-Excutor
                            |-StatementHandler
                                 |- ParameterHandler , ResultSetHandler
                                              TypeHandler 
 }
 save(){
    sqlSession.insert("namespace.id",参数)
  }
}

手写Mapper接口的实现类代理

来回顾一下动态代理设计模式:

image-20221119114310496

核心逻辑需要SqlSession来实现,SqlSession操作JDBC又需要statement,namespace.id: 可以通过接口class和方法名拿到。(接口的方法名不就是mapper标签中的id吗?)

所以代理类的构造方法如下设计:

public class MyMapperProxy implements InvocationHandler {

    private SqlSession sqlSession;

    private Class daoClass;

    public MyMapperProxy(SqlSession sqlSession,Class daoClass){
        this.sqlSession = sqlSession;
        this.daoClass = daoClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("namespace:"+daoClass.getName()+"."+method.getName());
        return sqlSession.selectList(daoClass.getName()+"."+method.getName());
    }
}

测试方法如下:

@Test
    public void testPorxy() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("MyBatis/MyBatis-config.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);

        SqlSession sqlSession = factory.openSession();
        Class[] interfaces = new Class[]{UserDao.class};
        //创建代理
        UserDao userDao = (UserDao) Proxy.newProxyInstance(
                MybatisTest.class.getClassLoader(),
                interfaces,
                new MyMapperProxy(sqlSession,UserDao.class));

        List<User> users = userDao.queryAll();
        users.forEach(System.out::println);
    }

image-20221119141053617

那如果我们userDao中接口如果有参数呢?Dao中还会有很多方法,上面的例子只有一个方法。

源码探究

  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.newInstance(sqlSession); 就相当于上面例子中 Proxy.newProxyInstance
MapperProxyFactory核心代码分析:
public class MapperProxyFactory<T> {

  //各种xxxDao的class文件
  private final Class<T> mapperInterface;
  //方法缓存
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
     //核心代码,创建代理,mapperProxy就是具体的代理实现
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }


  public T newInstance(SqlSession sqlSession) {
    //相当于上面简单例子中的MyMapperProxy中的构造方法
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
MapperProxy

看完了创建代理的工厂源码,下面该去看看添加的额外方法了:这里是关键代码,我们结合debug来看。

MapperProxy的构造方法如下:

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    //方法缓存
    this.methodCache = methodCache;
  }

最开始的方法缓存是空的:

image-20221119151501346

MapperProxy实现了InvocationHandler,所以我们核心看invoke方法的实现:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    
  // ....
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
       //如果是Object中的方法直接执行,例如toString、equals、wait...
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
         //核心代码,对dao接口中的方法做了封装,然后再invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
    // ....
}

这里通过debug来看一下cachedInvoker(method)的作用:

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        //computeIfAbsent() 方法对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hashMap 中。
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        //判断是不是默认方法
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          //不是默认方法
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

debug发现执行完这个方法,methodCache中有了我们正在调用的userDao中的方法:

image-20221119152920893

那么这个mapperMethod又是什么呢?

MapperMethod
public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
}

image-20221119153853184

实际上MapperMethod就是封装了两个非常核心的成员变量:

  • SqlCommand :sql命令

        private final String name; //标签id(namespace.id)
        private final SqlCommandType type;   //Sql命令类型:这条sql是查询还是插入?
    
    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
          final String methodName = method.getName();
          final Class<?> declaringClass = method.getDeclaringClass();
          MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
              configuration);
          if (ms == null) {
            if (method.getAnnotation(Flush.class) != null) {
              name = null;
              type = SqlCommandType.FLUSH;
            } else {
              throw new BindingException("Invalid bound statement (not found): "
                  + mapperInterface.getName() + "." + methodName);
            }
          } else {
            //核心代码
            name = ms.getId();
            type = ms.getSqlCommandType();
            if (type == SqlCommandType.UNKNOWN) {
              throw new BindingException("Unknown execution method for: " + name);
            }
          }
        }
    

    image-20221119154607485

  • MethodSignature 方法签名(返回值、分页、参数…)

    public static class MethodSignature {
    
        private final boolean returnsMany;
        private final boolean returnsMap;
        private final boolean returnsVoid;
        private final boolean returnsCursor;
        private final boolean returnsOptional;
        private final Class<?> returnType;
        private final String mapKey;
        private final Integer resultHandlerIndex;
        private final Integer rowBoundsIndex;
        private final ParamNameResolver paramNameResolver;
    
        public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
          Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
          if (resolvedReturnType instanceof Class<?>) {
            this.returnType = (Class<?>) resolvedReturnType;
          } else if (resolvedReturnType instanceof ParameterizedType) {
            this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
          } else {
            this.returnType = method.getReturnType();
          }
          this.returnsVoid = void.class.equals(this.returnType);
          this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
          this.returnsCursor = Cursor.class.equals(this.returnType);
          this.returnsOptional = Optional.class.equals(this.returnType);
          this.mapKey = getMapKey(method);
          this.returnsMap = this.mapKey != null;
          this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
          this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
          //@param 中的参数名是如何解析的,在这一步
          this.paramNameResolver = new ParamNameResolver(configuration, method);
        }
    

    image-20221119155325082

ParamNameResolver方法:处理@Param

public ParamNameResolver(Configuration config, Method method) {
    this.useActualParamName = config.isUseActualParamName();
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // @Param was not specified.
        if (useActualParamName) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }

分析到这里,我们已经将mapper.xml中的各种参数返回值都封装到了MapperMethod中,下面也就该找真正执行sqlSession的地方:

invoke

在MapperProxy类中,真正执行SqlSession相关操作的代码在mapperMethod.execute

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }

image-20221119160631450

进入到execute方法中:这里就是我们熟悉的sqlSession方法:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else 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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    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;
  }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/43839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL----存储过程

目录 一、存储过程的介绍 二、存储过程的基本语法 三、变量 &#xff08;1&#xff09;系统变量 &#xff08;2&#xff09;用户自定义变量 &#xff08;3&#xff09;局部变量 四、存储过程的语法详解 &#xff08;1&#xff09;if判断 &#xff08;3&#xff09;条件…

数据要想管理得好,不得不提开源大数据处理解决方案

在很多企业里&#xff0c;内部数据的管理几乎是一团糟的。在大数据时代的环境中&#xff0c;不少企业急需要提升数据管理的效率&#xff0c;因此想通过一些有利途径来实现这一目的。开源大数据处理解决方案就是其中一个有效途径&#xff0c;是助力企业做好数据管理&#xff0c;…

07 ConfigMap/Secret:怎样配置、定制我的应用

文章目录1. ConfigMap/Secret 介绍1.1 为什么kubernets 要使用应用的配置管理&#xff1f;1.2 有什么类别的配置信息&#xff1f;2. 什么是 ConfigMap&#xff1f;2.1 创建ConfigMap模板文件2.1.1 ConfigMap 怎么生成带data 字段的 模板2.2 创建ConfigMap 对象2.3 查看ConfigMa…

傻白入门芯片设计,一颗芯片的诞生(九)

CPU生产和制造似乎很神秘&#xff0c;技术含量很高。许多对电脑知识略知一二的朋友大多会知道CPU里面最重要的东西就是晶体管了&#xff0c;提高CPU的速度&#xff0c;最重要的一点说白了提高主频并塞入更多的晶体管。由于CPU实在太小&#xff0c;太精密&#xff0c;里面组成了…

Java中的多线程如何理解——精简

目录 线程池处理Runnable任务 线程池处理Callable任务 Executors的工具类构建线程池对象 引言 通过前面的学习&#xff0c;我们已经学会了线程是如何创建的以及线程的常用方法&#xff0c;接下来呢&#xff0c;我们将要深入性了解线程中的知识&#xff0c;主要是线程安全&…

基于PHP+MySQL学生信息管理系统的开发与设计

一直以来我国领导人提倡以人为本的治国方案,而大学是未来人才的培养基地,如何能够更好的对学生信息进行管理,是很多高校一直在研究的一个问题,只有更加科学的对学生信息进行管理,才能够更加积极的培养国家的栋梁之才。 本系统是一个学生信息信息管理系统,为了能够更加灵活的对学…

mysql InnoDB 事务的实现原理

前言 关于mysql的InnoDB存储引擎的关键知识点&#xff0c;已经输出了6篇文章了&#xff0c;但是好像阅读量并不大&#xff0c;可能大家都不太喜欢理论性特别强的东西&#xff1f;或者是这些知识点难度有点高&#xff0c;不太容易被接受&#xff1f;不过&#xff0c;我觉得我分享…

【Java实战】工作中并发处理规范

目录 一、前言 二、并发处理规范 1.【强制】获取单例对象需要保证线程安全&#xff0c;其中的方法也要保证线程安全。 2.【强制】创建线程或线程池时请指定有意义的线程名称&#xff0c;方便出错时回溯。 3.【强制】线程资源必须通过线程池提供&#xff0c;不允许在应用中…

数仓数据同步策略

学习内容一、同步策略一、同步策略 数据同步策略的类型包括&#xff1a;全量同步、增量同步、新增及变化同步、特殊情况 全量表&#xff1a;存储完整的数据增量表&#xff1a;存储新增加的数据新增及变化表&#xff1a;存储新增加的数据和变化的数据特殊表&#xff1a;只需要…

定义自定义指令;inserted()、update()

自定义指令的意义&#xff1a;对普通DOM元素进行底层操作&#xff1b; 作用 &#xff1a;可以获取到底层的dom&#xff0c;拿到想要的节点&#xff0c;从而进行操作&#xff1b; 实际应用&#xff1a;可以通过指令知道什么时候dom创建完成&#xff0c;从而进行依赖dom的库的初…

刷爆力扣之有效的山脉数组

刷爆力扣之有效的山脉数组 HELLO&#xff0c;各位看官大大好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 今天阿呆继续记录下力扣刷题过程&#xff0c;收录在专栏算法中 &#x1f61c;&#x1f61c;&#x1f61c; 该专栏按照不同类别标签进行刷题&#x…

超级浏览器的Cookies实现跨境电商防关联

大家有没有过这种感觉&#xff0c;打开电脑或手机&#xff0c;一些你喜欢的视频&#xff0c;总能一下子打到心巴上;心心念念想要下单的东西&#xff0c;总是不停出现在屏幕上诱惑你下单。你以为网络才是最懂你的人&#xff0c;其实是大数据正在研究你的一举一动。而聊到大数据&…

Cerebral Cortex:疼痛热刺激引起的脑功能网络分离与整合

目前的研究旨在确定热痛期间大脑网络整合/分离的变化&#xff0c;使用高时间分辨率的网络连接事件优化方法。参与者(n 33)主动判断施加于前臂掌侧的热刺激是否疼痛&#xff0c;然后在每次试验后评价温暖/疼痛强度。我们表明&#xff0c;试验中整合/分离的时间演化与疼痛的主观…

Ubuntu中安装Qt

文章目录Ubuntu中安装必要的软件安装流程配置运行配置运行Ubuntu中安装必要的软件 主要为了打开图形程序 sudo apt-get update sudo apt-get --assume-yes upgrade sudo apt-get install --assume-yes xfce4 xorg-dev libopencc2 libopencc2-data unzip zip主要是一些共享lib…

ECU简介

ECU是电子控制单元的简称&#xff0c;广泛用于汽车系统中&#xff0c;是电控系统的神经中枢。本文将以比较基础的方式展开ECU的工作原理。 一、基本结构 ECU主要由CPU、存储器、IO接口、信息传递总线组成。ECU可以把传感器传入的信号用内存程序和数据启动相应的程序&#xff0c…

人工智能:语音识别技术介绍

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&…

移动Web

her~~llo&#xff0c;我是你们的好朋友Lyle&#xff0c;是名梦想成为计算机大佬的男人&#xff01; 博客是为了记录自我的学习历程&#xff0c;加强记忆方便复习&#xff0c;如有不足之处还望多多包涵&#xff01;非常欢迎大家的批评指正。 目录 一、字体图标 1.1 使用字体图…

Windows使用scp上传文件到linux服务器

我不是管理员&#xff0c;所以上传有点麻烦&#xff0c;需要在windows电脑上操作scp命令&#xff0c; 命令格式&#xff1a;上传文件夹带上-r&#xff0c;上传文件就不用带-r了&#xff0c;而且只能上传到tmp目录下&#xff0c;然后再使用mv命令移动到你想要的目录下&#xff…

宝塔上的wordpress站点更换域名+配置SSL+改版百度收录

前言 好久没写文章了&#xff0c;甚是想念&#xff0c;近半年来发生了很多事情&#xff0c;心态也变了很多。 这个博客自创办以来&#xff0c;原域名叫“is-hash.com”&#xff0c;是我2019年的突发奇想注册此域名&#xff0c;“is-hash”即为“是#”&#xff08;hash是#的英…

力扣第73题

一、题目&#xff1a;73. 矩阵置零 二、题目解析&#xff1a; 解题步骤&#xff1a;注意题目要求原地算法–>利用矩阵的第一行和第一列记录矩阵需要置0的行和列&#xff0c;只要把0所在行和列的第一个位置置为0&#xff0c; 然后再根据第一行和第一列0的位置&#xff0c;对…