基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Mapper接口(DAO接口)的工作原理是什么?

知识点图片

MyBatis Mapper接口无需实现类,通过动态代理自动生成代理对象,负责SQL执行与结果映射,消除样板代码,实现关注点分离。

这是一个非常经典且核心的关于MyBatis框架的问题。我们来深入浅出地解释一下。

核心摘要

简单来说,Mapper接口(DAO接口)之所以不需要我们编写实现类,是因为 MyBatis框架在运行时通过动态代理(Dynamic Proxy)技术,自动为我们创建了一个代理对象。这个代理对象实现了接口中的方法,并帮助我们完成了所有与数据库的交互。

我们只需要定义好“契约”(接口),以及这个“契约”该如何执行(通过XML或注解提供SQL),MyBatis就会帮我们把剩下的脏活累活全部干完。


一、Mapper接口的工作原理:动态代理的魔力

下面是当你调用一个Mapper接口方法时,背后发生的一系列事情,我们一步步来看:

1. 加载与解析(程序启动时)

当你的应用程序(尤其是整合了Spring的)启动时,MyBatis会:

  • 读取配置文件(如 mybatis-config.xml)和Mapper映射文件(XML)。
  • 扫描所有被 @Mapper 注解标记的接口,或者XML中指定的Mapper接口。
  • 将每个SQL语句(无论是XML里的 <select><insert> 标签,还是注解里的 @Select@Insert)解析成一个MappedStatement对象。
  • 这些MappedStatement对象被存放在一个全局的Configuration对象里,以Key-Value的形式存储。这个Key通常是 “接口的全限定名 + 方法名”,例如 com.example.mapper.UserMapper.selectById

这个阶段,MyBatis已经知道了每个接口方法应该对应哪一条SQL语句以及如何处理参数和返回结果。

2. 生成代理对象(获取Mapper时)

当你从Spring容器中注入(@Autowired)一个Mapper接口,或者手动调用sqlSession.getMapper(UserMapper.class)时,神奇的事情发生了:

  • MyBatis并不会去寻找一个叫 UserMapperImpl 的实现类,因为它根本不存在。
  • 它会使用JDK动态代理(或者CGLIB,但对于接口通常是JDK动态代理)技术,在内存中动态地创建一个代理对象。
  • 这个代理对象实现了 UserMapper 接口,所以它可以被正常地赋值给你声明的 UserMapper 变量。
  • 这个代理对象内部包含了一个重要的处理器,叫做MapperProxy

所以,你拿到的userMapper变量,实际上是MapperProxy这个代理类的实例。

3. 方法调用与拦截(执行代码时)

当你调用接口的方法时,例如 userMapper.selectById(1)

  • 这个调用实际上是作用于上一步生成的那个代理对象上。
  • 动态代理机制会将这个方法调用拦截下来,转交给MapperProxyinvoke方法来处理。

4. 执行SQL(代理对象内部)

MapperProxyinvoke方法是真正的执行者,它会:

  1. 获取SqlSession:从数据库连接池中获取一个数据库连接,并包装成SqlSession对象。
  2. 定位MappedStatement:根据你调用的方法(com.example.mapper.UserMapper.selectById),去第一步加载好的Configuration对象中找到对应的MappedStatement
  3. 执行数据库操作MappedStatement中包含了所有需要的信息(SQL语句、参数类型、返回类型等)。MapperProxy会调用SqlSession中相应的方法(如 selectOne, selectList, insert等),并把方法参数(例如1)和MappedStatement传递过去。
  4. 结果映射SqlSession执行完SQL后,从数据库获取到ResultSet。MyBatis会根据MappedStatement中定义的返回类型(resultTyperesultMap),自动将ResultSet中的数据映射成Java对象(例如一个User对象或List<User>)。
  5. 返回结果:将映射好的Java对象返回给调用方。

整个过程对我们开发者是完全透明的,我们感觉就像在调用一个普通的Java接口方法一样。


二、为什么不需要我们手动编写实现类?

理解了上面的工作原理,这个问题就迎刃而解了。我们不需要实现类,是因为:

  1. MyBatis已经自动生成了:MyBatis通过动态代理技术,在运行时为我们动态创建了一个实现了接口所有方法的代理类。这个代理类就是“实现类”,只是它存在于内存中,而不是一个.java文件。

  2. 实现类的代码是重复且模板化的:如果让我们自己写实现类,代码会是什么样的?无非就是:

    • 获取SqlSession
    • 拼接statementId
    • 调用sqlSession.selectOne(...)
    • 处理异常
    • 关闭SqlSession
    • ...
      这些代码对于每个DAO方法都大同小异,充满了大量的样板代码(Boilerplate Code)。MyBatis把这些重复的工作都封装在了代理对象内部,极大地解放了开发者。
  3. 实现了关注点分离(Separation of Concerns)

    • 接口(Interface):只负责定义数据访问的契约,即有哪些操作,参数是什么,返回什么。
    • XML或注解:只负责定义SQL以及如何映射结果。
    • MyBatis框架:负责将两者粘合起来,处理所有JDBC的底层细节、事务管理、连接池等。
      开发者只需要关注业务逻辑和SQL本身,而不需要关心底层的实现细节,代码更加清晰、简洁,易于维护。
  4. 符合面向接口编程的最佳实践
    业务层(Service)依赖的是DAO接口,而不是具体的实现。这使得系统耦合度更低,也更容易进行单元测试(例如,可以用一个Mock实现来替换掉MyBatis的代理对象)。

总结与比喻

你可以把Mapper接口想象成一家餐厅的菜单(Menu)

  • 你(开发者) 是顾客。
  • Mapper接口 是菜单,上面写了有哪些菜(方法),需要什么原料(参数)。
  • XML/注解里的SQL 是菜谱,详细说明了每道菜怎么做。
  • MyBatis的动态代理 是一个神通广大的服务员

你只需要看着菜单点菜(调用接口方法),服务员(代理对象)就会自动拿着你的点单(方法调用信息)和菜谱(SQL),去后厨(数据库)把菜做好(执行SQL并映射结果),然后端到你面前(返回Java对象)。

你完全不需要关心服务员是怎么跟后厨沟通的,也不需要自己去后厨做菜,这就是MyBatis Mapper接口设计的优雅之处。

00:00
00:00