在Mybatis的开发过程中,程序员更加关注
Mapper
接口中的方法以及
xxxMapper.xml
文件的编写。但是我们仅仅只是写了一个方法名和Sql语句,并且接口是不能被实例化的,那么Mybatis是如何通过
Mapper
接口来执行对应的Sql语句呢?其实是在运行过程中Mybatis通过动态代理的方式创建了
Mapper
接口的实现类。
示例代码
UserDAOMapper.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">
<mapper namespace="edu.hzb.dao.UserDAO">
<resultMap id="BaseResultMap" type="edu.hzb.entity.User">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
</resultMap>
<sql id="BaseColumn">
id, name, age
</sql>
<select id="selectList" resultMap="BaseResultMap" useCache="true">
select <include refid="BaseColumn"></include>
from t_user
</select>
</mapper>
UserMapper
接口:
public interface UserMapper {
List<User> selectList();
}
测试代码:
public void test1() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//Mybatis会通过代理设计模式会为UserDAO接口创建代理实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectList();
}
可以看到userMapper
运行类型是org.apache.ibatis.binding.MapperProxy@8c3b9d
,userMapper
是一个代理对象,那userMapper.selectList()
就是被增强的代理方法。
创建代理对象
JDK动态代理
代理模式主要分为JDK动态代理
和CGlib动态代理
,因为UserMapper
是一个接口,所以默认会采用JDK动态代理。
JDK动态代理是通过反射机制生成一个实现代理接口的匿名类,要求目标对象必须是一个接口。
CGLIB动态代理则使用的继承机制,对目标类(父类)进行继承,生成一个代理类(子类),从而对父类中的方法增强。
通过JDK动态代理
创建代理对象的方法定义:
/**
ClassLoader: 动态代理类没有对应的.class文件,JVM也就不会为其分配ClassLoader,所以需要借用一个 ClassLoader,用来创建代理类的Class对象
interfaces: 目标对象实现的接口,这里就是UserMapper接口
InvocationHandler:对目标对象中的方法进行增强,这里就是对UserMapper接口中的方法增强
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
创建代理对象流程
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Mybatis是如何通过这行代码给UserMapper
接口创建实现类,进入到getMapper(UserMapper.class)
方法中:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//获取Mapper接口代理工厂
//knowMappers是一个Map,key为Mapper接口,value就是MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//通过工厂创建MapperProxy对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
进入到mapperProxyFactory.newInstance(sqlSession)
方法中
public T newInstance(SqlSession sqlSession) {
//构建MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
在了解newInstance(mapperProxy)
方法之前需要先知道MapperProxy
对象是什么:
//实现了InvocationHandler接口,实现了该接口中的Invoke方法,在创建代理对象时就会把MapperProxy对象作为参数传入
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
//sqlSession,执行sql语句需要用到
private final SqlSession sqlSession;
//目标对象实现的接口,这里就是UserMapper接口
private final Class<T> mapperInterface;
//MapperMethod只是对Method进一步封装,添加了SqlCommand和MethodSignature两个属性
private final Map<Method, MapperMethod> methodCache;
}
public class MapperMethod {
//c封装了Sql语句的namespace.id值和类型
private final SqlCommand command;
//封装了方法的返回值和参数
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
}
创建了MapperProxy
对象并且知道了MapperProxy
是什么之后,接下来分析newInstance(mapperProxy)
方法,进入到该方法中:
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
发现这里就是采用JDK动态代理创建代理对象的方法,mapperProxy
就作为InvocationHandler
参数传入了。
执行代理方法
List<User> users = userMapper.selectList();
这里的userMapper
是代理对象,类型是MapperProxy
,而MapperProxy
实现了InvocationHandler
接口,就一定会实现其中的invoke(Object proxy, Method method, Object[] args)
方法。所以最后userMapper
调用的其实是invoke方法。进入到该方法中:
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)) {
//也不会对jdk8中的默认方法进行代理增强
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//只会对Mapper接口中的方法代理
//获取要执行的方法(MapperMethod只是对Method进行了一层包装)
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行Method
return mapperMethod.execute(sqlSession, args);
}
进入到mapperMethod.execute(sqlSession, args)
方法中,就非常直观了:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
//执行插入方法
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
//执行修改方法
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
//执行删除方法
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
//执行查询方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
根据这段代码就会发现,不管是增删改查哪一个方法,最后都是交给SqlSession
来执行,所以其实下面的代码达到的效果是一样的:
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> users = sqlSession.selectList("edu.hzb.dao.UserDAO.selectList");