在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();
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vAOP3Jc-1684657692315)(res/Mybatis创建Mapper接口实现类/image-20230521103126185.png)]](https://i-blog.csdnimg.cn/blog_migrate/b91a480a3896248b0982b1f53874c37f.png)
可以看到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);
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z13vGAd0-1684657692316)(res/Mybatis创建Mapper接口实现类/image-20230521154328909.png)]](https://i-blog.csdnimg.cn/blog_migrate/6009ceddec0070c8e2940469d70a29e1.png)
进入到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");
文章详细阐述了Mybatis如何通过JDK动态代理为Mapper接口创建实现类,解释了创建代理对象的流程,包括MapperProxy的作用和MapperMethod的执行过程,展示了代理对象如何执行Mapper接口中的方法,最终将SQL语句交由SqlSession处理。
1599

被折叠的 条评论
为什么被折叠?



