手写源码:MyBatis代理Mapper的核心实现

举报
薛伟同学 发表于 2024/12/20 12:04:41 2024/12/20
【摘要】 本文将带领读者一步步手写MyBatis Mapper代理的核心实现,深入理解框架背后的原理和机制。我们将从动态代理、反射等基础知识出发,逐步构建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 关于代理的核心实现就是这样的,只不过他设计的更精美,且考虑问题更加全面。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。