Mybatis源码解析-MapperRegistry注册mapper接口
前话
根据前文的分析我们可以得知Spring在使用MapperScannerConfigurer扫描DAO接口类集合时,会将相应的DAO接口封装成类型为org.mybatis.spring.mapper.MapperFactoryBean对象,并将相应的mapperInterface(dao接口)加入至mybatis框架中的org.apache.ibatis.session.Configuration对象里
configuration.addMapper(this.mapperInterface);
笔者深究下这个方法的调用,跟踪下去发现其实其调用的是org.apache.ibatis.binding.MapperRegistry#addMapper()方法。本文则通过这个方法进行展开讲解
MapperRegistry#addMapper()
直接查看相应的源码
public <T> void addMapper(Class<T> type) {
// 类必须为接口类
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 放入knownMappers中,并以MapperProxyFactory来进行包装
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
由上述的代码可知,所加入的mapperInterface正如其英文描述一样,必须是一个接口类。而且mybatis还将此接口类包装成org.apache.ibatis.binding.MapperProxyFactory对象,很有代理的味道~~~
MapperProxyFactory
上文讲了如何存放,那么获取的代码呢??如下所示
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从map中获取相应的代理类
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);
}
}
我们直接去翻看下MapperProxyFactory#newInstance()方法
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);
}
粗粗一看,发现是最终是通过JDK动态代理来代理相应的mapper接口。而对应的代理处理则为java.lang.reflect.InvocationHandler接口的实现类org.apache.ibatis.binding.MapperProxy。接下来笔者针对这个类进行详细的分析
MapperProxy
首先看下构造函数
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
// sql会话
this.sqlSession = sqlSession;
// mapper接口
this.mapperInterface = mapperInterface;
// 对应接口方法的缓存
this.methodCache = methodCache;
}
我们直接看下代理的实现方法invoke()
@Override
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 (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 关注此处即可
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
看来真正处理的是org.apache.ibatis.binding.MapperMethod类,我们继续分析
MapperMethod
也观察下构造函数
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// SqlCommand表示该sql的类型,一般为select|update|insert|delete|flush等类型
this.command = new SqlCommand(config, mapperInterface, method);
// method适配器,一般解析mapper接口对应method的参数集合以及回参等
this.method = new MethodSignature(config, mapperInterface, method);
}
继而简单的看下其execute()方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据command的类型进行CRUD操作
switch (command.getType()) {
case INSERT: {
// 解析入参集合,@Param注解。详见ParamNameResolver#names注解
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来进行真正的sql执行。我们可以简单看下sqlsession接口的方法
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
nsert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
提供数据库操作的CRUD方法~~很齐全
小结
作下简单的小结
1.mybatis对mapper接口的对应方法采取了JDK动态代理的方式
2.SERVICE或者CONTROLLER层调用mapper接口的时候,便会通过mapperRegistry去获取对应的mapperMethod来进行相应的SQL语句操作
Mybatis源码解析-MapperRegistry注册mapper接口的更多相关文章
- mybatis源码解析8---执行mapper接口方法到执行mapper.xml的sql的过程
上一篇文章分析到mapper.xml中的sql标签对应的MappedStatement是如何初始化的,而之前也分析了Mapper接口是如何被加载的,那么问题来了,这两个是分别加载的到Configura ...
- Mybatis源码解析-MapperRegistry代理注册mapper接口
知识储备 SqlsessionFactory-mybatis持久层操作数据的前提,具体的解析是通过SqlSessionFactoryBean生成的,可见>>>Spring mybat ...
- MyBatis源码解析【7】接口式编程
前言 这个分类比较连续,如果这里看不懂,或者第一次看,请回顾之前的博客 http://www.cnblogs.com/linkstar/category/1027239.html 修改例子 在我们实际 ...
- Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...
- Mybatis源码解析(三) —— Mapper代理类的生成
Mybatis源码解析(三) -- Mapper代理类的生成 在本系列第一篇文章已经讲述过在Mybatis-Spring项目中,是通过 MapperFactoryBean 的 getObject( ...
- mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)
目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...
- mybatis源码-解析配置文件(四)之配置文件Mapper解析
在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ...
- Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取
在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao ...
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
随机推荐
- 测序分析软件-phred的安装
1.进入phred官网,给作者写信,获得所需的软件,大约需要两三天的时间即可收到回信. 2.根据作者的指示下载,解压相应软件. 3.以笔者本人的安装为例unbuntu系统(phred自带的instal ...
- MVC架构简介及其测试策略
最近在WEB端测试工作中陷入了瓶颈,单纯的手动功能测试在没有成熟的代码规范之前还是很容易坑的,WEB自动化测试一时半会还没有什么进展,所以决定先学习一下网站用的MVC架构,跟着教程写了一个小网站,大概 ...
- CSS3的使用方法解析
自己过去有段时间使用CSS3开发过一些小的部件和效果,但是由于太久没有再次去使用,导致当自己再次去使用的时候我就需要去翻手册重新找一次然后按着方法使用才可以. 现在我就把这份CSS3的使用技巧展示给各 ...
- ionic2 跳转子页面隐藏底部导航栏
第一种方法: 在tab里面添加一个属性[tabsHideOnSubPages]='true' <ion-tab [root]="tab1Root" [tabsHideOnSu ...
- SQLServer数据库操作
--创建数据库create database 在线考试系统on(name=在线考试系统_DATA,filename='E:\DB\在线考试系统_DATA.mdf',size=5mb,maxsize=2 ...
- [0] AssemblyInfo.cs文件介绍
AssemblyInfo.cs文件:包含程序版本.信息.版权的属性文件 先介绍AssemblyInfo.cs文件中的程序集属性 内容: using System.Reflection;using Sy ...
- post和get方式区别
Ajax中我们经常用到get和post请求.那么什么时候用get请求,什么时候用post方式请求呢? 在做回答前我们首先要了解get和post的区别.1. get是把参数数据队列加到提交表单的ACTI ...
- “前”方有坑,绕道而行(一)-- H5+CSS
1. 关于 数字.英文 不换行问题: 情景:昨天测试 小程序,输入英文,没有换行,且 下方有横向滚动条: 解决:word-break: word-break:break-all; /*只对英文起作用 ...
- idea live template高级知识, 进阶(给方法,类,js方法添加注释)(二)
上一篇文章(http://www.cnblogs.com/xzjxylophone/p/6994488.html) 是在 groovyScript中直接添加的代码,这个看起来是简单,粗暴,麻烦和不美观 ...
- octomap中3d-rrt路径规划
路径规划 碰撞冲突检测 在octomap中制定起止点,目标点,使用rrt规划一条路径出来,没有运动学,动力学的限制,只要能避开障碍物. 效果如下: #include "ros/ros.h&q ...