在工作中,使用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接口的代理对象。

请看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. 流程图

mybatis源码分析之04Mapper接口的动态代理的更多相关文章

  1. MyBatis源码分析(七):动态代理(Mybatis核心机制)

    一.动态代理 动态代理是一种比较高级的代理模式,它的典型应用就是Spring AOP. 在传统的动态代理模式中,客户端通过ProxySubject调用RealSubject类的request( )方法 ...

  2. MyBatis 源码分析——生成Statement接口实例

    JDBC的知识对于JAVA开发人员来讲在简单不过的知识了.PreparedStatement的作用更是胸有成竹.我们最常见用到有俩个方法:executeQuery方法和executeUpdate方法. ...

  3. MyBatis源码分析(3)—— Cache接口以及实现

    @(MyBatis)[Cache] MyBatis源码分析--Cache接口以及实现 Cache接口 MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口. ...

  4. 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  5. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  6. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  7. MyBatis源码分析(5)——内置DataSource实现

    @(MyBatis)[DataSource] MyBatis源码分析(5)--内置DataSource实现 MyBatis内置了两个DataSource的实现:UnpooledDataSource,该 ...

  8. MyBatis源码分析(2)—— Plugin原理

    @(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...

  9. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

随机推荐

  1. Vim 8.0 版本安装方法及添加Python支持

    利用Git安装 最简单也是最有效的方法 1. 获取Vim仓库: git clone https://github.com/vim/vim.git 2. 升级到最新的版本: cd vim git pul ...

  2. Spring cloud gateway自定义filter以及负载均衡

    自定义全局filter package com.example.demo; import java.nio.charset.StandardCharsets; import org.apache.co ...

  3. Kubernetes tutorial - K8S 官方入门教程

    tutorials 教程 kubectl 的命令手册 1 Creating a Cluster 1.1 Using Minikube to Create a Cluster Kubernetes Cl ...

  4. PHP 数组函数-数组排序

    php数组排序函数sort ( &$arr [,fruits] ) 对数组进行从低到高排序 ,并赋予新的键名 返回boolrsort ( &$arr [,fruits] ) 对数组进行 ...

  5. 2019Hdu多校第三场:1007 Find the answer(multiset 解法)

    原题链接: Find the answer c++中,multiset是库中一个非常有用的类型,它可以看成一个序列,插入一个数,删除一个数都能够在O(logn)的时间内完成,而且他能时刻保证序列中的数 ...

  6. hdu6575Budget

    Problem Description Avin’s company has many ongoing projects with different budgets. His company rec ...

  7. 京东商品评论的分类预测与LSA、LDA建模

    (一)数据准备 1.爬取京东自营店kindle阅读器的评价数据,对数据进行预处理,使用机器学习算法对评价文本进行舆情分析,预测某用户对本商品的评价是好评还是差评.通过数据分析与模型分析,推测出不同型号 ...

  8. 动态规划-递推-HDU2048

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=2048 全错=全不匹配 设当前全错的个数是dp[n] 那么前(n-1)个全错的话,第n个数就可以从前(n- ...

  9. 获取jQuery DataTables 的checked选中行

    $(function () { var  tabel = $('#userlist').DataTable({        destroy: true, //Cannot reinitialise ...

  10. mybatis插件机制及分页插件原理

    MyBatis 插件原理与自定义插件: MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要注意的是,如果没有完全理解MyBatis 的运行原理和插件的工作方式 ...