连接MyBatis内部SqlSession与业务接口的代理类MapperProxy

目的

系统中的业务接口需要调用MyBatis的SQL时,业务接口定义的参数不符合MyBatis自己内部的规范,那么就需要把业务接口的参数转换成MyBatis内部参数规,MapperProxy代理就完成了这一职责,下面就来分析一下。

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

private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
}

上面的invoke就是代理类回调的方法,而cachedMapperMethod方法,做了一个缓存。这里MapperMethod类的构造器与MapperMethod类的execute方法就是主要的逻辑,先来看一下MapperMethod构造器。

1 MapperMethod构造器

这里有2个类的实例化,根据图中的查询分析一下这2个类。

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
      // cn.vansky.schedule.time.menu.dao.MenuMapper.findMenuByUserId
      String statementName = mapperInterface.getName() + "." + method.getName();
      MappedStatement ms = null;
      if (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if (configuration.hasStatement(parentStatementName)) {
          ms = configuration.getMappedStatement(parentStatementName);
        }
      }
      if (ms == null) {
        throw new BindingException("Invalid bound statement (not found): " + statementName);
      }
      // cn.vansky.schedule.time.menu.dao.MenuMapper.findMenuByUserId
      name = ms.getId();
      // SELECT
      type = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        throw new BindingException("Unknown execution method for: " + name);
      }
}

public MethodSignature(Configuration configuration, Method method) throws BindingException {
      // 返回值类型Class
      // interface java.util.List
      this.returnType = method.getReturnType();
      // 有无返回值 true:无,false:有
      // false
      this.returnsVoid = void.class.equals(this.returnType);
      // 返回类型是否是集合Collection或者是数组
      // true
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
      // 返回类型是Map,获取注解MapKey
      // 以上方法调用,值为null
      this.mapKey = getMapKey(method);
      // 返回类型是否Map
      // false
      this.returnsMap = (this.mapKey != null);
      // 参数是否有@Param注解
      // true
      this.hasNamedParameters = hasNamedParams(method);
      // null
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      // null
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      // 获取参数列表
      // 0 -> userId 
      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}

SqlCommand类获取处理的唯一标识及SQL语句类型,MethodSignature类对业务接口方法的入参类型及出参类型进行处理。

2 MapperMethed执行入口-->execute方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      // 新增
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      // 修改
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      // 删除
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      // 查询
      if (method.returnsVoid() && method.hasResultHandler()) {
        // 无返回值void
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        // 集合Collection或数组
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        // Map
        result = executeForMap(sqlSession, args);
      } else {
        // 唯一结果
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
}

增(INSERT)、改(UPDATE)、删(DELETE),这3种操作可以归纳为一种,首先对业务接口的实际入参转成MyBatis内部参数,然后调用SqlSession的处理方法,最后对结果进行处理,返回结果。

查(SELECT)有4中情况。

一:无返回值

二:返回是集合Collection或者数组

三:返回是Map

四:返回一个结果

4种情况的具体细节不再分析。

总结

综上首先需要先把业务接口的实际入参转成MyBatis内部的参数,然后调用SqlSession相应的处理方法,最后对返回结果进行处理,在返回给业务接口。

时间: 2024-12-15 06:54:28

连接MyBatis内部SqlSession与业务接口的代理类MapperProxy的相关文章

(转)Mybatis MapperScannerConfigurer 自动扫描 将Mapper接口生成代理注入到Spring

Mybatis MapperScannerConfigurer 自动扫描 将Mapper接口生成代理注入到Spring Mybatis在与Spring集成的时候可以配置MapperFactoryBean来生成Mapper接口的代理. 例如 <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInte

MyBatis 源码分析——SqlSession接口和Executor类

mybatis框架在操作数据的时候,离不开SqlSession接口实例类的作用.可以说SqlSession接口实例是开发过程中打交道最多的一个类.即是DefaultSqlSession类.如果笔者记得没有错的话,早期是没有什么getMapper方法的.增删改查各志有对应的方法进行操作.虽然现在改进了很多,但是也保留了很多.我们依旧可以看到类似于selectList这样子的方法.源码的例子里面就可以找到.如下 SqlSession session = sqlMapper.openSession(T

java mybatis学习之$和#区别,mapper代理接口,动态SQL,在日志中输出mybatis的sql语句

1.在mybatis中,$和#的区别: #{}:表示一个预处理参数,参数类型不定,是根据传入的参数类型来设定的.类似于JDBC中的? 特例使用,模糊查询:(针对oracle): and username like concat(concat('%',#{username}),'%') ${}:相当于是我们的JDBC里的字符串拼接.这里就相当于传入的就是一个字符串(不管传入什么样的数据类型,都是字符串) and username like '%${value}%' 2.$和#在mybatis中的优

Mybatis内部原理与插件原理

Mybatis的运行分为两大问题,第一部分是读取配置文件保存在Configuration对象中,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程.相对而言SqlSessionFactory创建比较容易,而SqlSession的执行过程就没那么简单了. 构建SqlSessionFactory Mybatis采用构造模式去创建SqlSessionFactory,我们可以直接使用SqlSessionFactoryBuilder去创建,其内部原理分为两步: 第一步,通

Mybatis 中sqlsession源码解析

一.sqlsession获取过程 1.基础配置 在mybatis框架下进行的数据库操作都需要首先获取sqlsession,在mybatis与spring集成后获取sqlsession需要用到sqlsessionTemplate这个类. 首先在spring对sqlsessionTemplate进行配置,使用到的是 org.mybatis.spring.SqlSessionTemplate 这个类. <!-- SqlSession实例 --> <bean id="sessionTe

全网最通俗易懂理清mybatis中SqlSession、SqlSessionTemplate、SessionFactory和SqlSessionFactoryBean之间的关系

摘自:https://www.cnblogs.com/xiaoming0601/p/12166160.html 我潇洒的灰大狼又回来啦.今天送大家的一句话是: 保持耐心,永远年轻,永远热泪盈眶. 前言 先容我哭一会儿,呜呜呜~昨晚写了一半的文章,还没保存就盖上盖子准备回家,拔下电源准备把电脑塞进书包带回家完成时,懒惰阻止了我,最终还是没带回家,于是,遭报应了,今天早上来,电脑直接就是没电关机了,开机后写的文章再也找不回来了...(不争气的mac真是对不起我前面特地写了一篇文章来夸赞mac真香啊.

无状态会话bean(3)---远程业务接口(未排版)

迄今为止,我们只讨论了使用一个本地业务接口的会话bean.在这种情况下,本地意味着只能由运行在同一个应用程序服务器实例的JavaEE组件声明会话bean的依赖性.例如,远程客户端不可能通过本地接口使用会话bean. 为了容纳远程客户端,会话bean可以采用@Remote注解来标记它们的业务接口,以声明它是远程可用的.下面代码演示了前面所示的HelloService接口的远程版本语法.标记一个接口为远程的相当于使其扩展java.rmi.Remote接口.客户端获取的bean的引用不再是服务器上的一

java中业务接口

今天写完业务层在抽取接口的时候脑子里突然产生了一个问题:抽取接口到底有什么用呢? 在刚刚学习接口的时候知道接口是为了实现java的多继承,但是现在每一个业务类都要抽取一个接口,每当该类需要增加方法的时候还要到接口中增加方法,也就是两份,这个时候感觉维护接口增加了工作量,想来想去也不知道为什么这么做,反正看到别人都是这么做的.为什么呢?在网上找了好多例子,有好多说的是为了后面扩展,也举了例子,也看懂了,但是想想现在做的项目,把每一个业务抽取一个接口,是不是有点过了呢?等待解答..... java中

SQLSERVER连接池内部机制

前言介绍: 当应用程序运行的时候,会有一个连接池的管理控件运行在应用程序的进程里,统一管理应用程序和SQLSERVER建立的所有连接, 并且维护这些连接一直处于活动状态.当有用户发出一个connection open指令时连接池会在自己维护的连接池中找一个处于空闲状态 的连接放回自己管理的连接池里,给这个用户使用.当用户使用完毕后,发出connection close指令,连接池会把这个连接放回自己 管理的连接池里,让他重新处于空闲状态,而不是真的从SQL里登出.这样如果下次有用户需要相同连接,