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