学向勤中得,萤窗万卷书。这篇文章主要讲述mybatis源码分析之04Mapper接口的动态代理相关的知识,希望能为你提供帮助。
在工作中,使用mybatis操作数据库,只需要提供一个接口类,定义一些方法,然后调用接口里面的方法就可以CRUD,感觉是牛了一逼!
该篇就是记录一下,mybatis是如何完成这波骚操作的,即分析我们测试代码的第4行。
FemaleMapper femaleMapper = sqlSession.getMapper(FemaleMapper.class);
由上篇可知,sqlSession的真实类型是DefaultSqlSession. 所以,我们直接是看DefaultSqlSession#getMapper(Class< T> type)方法,当然,断点也是少不了的!
public < T> T getMapper(Class< T> type) { return configuration.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) { final MapperProxyFactory< T> mapperProxyFactory = (MapperProxyFactory< T> ) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
看到上面这段代码 ,肯定有反应了。 在上上篇中,我们说到解析xml配置时,会将Mapper接口缓存到MapperRegistry#knownMappers集合中,key是Mapper接口全路径,
Value是该Mapper接口的一个代理工厂类MapperProxyFactory, 源代码就是MapperRegistry#addMapper(Class< T> type)方法,代码如下:
public < T> void addMapper(Class< T> type) {try { knownMappers.put(type, new MapperProxyFactory< > (type)); } finally { } }
回归正题,此时getMapper()就是根据Mapper接口类型,去knownMappers集合中拿到其对应的代理工厂类。然后通过这个代理工厂类去创建Mapper接口的代理对象。
【mybatis源码分析之04Mapper接口的动态代理】请看MapperProxyFactory#newInstance(SqlSession sqlSession)方法
public T newInstance(SqlSession sqlSession) { final MapperProxy< T> mapperProxy = new MapperProxy< > (sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
又new了个MapperProxy, 看框架就是麻烦 ,封装了一层又一层, 但是还得看, 因为MapperProxy才是真正干事的!
然后就是下面这个方法,创建代理类
protected T newInstance(MapperProxy< T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
debug 看一下,感觉不用debug完全活不下去了。。。
文章图片
看到没有, 返回的是一个MapperProxy@1546
文章图片
好,测试代码 FemaleMapper femaleMapper = sqlSession.getMapper(FemaleMapper.class) 这一句执行完毕,返回了一个MapperProxy代码对象,即然是代理,那肯定是
要去看看它的invoke()方法了, 这才是重头戏。
而当程序执行 Female female = femaleMapper.getFemaleById(1) 这行代理时,就会调用MapperProxy#invoke()方法。
MapperProxy#invoke()方法,源码如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (method.isDefault()) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
文章图片
所以,应该去看看cachedMapperMethod(method)在干啥?
private MapperMethod cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }
创建 MapperMethod,并缓存起来,所以说,mybatis也没那么傻,并不是每次都去构造MapperMethod实例 ,这个实例干嘛呢? 接着看!
public MapperMethod(Class< ?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); }
这里就不具体说了,代码也很简单。
< 1> SqlCommand两个属性,一个name,一个type, debug一下,啥都明白了
文章图片
< 2> MethodSignature 方法签名,就是对方法的返回值判断,还有比如@Param注解等处理。。。
文章图片
MapperProxy#invoke()方法的最后就是调用MapperMethod#execute(sqlSession,args)方法,该方法最终就是调用Executor类中方法操作数据库。
< 1> MapperMethod#execute()
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); if (method.returnsOptional() & & (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } 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; }
该方法就是根据sql的类型,执行具体的逻辑 ,咱们的测试代码是SELECT,所以会走到这儿
文章图片
文章图片
selectOne底层还是调用的selectList,只是取的get(0) , 还有这个异常,工作中也是很常见的呀,原来是这儿抛出来的!
接着看selectList()方法
@Override public < E> List< E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }
RowBounds, mybatis提供的分页功能,不过这里使用的是DEFAULT,即是偏移量是0,limit 是 Integer.MAX_VALUE, 没啥用,如果我们想使用RowBounds分页,传一个自定义的RowBounds对象即可!
一路往前奔。。。 终于来到这儿。。。
@Override public < E> List< E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database.Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
前文说过,跟数据库sql相关的东西都封装在MappedStatement 对象中,最终操作数据库的都是Executor实例 ,这儿可以得到证明!
到这儿就差不多了,剩下的就是jdbc操作数据库那一套了。
总结:
1. mybatis创建了一个MapperProxy的代理,用于操作Mapper接口的方法,从而做到CRUD, 在此过程中需要用到的一些实例,前期都已经准备好了!
2. 流程图
文章图片
推荐阅读
- RK3399 Android 7.0的开机横屏设置
- Re-DD-androideasy
- spring的核心容器ApplicationContext
- android常用adb指令集
- 阿里云物联网平台: Android 连接阿里云物联网平台
- spring中的ApplicationListener监听器
- CSAPP第二章show_bytes函数的探究
- Spring----事件(Application Event)
- mybatis配置约束config,mapper