在上一篇文章中我们对Mybatis的流程进行了一次Debug,对于整体流程有了更清晰的认识。在我们Debug的过程中也提出了一个问题,**我们使用的Mapper接口并没有写接口的实现,那么这个接口是如何执行业务逻辑的呢?**今天我们将带着这个问题进行进一步学习。
在解答今天的问题之前,我们写回顾一下Mapper接口,这里以一个简单的查询为例。首先我们会定义一个Mapper接口如下
public interface PersonMapper {
List<Person> selectAll();
Person selectById(@Param("id")Long id);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmxy.mapper.PersonMapper">
<resultMap id="BaseResultMap" type="com.cmxy.entity.Person">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
resultMap>
<select id="selectAll" resultMap="BaseResultMap" >
select * from t_person
select>
<select id="selectById" resultMap="BaseResultMap">
select * from t_person where id = #{id}
select>
mapper>
1、namespace(命名空间)指向了PersonMapper
2、resultMap:定义了结果集映射
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
//读取mybatis配置文件并获取输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
PersonMapper mapper = session.getMapper(PersonMapper.class);
Person person = mapper.selectById(1L);
System.out.println(person);
}
}
我们知道在Java中,接口是不能被实例化的。我们自己又没有实现Mapper接口所提供的方法那么Myabtis是如何通过Mapper接口来执行我们所编写的SQL呢?带着这个问题我们进行Debug。
这里我们可以看到我们写的mapper并不是一个接口,而是一个名为org.apache.ibatis.binding.MapperProxy@78691363的对象,从该类的命名我们可以看出这个是一个代理类。所以这里我们得出第一个论,在Mybatis中执行接口中方法的并不是接口本身,而是接口的实现类。那么问题来了
在我们的编程中并没有显式的编写Mapper接口的实现类,那么该实现类是如何出现的呢。结合该类的名字MapperProxy,我们可以大概猜测这是一个代理类。代理又分为静态代理和动态代理,很明显这里是动态代理(如果是静态代理则需要自己编码),说到动态代理我们有能想到的是JDK动态代理,那是否真的是这样呢。我们可以Debug获取Mapper的代码:PersonMapper mapper = session.getMapper(PersonMapper.class);
一路Debug我们来到了MapperRegistry这个类的个getMapper方法,其中核心代码为:
return mapperProxyFactory.newInstance(sqlSession); 通过Mapper代理工厂动态的创建Mapper的实现类。继续Debug
很明显这里是就是通过JDK动态代理来创建了接口的实现类。
小结:回答前面的问题
(1)Mapper接口的实现类是Mybatis创建的
(2)在调用getMapper方法时创建
(3)通过JDK动态代理来创建
前面我们大致了解Mybatis中是如何为我们所编写的Mapper接口生成实现类的,现在我们跟着源码具体探究一下生成源码的过程。继续刚才的Debug
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
在这个方法中,核心是如何创建MapperProxy这个对象,我们进入其构造函数来探究。进入MapperProxy类,我们看到了invoke方法(代理类的方法)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断是否为Object类的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
源码分析:首先Mybatis会判断当前类的方法是否属于Object,例如equals、hashcode等方法直接用被代理类执行即可,然后继续判断是不是default方法(JDK8之后接口可以定义Default方法)如果是Default方法也直接使用被代理类来这执行。最后从缓存里取被代理的方法,也就是被例子中的
Person selectById(@Param(“id”)Long id);和List selectAll();
然后调用的mapperMehtod.execute方法,这部分代码在前文中已经讲述过,这里不再赘述。
至此我们已经知道了Mybatis是如何帮我创建接口的实现类,以及如何调用对应方法来执行SQL的,这里以一个图来简单梳理一下流程。
对于Mybatis的执行流程我们已经相对熟悉了,之后的文章会分析一些Mybatis中其他的知识,例如缓存、拦截器等等,希望对你有所帮助。