探秘MyBatis:手写Mapper代理的源码解析与实现

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 探秘MyBatis:手写Mapper代理的源码解析与实现

前言

基于前面写的文章:MyBatis精髓揭秘:Mapper代理实现的黑盒探索,里面详细的介绍了 MyBatis 代理的实现逻辑,整体来看就是基于 JDK 动态代理的实现,虽然我们在使用的时候没有创建任何的实现类,但是基于动态代理技术,我们可以无中生有。

本文我们就基于这个核心思想,手写一份超精简的 MyBatis 源码

前提准备

准备数据库表并创建实体

/**
 * 账户实体
 *
 * @author 薛伟
 */
public class Account implements Serializable {
    private Integer id;
    private String name;
    private String password;
    public Account() {
    }
    public Account(Integer id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "Account{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + '}';
    }
}

创建数据访问层

/**
 * 账户数据库访问
 *
 * @author 薛伟
 */
@Mapper
public interface AccountDao {
    /**
     * 查询全部
     */
    List<Account> getAll();
}

创建XML配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 配置 namespace -->
<mapper namespace="world.xuewei.mybatis.dao.AccountDao">
    <select id="getAll" resultType="Account">
        select *
        from account;
    </select>
</mapper>

我们这里只准备了一个最简单的查询方法。

创建测试类

public class DaoTest {
    private SqlSession sqlSession;
    @Before
    public void before() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sessionFactory.openSession();
    }
    @After
    public void after() {
        sqlSession.commit();
    }
  
    // 这里编写测试类
}

代码实现

首先我们可以基于原生的 ibatis 和 MyBatis 来调用上面创建的 AccountDao.getAll 方法实现查询效果。

/**
  * 测试原生 iBatis 
  */
@Test
public void sqlSessionTest() {
    List<Account> list = sqlSession.selectList("world.xuewei.mybatis.dao.AccountDao.getAll");
    System.out.println(list);
}
/**
  * 测试原生 MyBatis 
  */
@Test
public void testGetAll() {
    AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
    System.out.println(accountDao.getAll());
}

接下来就是代理的核心实现

/**
  * 测试自定义代理实现
  */
@Test
public void proxyTest() {
    ClassLoader classLoader = this.getClass().getClassLoader();
    Class<?>[] interfaces = new Class[]{AccountDao.class};
    MyMapperProxy handler = new MyMapperProxy(sqlSession, AccountDao.class);
    AccountDao dao = (AccountDao) Proxy.newProxyInstance(classLoader, interfaces, handler);
    System.out.println(dao.getAll());
}

上面的代码我们为 AccountDao 创建了代理对象,调用方法的增强逻辑交个 去处理。初始化 MyMapperProxy 时将当前的 SqlSession 对象和要 Mapper 的类型传递过去。接下来我们看一下 MyMapperProxy 是如何实现的。

/**
 * Mapper 代理增强
 *
 * @author XUEW
 */
public class MyMapperProxy implements InvocationHandler {
    private final SqlSession sqlSession;
    private final Class<?> daoClass;
    public MyMapperProxy(SqlSession sqlSession, Class<?> daoClass) {
        this.sqlSession = sqlSession;
        this.daoClass = daoClass;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return sqlSession.selectList(daoClass.getName() + "." + method.getName(), args);
    }
}

我们这个示例只有一个查询方法,并且没有参数,所以这里的 invoke 方法是非常简单的。直接就调用了 sqlSession 的查询方法。

尽管代码相当的简单,但是 MyBatis 关于代理的核心实现就是这样的,只不过他设计的更精美,且考虑问题更加全面。


相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
14 2
|
3天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
16天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
36 3
|
1月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
30 10
|
1月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
53 5
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
107 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
66 0
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
52 0
|
1月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
59 0

推荐镜像

更多