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

Mybatis 为什么不需要给Mapper接口写实现类,为什么要使用代理而不是硬编码?

文章目录

  • 核心机制概述
  • 源码分析
    • 1. 获取 Mapper 实例
    • 2. 创建 Mapper 代理对象
    • 3. 拦截方法调用 MapperProxy
    • 4. 关联 SQL 并执行
  • 为什么 MyBatis 采用了代理机制,而不是简单地面向流程化的方式?
      • 1. 解耦和灵活性
      • 2. 方法拦截和事务管理
      • 3. 动态代理支持方法级别的 SQL 定义
      • 4. 增强的扩展性
      • 5. 提高代码的可维护性
  • 为什么MyBatis Mapper接口中的方法不支持重载?

核心机制概述

MyBatis 是一款流行的持久层框架,其核心特点之一是可以直接通过定义 Mapper 接口与数据库交互,而无需显式地为接口编写实现类。这种特性极大地提高了开发效率。那么,MyBatis 是如何实现这一特性的?
MyBatis 通过动态代理机制,为 Mapper 接口生成代理对象,拦截接口方法的调用,并根据方法签名找到对应的 SQL 映射进行执行,最后将执行结果返回给调用者。这一过程主要依赖以下几个关键组件:
• SqlSession:提供与数据库交互的核心 API,用于执行 SQL、获取映射器等。
• MapperProxy:动态代理类,用于拦截 Mapper 接口方法调用。
• MapperMethod:封装了 Mapper 方法与 SQL 映射之间的关联关系。

源码分析

1. 获取 Mapper 实例

当调用 SqlSession.getMapper(Class type) 方法时,MyBatis 会为指定的 Mapper 接口生成代理对象。

<T> T getMapper(Class<T> type);

DefaultSqlSession 是 SqlSession 的默认实现类,其 getMapper 方法逻辑如下:

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

这里将 type 和当前的 SqlSession 传递给了 Configuration 对象。

2. 创建 Mapper 代理对象

Configuration 中的 getMapper 方法会通过 MapperRegistry 创建代理对象:

    /*** 根据 Mapper 类型和 SqlSession 获取 Mapper 的代理实例** @param type Mapper 接口类型* @param sqlSession SqlSession 实例* @param <T> Mapper 接口类型* @return 返回对应的 Mapper 代理实例* @throws RuntimeException 如果没有找到对应的 Mapper 或获取代理实例时出错*/public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);}}

MapperProxyFactory 是一个工厂类,专门用于生成 MapperProxy 代理对象。

   /*** 创建 MapperProxy 的实例,生成指定 Mapper 接口的动态代理对象* @param sqlSession* @return*/@SuppressWarnings("unchecked")public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);}

3. 拦截方法调用 MapperProxy

生成的代理对象会通过 MapperProxy 拦截 Mapper 接口的方法调用。
MapperProxy 类
MapperProxy 实现了 InvocationHandler 接口,其 invoke 方法会处理所有代理方法调用。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);
}private MapperMethod cachedMapperMethod(Method method) {return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}

• 如果调用的是 Object 的方法(如 toString、equals),直接执行。
• 否则,将方法与参数封装为 MapperMethod,并执行对应的 SQL。

4. 关联 SQL 并执行

MapperMethod 负责将接口方法与 SQL 映射绑定,并执行 SQL。
MapperMethod 的执行逻辑

public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.insert(command.getName(), param);break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.update(command.getName(), param);break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.delete(command.getName(), param);break;}case SELECT: {if (method.returnsVoid()) {sqlSession.select(command.getName(), method.convertArgsToSqlCommandParam(args), null);result = null;} else if (method.returnsMany()) {result = sqlSession.selectList(command.getName(), method.convertArgsToSqlCommandParam(args));} else {result = sqlSession.selectOne(command.getName(), method.convertArgsToSqlCommandParam(args));}break;}default:throw new BindingException("Unknown execution method for: " + command.getName());}return result;
}

这里通过 SqlSession 的 insert、update、delete 和 select 方法与数据库交互。

为什么 MyBatis 采用了代理机制,而不是简单地面向流程化的方式?

在 MyBatis 中,使用代理的方式相比于直接在方法内部获取 namespace 对应的 XML 并解析 SQL,具有几个显著的优点。虽然通过手动解析 SQL 也能实现功能,但代理机制的使用带来了更多的灵活性、简洁性和可维护性。

1. 解耦和灵活性

使用代理的核心优点之一是 解耦。代理机制将 SQL 执行和业务逻辑分离,避免了在每个方法中手动查找和解析 SQL。这样可以确保:
• 接口和实现的分离:你定义的接口方法与数据库操作解耦,接口定义只是一个“约定”,不需要担心 SQL 语句如何查找、解析、执行等细节。
• 灵活性:动态代理允许在运行时确定需要执行的 SQL,而不需要在方法中硬编码 SQL 路径和执行细节。SQL 的解析和执行由 MyBatis 的代理类自动完成,开发者专注于业务逻辑,不必关心数据库操作的实现。
如果不使用代理,而是面向流程化,方法内部需要实现:
• 手动加载 SQL 映射文件(如 XML)。
• 从 XML 文件中根据方法名解析对应的 SQL 语句。
• 绑定 SQL 的参数并执行。
• 处理结果集并返回。
这样做会导致每个方法的逻辑变得复杂、重复,降低了代码的可读性和可维护性。

2. 方法拦截和事务管理

使用代理时,MyBatis 可以利用代理对象来拦截方法调用,这样就能:
• 统一处理日志、事务、缓存等跨切面功能。例如,事务管理和 SQL 执行的处理都可以统一通过代理类来完成,不需要在每个方法内部显式地调用事务控制逻辑。
• 统一的错误处理:代理机制可以在方法调用时自动捕获异常并进行统一的处理,如 SQL 异常的转换、事务的回滚等。
这种集中式的管理方式,比在方法内部手动管理事务和错误处理更加简洁、可靠和一致。

3. 动态代理支持方法级别的 SQL 定义

MyBatis 的代理机制通过注解或 XML 配置能够动态地将方法名和 SQL 语句绑定起来。代理对象能在方法调用时,动态查找映射文件中对应的 SQL 语句,并通过传入的参数执行 SQL。
• 例如,接口 UserDao 中的 findUserById(int id) 方法,会在代理类中被拦截,动态解析出对应的 SQL 语句并执行。这种方式的好处是,你不需要在每个方法内部去查找 SQL 映射,MyBatis 会自动根据方法名(或注解)定位对应的 SQL,从而使得代码更加简洁和规范。
如果不使用代理,方法内部就需要自己编写代码来:
• 查找与方法名对应的 SQL。
• 解析 SQL 文件。
• 绑定参数并执行查询。
这种手动的方式不仅增加了冗余代码,还会让每个方法的实现变得更复杂。

4. 增强的扩展性

代理机制还为 MyBatis 提供了很强的扩展性。例如:
• 插件机制:你可以通过自定义 MyBatis 插件来扩展或修改 SQL 执行过程,如日志打印、性能监控、缓存等功能。这些都可以通过代理类来实现,而不需要修改每个方法的实现。
• 跨切面功能:代理对象使得 MyBatis 能够轻松地为方法调用提供横向的功能(例如,缓存、事务、权限检查等),而这些功能在流程化的实现中会变得难以统一处理。

5. 提高代码的可维护性

由于代理机制将所有的 SQL 执行和参数绑定逻辑封装在 MapperProxy 中,开发者只需要关注业务逻辑的实现。修改 SQL、改变数据库连接等都可以在配置层完成,不需要修改业务层的代码。
如果采用面向流程的方式,每次修改 SQL 时都需要修改每个方法的实现,导致代码的维护变得复杂,且容易出错。

为什么MyBatis Mapper接口中的方法不支持重载?

在 MyBatis 中,每个方法与 SQL 语句之间的映射关系是通过 MethodSignature 来建立的。每个方法的签名(方法名和参数类型)都应该是唯一的,以便 MyBatis 能够根据方法签名来匹配 SQL 语句。

如果 Mapper 接口中的方法重载,MethodSignature 将不再唯一,导致 SQL 映射无法确定是哪一个具体的方法。例如,两个方法虽然参数不同,但在解析时会共享相同的方法名,从而无法区分。


http://www.mrgr.cn/news/82089.html

相关文章:

  • Redis高可用集群部署
  • linux上使用cmake编译的方法
  • 从零用java实现 小红书 springboot vue uniapp (7)im 在线聊天功能 关注功能
  • python实现自动登录12306抢票 -- selenium
  • [Qt] 常用控件 | QWidget | “表白程序2.0”
  • 走进深圳华为总部参观研学
  • 数据库的概念和操作
  • day22-YUM与开源项目(Web运维)
  • 我的JAVA-Web进阶--Mybatis
  • ES elasticsearch-8.17.0-windows-x86_64使用
  • vue3学习笔记(11)-组件通信
  • 【阅读笔记】《基于区间梯度的联合双边滤波图像纹理去除方法》
  • STM32配合可编程加密芯片SMEC88ST的防抄板加密方案设计
  • 深度学习blog-Transformer-注意力机制和编码器解码器
  • UE5材质节点BumpOffset
  • GDPU Vue前端框架开发 跨年大礼包
  • Linux(Centos 7.6)基础命令/常用命令说明
  • Linux系统
  • Log4j2的Policies详解、SizeBasedTriggeringPolicy、TimeBasedTriggeringPolicy
  • matlab-数字滤波器设计与实战
  • ESP-IDF学习记录(4) ESP-IDF examples目录
  • 【SQLi_Labs】Basic Challenges
  • 某网站手势验证码识别深入浅出(全流程)
  • 大模型WebUI:Gradio全解系列9——Additional Features:补充特性(下)
  • NFS V4网络文件共享新认识——筑梦之路
  • SpringCloudAlibaba实战入门之路由网关Gateway过滤器(十三)