自动映射器Mapper的底层实现原理

本文探讨了MyBatis如何在没有实现类的情况下,通过动态代理返回接口实例并执行数据库操作。重点介绍了自定义增强mapper和MyBatis的MapperProxy机制。MapperProxy作为增强类,拦截请求并执行SQL,使得接口方法能够获取数据库数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

种一棵树最好的时间是十年前,其次是现在;

Mybatis中声明一个interface接口(如代码所示),没有编写任何实现类,Mybatis就能返回接口实例,并调用接口方法返回数据库数据,你知道为什么不?

public interface UserMapper {
    User getUser(Integer userId);
    List<User> getAllUser();
    Map findName();
}

其中使用的就是动态代理;

动态代理的功能:通过拦截器方法回调,对目标target方法进行增强。

1、自定义增强mapper

自己实现一个 InvocationHandler ;

/**
 * @author zhaojm
 * @date 2020/4/11 21:29
 */
public class MapperProxy implements InvocationHandler {
    // 新建一个增强Mapper
    @SuppressWarnings("unchecked")
    public <T> T newInstance(Class<T> clz) {
        return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{clz}, this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Object.class 中的方法 Object
        if(Object.class.equals(method.getDeclaringClass())){
            try {
                return method.invoke(this, args);
            } catch (Throwable t) {
            }
        }
        // 请求
        return new User((Integer)args[0], "matt", 24);
    }

     自己的mapperProxy 测试
    public static void main(String[] args) {
        MapperProxy proxy = new MapperProxy();
        // 增强 UserMapper => MapperProxy
        UserMapper mapper = proxy.newInstance(UserMapper.class);
        // 调用 MapperProxy.invoke
        User user = mapper.getUser(2);
        System.out.print(user.getUserId());
        System.out.print(user.getUsername());
        System.out.print(user.getAge());
        System.out.println();
        // object 方法 调用的是Object.tostring 
        // (此时已经是增强类了 MapperProxy)
        System.out.println(mapper.toString());
    }
}

 输出

# getUser方法是Inte

2 matt 24

# object

org.mybatis.internal.example.handler.MapperProxy@610455d6

 

2、MyBatis MapperProxy

 我们再来看下 Mybatis 如何实现的

2.1、测试类

public static void main(String[] args) {
    String resource = "org/mybatis/internal/example/Configuration.xml";
    Reader reader;
    try {
        // mybatis 初始化
        reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);

        try (SqlSession sqlSession = factory.openSession()) {
            // 1、增强 UserMapper => MapperProxy
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 2、增强类拦截请求,并执行sql
            List<User> userList = userMapper.getAllUser();
            for (User user : userList) {
                System.out.println(user);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.2 MapperProxyFactory  增强 UserMapper => MapperProxy

实现的起点:sqlSession.getMapper(UserMapper.class);

调用 MapperProxyFactory 创建 UserMapper 代理器

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (isDefaultMethod(method)) {
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 截断请求,执行sql
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

2.3 MapperProxy   增强类拦截请求,并执行sql

这个地方对比自己写的增强方法

org.apache.ibatis.binding.MapperProxy.java 部分源码

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (isDefaultMethod(method)) {
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 截断请求,执行sql
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

如同上述列子一样,userMapper 被增强为 MapperProxy 代理类,代理类接管了请求之后继续执行后续操作(查询返回结果集)

 

这样我们写的Mapper层的接口为什么不需要实现类与执行逻辑了。


以上是学习MyBatis 技术内幕时的总结,有关于Mybatis的相关总结会继续下去(立flag)如果有误欢迎留言指出。 

种一棵树最好的时间是十年前,其次是现在;

### MyBatis-Plus 分页插件的底层源码实现原理 #### 方法签名解析 `default <E extends IPage<T>> E page(E page)` 是一个默认方法,用于执行分页操作。此方法的核心功能在于调用 `this.page(page, Wrappers.emptyWrapper())` 来进一步处理分页逻辑[^1]。 #### 分页核心机制 MyBatis-Plus 的分页插件主要依赖于 AOP(面向切面编程)和拦截器技术来实现其功能。具体来说: 1. **拦截器的作用** 插件通过自定义 SQL 拦截器捕获所有的查询请求。当检测到某个方法被调用时,拦截器会对该方法及其参数进行分析。如果发现当前的操作是一个查询操作,则触发分页逻辑;反之,如果不是查询操作,则不会介入任何额外的处理[^2]。 2. **IPage 对象的识别与绑定** 在进入分页逻辑之前,拦截器会利用 Java 反射机制扫描目标方法的所有参数列表,寻找实现了 `IPage` 接口的对象实例。一旦找到符合条件的 `IPage` 参数,就会将其作为分页上下文的一部分保存下来并参与后续的 SQL 构建过程。如果没有匹配到对应的 `IPage` 类型参数,则直接跳过分页流程。 3. **SQL 动态拼接** 当确认需要执行分页时,框架会依据数据库类型动态生成适合的分页语句。例如,在 MySQL 中通常采用 `LIMIT OFFSET` 子句实现分页效果,而在 Oracle 数据库中可能需要用到子查询或者 ROWNUM 函数等方式完成相同目的。整个过程中涉及到了大量针对不同数据库特性的适配工作,从而保证跨平台兼容性。 4. **返回结果封装** 完成分页后的数据会被重新组装成指定类型的 `IPage` 结果集形式反馈给调用方。这不仅包含了实际查得的数据记录集合,同时还携带有关总条数统计等元信息以便前端展示更多关于分页状态的内容。 #### 开发环境准备提示 为了能够顺利使用 MyBatis-Plus 提供的功能特性,开发者需先确保自己的开发环境中已正确配置好 IntelliJ IDEA 并导入必要的 Maven 或 Gradle 项目依赖项。只有这样才可以充分发挥出诸如自动补全代码、调试支持等方面的便利之处[^3]。 ```java // 示例:如何在 Service 层调用分页接口 @Override public Page<User> selectUserPage(Page<User> page) { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); return userMapper.selectPage(page, queryWrapper); } ``` 上述例子展示了服务层如何简单地借助 Mapper 映射文件配合条件构造器一起完成复杂的业务需求场景下的高效检索任务。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值