当前位置: 首页 > news >正文

Mybatis执行SQL流程(五)之MapperProxy与MapperMethod

文章目录

    • MapperProxy
    • MapperMethod
      • Insert
      • Select

MapperProxy

实现 InvocationHandler 接口,作为代理的具体实现。主要功能就是执行invoke 方法。

invoke源码如下:

// mapperProxy 的 invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args); // 此处对Object的方法做了判断,例如toString,hashCode等} else {return cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}
}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {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 {// 普通 Mapper 方法。// PlainMethodInvoker 包装 MapperMethodreturn new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}
}
private static class PlainMethodInvoker implements MapperMethodInvoker {private final MapperMethod mapperMethod;public PlainMethodInvoker(MapperMethod mapperMethod) {super();this.mapperMethod = mapperMethod;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);  // 接下来就是核心处理类MapperMethod如何处理SQL了。}
}

MapperMethod

MapperMethod是核心执行类,包含:

  • SQL 命令类型(select/insert等)

  • 参数处理逻辑

  • 结果映射规则

  • 组件职责
    MapperProxy代理入口,路由方法调用
    MapperMethodInvoker方法调用器抽象接口
    DefaultMethodInvoker处理接口默认方法
    PlainMethodInvoker处理普通 Mapper 方法
    MapperMethod执行实际 SQL 逻辑
// MapperMethod.javapublic Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args); // 参数转换, 若是单个数据,则直接返回参数值,若是多个,则返回Mapresult = rowCountResult(sqlSession.insert(command.getName(), param));// 执行sql,并返回执行结果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()) { // 方法返回类型为 collection 或 数组result = executeForMany(sqlSession, args);} else if (method.returnsMap()) { // 方法返回类型为 Mapresult = executeForMap(sqlSession, args);} else if (method.returnsCursor()) { // 方法返回类型为 游标result = executeForCursor(sqlSession, args);} else {// 其他,返回单个对象 例如返回类型为 UserObject 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;}

接下来了解insert 与select语句。

Insert

  • 转换参数
// Object param = method.convertArgsToSqlCommandParam(args);
// 内部调用 getNamedParams 方法
// 返回一个不带名称的非特殊参数。使用命名规则命名多个参数。除了默认名称外,此方法还添加通用名称(param1、param2 等)。public Object getNamedParams(Object[] args) {final int paramCount = names.size(); // SortedMap<Integer, String> names; key为下标,value为参数名,从@Param中获取,若没有该注解,则使用下标。例如// aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}// aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}if (args == null || paramCount == 0) {return null;} else if (!hasParamAnnotation && paramCount == 1) { // 若没有使用Param注解,且参数个数仅为1个,则直接返回对象。例如参数为user对象,则返回的Object也是User对象Object value = args[names.firstKey()];return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);} else {// 包含@Param注解,或参数个数不止一个final Map<String, Object> param = new ParamMap<>();int i = 0;for (Map.Entry<Integer, String> entry : names.entrySet()) {param.put(entry.getValue(), args[entry.getKey()]); // 将参数存入param map中,key为参数名value为参数值// add generic param names (param1, param2, ...) // todo 为什么要添加通用参数名,暂时不知道在哪里用到,后续为sql的?占位符赋上对应的数据时,也是根据参数名获取的final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);// ensure not to overwrite parameter named with @Paramif (!names.containsValue(genericParamName)) { // 避免覆盖@Param中的参数名param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}}

以包含@Param注解为例:
其中param注解转换参数例如:

@Insert("insert into user(id, username) values (#{id}, #{username})")
int insert2User(Long id, @Param("username") String name);

在这里插入图片描述

其中names的值为:

(0, "id"), (1, "username");
// 将 参数下标与Param的值对应。若不存在Param注解,则使用参数名

传参args中包含两个入参值:10、testparam。经过转换后param包含了4个kv键值对。两个key是mapper接口中的入参名(如果有Param注解,以Parma注解中的为准;另外两个key是根据规则拼接的,即param中既包含了@Param注解中的参数名键值对,也包含了根据规则拼接好的参数键值对。

  • sqlSession.insert执行流程:
//         result = rowCountResult(sqlSession.insert(command.getName(), param));
// 1. sqlSession.insert
sqlSession.insert-> DefaultSqlSession.insert-> DefaultSqlSession.update-> ParamNameResolver.wrapToMapIfCollection(object, null)-> Executor.update // 此处的执行内容见 Executor 环节
// 2. rowCountResult :返回sql的执行结果,
// 将 Collection 或 数组类型的参数转化为 Map
public static Object wrapToMapIfCollection(Object object, String actualParamName) {if (object instanceof Collection) {ParamMap<Object> map = new ParamMap<>();map.put("collection", object);if (object instanceof List) {map.put("list", object);}Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;} else if (object != null && object.getClass().isArray()) {ParamMap<Object> map = new ParamMap<>();map.put("array", object);Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;}return object;
}

接下来就是要执行Executor.update。此处的执行内容见 Executor 环节

Select

参数转换
sqlSession.selectOne-> DefaultSqlSession.selectOne-> DefaultSqlSession.selectList-> Executor.query // 依然是交给 执行器 去执行sql

查询步骤大致如下:其中涉及到缓存问题,后续单独补充

ClientMapperProxycachedInvokerInvokermethodCacheMapperMethod调用Mapper方法直接调用获取调用器检查缓存返回调用器创建调用器存入缓存alt[缓存存在][缓存不存在]返回调用器执行invoke()执行SQL逻辑返回结果返回结果alt[Object方法][Mapper方法]返回结果ClientMapperProxycachedInvokerInvokermethodCacheMapperMethod

接下来就是要执行Executor.query。此处的执行内容见 Executor 环节

总结:MapperProxy作为JDK代理的实际执行内容,内部将执行方法转化为MapperMethod,为增删查改操作进行了区分,不同操作后续的处理内容不同。在MapperMethod中进行了参数转换,具体的执行交给了SqlSession,在Sqlsession中将操作交给了Executor执行器。

http://www.lryc.cn/news/626684.html

相关文章:

  • 在完全没有无线网络(Wi-Fi)和移动网络(蜂窝数据)的环境下,使用安卓平板,通过USB数据线(而不是Wi-Fi)来控制电脑(版本2)
  • 力扣 hot100 Day79
  • 大数据常见问题分析与解决方案
  • ODPS 十五周年实录 | 为 AI 而生的数据平台
  • Flask高效数据库操作指南
  • 面向AI应用的新一代迷你电脑架构解析 ——Qotom Q51251AI
  • 【39页PPT】大模型DeepSeek在运维场景中的应用(附下载方式)
  • imx6ull-驱动开发篇31——Linux异步通知
  • Jumpserver堡垒机使用VNC录入Linux图形界面资产
  • 十大经典 Java 算法解析与应用
  • 机器学习--数据清洗—(续篇)
  • (nice!!!)(LeetCode 每日一题) 1277. 统计全为 1 的正方形子矩阵 (动态规划)
  • C++ MFC/BCG编程:文件对话框(CFileDialog、CFolderPickerDialog)
  • 力扣48:旋转矩阵
  • 数据结构之排序大全(1)
  • 2.Shell脚本修炼手册之---创建第一个 Shell 脚本
  • 大模型入门实战 | 单卡 3090 十分钟完成 Qwen2.5-7B 首次微调
  • 电脑驱动免费更新? 这款驱动管理工具:一键扫更新,还能备份恢复,小白也会用~
  • c语言多任务处理(并发程序设计)
  • iOS App 混淆工具实战 医疗健康类 App 的安全与合规保护
  • Elasticsearch 写入全链路:从单机到集群
  • [系统架构设计师]面向服务架构设计理论与实践(十五)
  • [element-plus] el-tree 拖拽到其他地方,不拖拽到树上
  • Vue3 element ui 给表格的列设置背景颜色
  • 晨控EtherCAT设备分配IP操作手册
  • LWIP的TCP协议
  • 在 Golang 中复用 HTTP 连接
  • 26_基于深度学习的茶叶等级检测识别系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • CTFshow系列——命令执行web38-40
  • Qt音乐播放器项目实践:本地持久化与边角问题处理