点击上方"田守枝的技术博客",关注我
Mybatis可以说是目前国内使用最广泛的ORM框架。最原始的使用方式下,我们将sql写在xml配置文件中,通过SqlSession,根据statementId来唯一指定要执行的sql。从Mybatis 3.0之后,我们可以通过一个Mapper映射接口来完成相同的功能。你是否思考过,Mapper映射接口内部是如何完成这样的功能的。本文从源码的角度,深入分析mybatis 映射器接口的工作原理。
1 基础回顾
在最原始的情况下,我们需要使用SqlSession类,通过namespace.id方式来定位一个sql,如:
String namespace="com.tianshouzhi.mybatis.UserMapper";
sqlSession.insert(namespace+".insert",user);
sqlSession.selectOne(namespace+".selectById",1);
sqlSession.update(namespace+".update",user);
sqlSession.delete(namespace+".deleteById",1);
从Mybatis 3.0开始,引入了Mapper映射器接口,我们可以直接通过一个接口来引用需要使用的sql。只要这个接口满足以下条件,即可以引用xml配置文件中的sql:
接口的全路径就是映射文件的namespace属性值
接口中定义的方法,方法名与映射文件中<insert>、<select>、<delete>、<update>等元素的id属性值相同
例如,定义一个UserMapper接口
package com.tianshouzhi.mybatis;
public interface UserMapper {
public int insert(User user);
public User selectById(int id);
public int updateById(User user);
public int deleteById(int id);
}
之后我们就可以直接使用UserMapper类来进行增删改查,如下:
User user=...
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int insertCount = userMapper.insert(user);
user=userMapper.selectById(1);
userMapper.updateById(user);
userMapper.deleteById(1);
} finally {
sqlSession.close();
}
这里的原理很简单:
当接口方法执行时,首先通过反射拿到当前接口的全路径当做namespace,然后把执行的方法名当成id,拼接成namespace.id,最后在xml映射文件中寻找对应的sql。
在匹配上某个sql之后,底层实际上还是利用SqlSession的相关方法来进行操作,只不过这个过程对于用户来说屏蔽了。
另外,mybatis还会自动的根据Mapper接口方法的返回值类型,选择调用SqlSession的不同方法。例如:
返回一个对象,则调用selectOne方法;
返回List,则调用selectList;
返回Map,则调用selectMap。
你可能会好奇,Mapper映射接口,是如何完成这些功能的。在接下来的内容中,笔者将从源码角度来分析Mybatis内部是如何使用JDK动态代理机制来完成这些功能,我们带着几个问题开始源码分析之旅:
SQL与Mapper接口的绑定关系是如何建立的?
动态代理类是按照什么逻辑生成的?
动态代理类是如何对方法进行拦截并处理的?
2 SQL与Mapper接口的绑定关系是如何建立的?
这个过程在mybatis初始化阶段,解析xml配置文件的时候就确定了。具体逻辑是,当解析一个xml配置文件时,会尝试根据<mapper namespace="....">的namespace属性值,判断classpath下有没有这样一个接口的全路径与namespace属性值完全相同,如果有,则建立二者之间的映射关系。
关解析代码位于XMLMapperBuilder的 parse方法中:
XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//根据namespace属性值,尝试绑定对应的Mapper接口
bindMapperForNamespace();
}
...
}
从bindMapperForNamespace方法名,既可以看出来,其作用正是将Mapper映射器接口绑定到某个xml文件的namespace属性值。具体逻辑如下:
XMLMapperBuil