前言
基于前面写的文章: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 关于代理的核心实现就是这样的,只不过他设计的更精美,且考虑问题更加全面。