mybatis的Mapper代理原理

提兵百万西湖上,立马吴山第一峰!这篇文章主要讲述mybatis的Mapper代理原理相关的知识,希望能为你提供帮助。
前言:在mybatis的使用中,我们会习惯采用XXMapper.java+XXMapper.xml(两个文件的名字必须保持一致)的模式来开发dao层,那么问题来了,在XXMapper的文件里只有接口,里面只有方法体,在XXMapper.xml的文件里,里面只有sql,而在java中,方法调用必须通过对象,除非是静态方法,但是一般的接口里面的方法都不是静态的,那么mybatis的对象在哪里?是如何产生的,本篇博客我们就通过源码来解释一下这个问题。
如果不懂代理模式的同学,首先请看一下我的另一篇blog:https://www.cnblogs.com/wyq178/p/6938482.html
本篇博客目录
一: Mapper的使用模式
二: MapperProxy代理原理
三:总结
一:Mapper模式
1.1:常见开发模式
  在平时的开发中,我们一般都会遵从以下的开发模式:mapper接口+xml的方式,我举个例子,常见会这样:

public interface SeckillDao { /** * 减库存 * @param seckillId * @param killTime * @return 如果影响行数> 1,表示更新库存的记录行数 */ int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime); /** * 根据id查询秒杀的商品信息 * @param seckillId * @return */ Seckill queryById(long seckillId); /** * 根据偏移量查询秒杀商品列表 * @param offset * @param limit * @return */ List< Seckill> queryAll(@Param("offset") int offset,@Param("limit") int limit); }

可以看到其中的方法都是非静态的,这是一个接口类,而我们还会有一个文件叫SecKillDao.xml,这个文件的主要作用是用来映射这个接口,这其中有两个注意点就是
1:xml中的nameSpace必须是接口的全类路径名
2: 每个标签的id必须和接口中的id保持一致
3:两个文件的名字必须保持一致
我们来看一下具体的SecKillmapper.xml文件的配置:
< mapper namespace="cn.codingxiaxw.dao.SeckillDao"> < update id="reduceNumber"> UPDATE seckill SET number = number-1 WHERE seckill_id=#{seckillId} AND start_time < ![CDATA[ < = ]]> #{killTime} AND end_time > = #{killTime} AND number > 0; < /update> < select id="queryById" resultType="Seckill" parameterType="long"> SELECTFROM seckill WHERE seckill_id=#{seckillId} < /select> < select id="queryAll" resultType="Seckill"> SELECTFROM seckillORDER BY create_time DESC limit #{offset},#{limit} < /select> < /mapper>

二:MapperProxy代理原理
2.1:mapperProxyFactory类
这个类的主要作用就是生成具体的代理对象,想一下mapper有很多,如果每个都new的话,势必会产生性能上的损耗,而MapperProxyFactory就是通过一个代理工厂(使用了工厂模式)来生产代理Mapper,其中最重要的就是newInstance方法了,他通过Proxy,jdk提供的一个方法来生产一个代理对象,并且是泛型的,这就大大增加了伸缩性。其中的InvocationHandler来自于MapperProxy,接下来将会着重讲这个类:
public class MapperProxyFactory< T> { //mapper代理工厂private final Class< T> mapperInterface; //接口对象 private final Map< Method, MapperMethod> methodCache = new ConcurrentHashMap< Method, MapperMethod> (); //用map缓存method对象和mapperMethodpublic MapperProxyFactory(Class< T> mapperInterface) { this.mapperInterface = mapperInterface; }public Class< T> getMapperInterface() {//获取mapper接口类对象 return mapperInterface; }public Map< Method, MapperMethod> getMethodCache() {//获取方法缓存 return methodCache; }@SuppressWarnings("unchecked") protected T newInstance(MapperProxy< T> mapperProxy) { //生成代理对象 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }public T newInstance(SqlSession sqlSession) { //创建具体的代理对象 final MapperProxy< T> mapperProxy = new MapperProxy< T> (sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }}

 
2.1:MapperProxy
这个类的主要作用就是通过代理对象来执行mapper.xml中的sql方法,将其编译为statment,主要是交给MapperMethod去处理,该类实现了InvocationHandler接口,采用动态代理模式,在invoke方法中进行调用,其中还加入了MapperMethod的缓存类,主要作用就是为了提升性能,缓存Mapper中的方法
public class MapperProxy< T> implements InvocationHandler, Serializable {//继承自InvocationHandler,采用jdk的代理模式 private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; //注入sqlSession的主要作用是执行一系列查询操作 private final Class< T> mapperInterface; //接口类对象,这里指的是SecKill.class private final Map< Method, MapperMethod> methodCache; //方法缓存对象,比如Mapper中的queryById()方法,为了提升性能的做法public MapperProxy(SqlSession sqlSession, Class< T> mapperInterface, Map< Method, MapperMethod> methodCache) { //构造方法 this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; }public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//通过代理对象执行 if(Object.class.equals(method.getDeclaringClass())) {//这里相当于执行mapper.java中的方法继承自Object方法,比如toString(),equals()方法 try { return method.invoke(this, args); //把参数传递给代理类来执行并返回结果 } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } } else { MapperMethod mapperMethod = this.cachedMapperMethod(method); //如果不是Object,表示此时的接口中写的方法,比如 Seckill queryById()方法,必须通过MapperMethod来调用 return mapperMethod.execute(this.sqlSession, args); //具体的调用 } }private MapperMethod cachedMapperMethod(Method method) {//缓存Mapper中的方法,比如上面开发中的queryById()方法 MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); //通过传入的方法获取Mapper中的方法 if(mapperMethod == null) {//如果缓存中没有 mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); //新构造MapperMethod this.methodCache.put(method, mapperMethod); //把当前方法作为键放入缓存中 }return mapperMethod; //返回MapperMethod方法中 } }

2.2:MapperMethod类
这个类的作用是为了分析XXmapper.xml的方法,通过SqlCommand判断其类型,然后交给sqlSession去执行,然后返回结果.在mapperProxy这个方法中会引用到它来返回结果
public class MapperMethod { private final MapperMethod.SqlCommand command; //Mapper.xml中的sql方法封装 private final MapperMethod.MethodSignature method; //方法签名public MapperMethod(Class< ?> mapperInterface, Method method, Configuration config) { this.command = new MapperMethod.SqlCommand(config, mapperInterface, method); this.method = new MapperMethod.MethodSignature(config, method); }public Object execute(SqlSession sqlSession, Object[] args) {//具体的sql执行方法,在MapperProxy的invoke方法中会调用这个方法 Object param; //方法参数 Object result; //结果 if(SqlCommandType.INSERT == this.command.getType()) {//如果是insert方法 param = this.method.convertArgsToSqlCommandParam(args); //转换sql参数 result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); //用sqlSession来执行,返回结果 } else if(SqlCommandType.UPDATE == this.command.getType()) {//如果是update方法 param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); } else if(SqlCommandType.DELETE == this.command.getType()) {//如果是delete方法 param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); } else if(SqlCommandType.SELECT == this.command.getType()) {//如果是select方法 if(this.method.returnsVoid() & & this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if(this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if(this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); } } else { if(SqlCommandType.FLUSH != this.command.getType()) { throw new BindingException("Unknown execution method for: " + this.command.getName()); } result = sqlSession.flushStatements(); }
}

2.3:SqlCommand类:
这个类的主要作用就是通过读取配置的标签,然后从配置里取出放入的sql,然后返回到mappedStatement类中
public static class SqlCommand { //sql命令private final String name; //名字 private final SqlCommandType type; //sql命令类型 这里指的是insert\\delete\\update 标签public SqlCommand(Configuration configuration, Class< ?> mapperInterface, Method method) {//配置,mapper的借口对象,方法 final String methodName = method.getName(); //获取方法名 final Class< ?> declaringClass = method.getDeclaringClass(); //获取所有的类对象 MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } }public String getName() { return name; }public SqlCommandType getType() { return type; }private MappedStatement resolveMappedStatement(Class< ?> mapperInterface, String methodName, //解析mapper的语法 Class< ?> declaringClass, Configuration configuration) { String statementId = mapperInterface.getName() + "." + methodName; //获取接口名称+方法名 比如userMapper.getuser if (configuration.hasStatement(statementId)) {//判断配置里是否有这个 return configuration.getMappedStatement(statementId); //返回mapper的sql语法 比如 select * from user; } else if (mapperInterface.equals(declaringClass)) { return null; } for (Class< ?> superInterface : mapperInterface.getInterfaces()) { if (declaringClass.isAssignableFrom(superInterface)) { MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration); if (ms != null) { return ms; } } } return null; } }

其实从2.2和2.3以后就讲的有限跑偏了,主要是mybatis如何解析mapper.xml中的sql了,其实在代理原理中,最重要的就是MapperProxy,主要实现了用代理对象去处理sql,而mapperProxyFactory这个类的主要作用就是生产代理对象,而MapperProxy这个类的作用就是使用代理对象去执行具体的接口中的方法。
三:总结
【mybatis的Mapper代理原理】  本篇博客主要分析了mapper的代理模式,通过mapperProxyFactory产生具体的代理对象,然后MapperProxy使用该代理对象去对mapper中的方法执行,最终返回结果。站在我们日常编程的角度来看,这个模式无疑增加了我们开发的便捷度,减少了对象的显示声明。这都是mybatis带给我们的好处,高度      封装的便捷度,高效的sql执行效率,等等都是我们青睐它的理由。

    推荐阅读