本文主要围绕三个问题展开?

  • 1.spirng是怎么和mybatis关联起来的?
  • 2.xml和mapper是如何解析的
  • 3.mapper中的方法是怎么和xml中的方法关联起来的?

Spirng是怎么和mybatis关联起来的

在基本的 MyBatis 中,session 工厂可以使用 SqlSessionFactoryBuilder 来创建。而在MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来替代。

spring-mybatis包中有一个类SqlSessionFactoryBean

SqlSessionFactoryBean 实现了 Spring 的 FactoryBean 接口这就说明了由 Spring 最终创建的 bean 不是 SqlSessionFactoryBean 本身,。而是工厂类的 getObject()返回的方法的结果。

  @Overridepublic SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {// sqlSessionFactory默认为空,直接走afterPropertiesSet()方法// 实际不是这样的,由于SqlSessionFactoryBean实现了InitializingBean,则再该bean生成之后,// 会直接调用afterPropertiesSet()方法,来创建sqlSessionFactory,故sqlSessionFactory应该// 是已经被创建好的afterPropertiesSet();}return this.sqlSessionFactory;}

如果sqlSessionFactory为空的话就调用afterPropertiesSet();方法,该方法是重写InitializingBean的方法,在bean初始化的时候就会执行。

  @Overridepublic void afterPropertiesSet() throws Exception {notNull(dataSource, "Property 'dataSource' is required");notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),"Property 'configuration' and 'configLocation' can not specified with together");this.sqlSessionFactory = buildSqlSessionFactory();}

做了一些环境前置的判断。然后执行buildSqlSessionFactory();构建sqlSessionFactory

核心内容是对于 mapperLocations 的解析,如下代码

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {final Configuration targetConfiguration;XMLConfigBuilder xmlConfigBuilder = null;//....省略代码/*** mapperLocations里面就是xml文件* 把xml文件进行解析(重要)*/if (this.mapperLocations != null) {if (this.mapperLocations.length == 0) {LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");} else {for (Resource mapperLocation : this.mapperLocations) {if (mapperLocation == null) {continue;}try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());//解析mapper文件xmlMapperBuilder.parse();} catch (Exception e) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);} finally {ErrorContext.instance().reset();}LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");}}} else {LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");}return this.sqlSessionFactoryBuilder.build(targetConfiguration);}

这里主要就是构建一个Configuration对象,在最后利用这个对象去构建一个sqlSessionFactory。
this.sqlSessionFactoryBuilder.build(targetConfiguration)

SqlSessionFactoryBuilder支持不同的方式去创建SqlSessionFactory。(SqlSessionFactoryBuilder是mybatis源码里面的类了)

  • 如通过配置文件,如:mybatis-config.xml,以前ssm那一套
  • 通过IO流
  • 通过Configuration参数
  public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

xml和mapper是如何解析的

进入xmlMapperBuilder.parse();

public void parse() {// 1.如果 resource 没被加载过才进行加载if (!configuration.isResourceLoaded(resource)) {// 1.1 解析 mapper 文件configurationElement(parser.evalNode("/mapper"));// 1.2 将 resource 添加到已加载列表configuration.addLoadedResource(resource);// 1.3 绑定 namespace 的 mapperbindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();
}

configurationElement

//context就是一个mapper.xml的内容
private void configurationElement(XNode context) {try {// 1.获取namespace属性String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {throw new BuilderException("Mapper's namespace cannot be empty");}// 2.设置currentNamespace属性builderAssistant.setCurrentNamespace(namespace);// 3.解析parameterMap、resultMap、sql等节点cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));// 4.解析增删改查节点,封装成 StatementbuildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}
}
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}// 解析增删改查节点,封装成StatementbuildStatementFromContext(list, null);
}

这里的每个Xnode就是对应一个方法

解析节点构建成一个MappedStatement对象,最后 放到Configuration中的Map<String, MappedStatement> mappedStatements变量中

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {// 1.构建XMLStatementBuilderfinal XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {// 2.解析节点statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}
}

一次put会放入两个值,key不同但是value是一样的。MappedStatement就是一个sql方法。所以通过这两种key都可以找到对应的sql。

bindMapperForNamespace

  • 通过之前解析xml得到的namespace获取到对应的Class
  • 放到Map<Class<?>, MapperProxyFactory<?>> knownMappers变量中
private void bindMapperForNamespace() {// 找出当前的命名空间String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {// 1.找到对应的mapper类boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {}if (boundType != null && !configuration.hasMapper(boundType)) {// 2.boundType不为空,并且configuration还没有添加boundType,// 则将namespace添加到已加载列表,将boundType添加到knownMappers缓存configuration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}
}public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);
}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缓存中去,value是一个MapperProxyFactory的代理knownMappers.put(type, new MapperProxyFactory<>(type));MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);//解析mapper类parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}
}

解析mapper类

public void parse() {// Class对象的唯一标识,如:// 类似"interface com.xxx.mapper.xxxMapper"String resource = type.toString();// 如果当前Class对象已经解析过,则不在解析if (!configuration.isResourceLoaded(resource)) {// 加载并解析指定的xml配置文件,Class所在的包对应文件路径,Class类名对应文件名称,如:// com.xxx.mapper.xxxMapper类对应的配置文件为com/xxx/mapper/xxxMapper.xmlloadXmlResource();// 把Class对应的标识添加到已加载的资源列表中configuration.addLoadedResource(resource);// 设置当前namespace为接口Class的全限定名assistant.setCurrentNamespace(type.getName());// 解析@CacheNamespace注解对应mapper.xml配置文件中的<cache>元素parseCache();// 解析缓存引用,会覆盖之前解析的缓存对象parseCacheRef();// 获取mapper接口所有方法,解析方法上的注解Method[] methods = type.getMethods();// 遍历所有获取到的方法for (Method method : methods) {try {// 主要是针对注解的 例如:方法上的@Select// 解析一个方法生成对应的MapperedStatement对象// 并添加到配置对象中parseStatement(method);} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}// 解析挂起的方法parsePendingMethods();}

mapper中的方法是怎么和xml中的方法关联起来的

在上篇文章说到用@MapperScan扫描到的bean,BeanDefinition中的beanClass会被替换成MapperFactoryBean.class

我们来看一看MapperFactoryBean的类图

  • SqlSessionDaoSupport:提供SqlSessionTemplate模版,这个变量会在初始化mapper bean的时候属性填充的时候设置进去
  • DaoSupport:实现了InitializingBean,具体方法有子类实现,做一些检查,初始化工作

MapperFactoryBean也是一个FactoryBean,也就是说spirng真正返回的是getObject()中的bean。

  @Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}

使用mapper来执行方法的流程

首先就要先获取到mapper,这个mapper是一个被代理的对象

SqlSessionTemplate

  @Overridepublic <T> T getMapper(Class<T> type) {return getConfiguration().getMapper(type, this);}

Configuration中的getMapper方法。mapper是哪里来的呢?

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}

mapper是从knownMappers变量中获取,也就是上面在bindMapperForNamespace方法中就放进入了

  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);}}

最后调用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<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}

MapperProxy实现了InvocationHandler,是基于jdk动态代理的。里面的invoke方法。最后执行的是MapperMethod中的execute方法。

// MapperProxy.invoke()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Object的方法执行if (Object.class.equals(method.getDeclaringClass())) {try {return method.invoke(this, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}// 获取MapperMethodfinal MapperMethod mapperMethod = cachedMapperMethod(method);// 真正的处理在这里return mapperMethod.execute(sqlSession, args);
}private MapperMethod cachedMapperMethod(Method method) {MapperMethod mapperMethod = methodCache.get(method);if (mapperMethod == null) {mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());methodCache.put(method, mapperMethod);}return mapperMethod;
}

MapperMethod.execute()方法执行

// MapperMethod.execute()
public Object execute(SqlSession sqlSession, Object[] args) {Object result;// 具体的增删改查操作,都有具体的执行,if (SqlCommandType.INSERT == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));} else if (SqlCommandType.UPDATE == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));} else if (SqlCommandType.DELETE == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));// 在这里我们主要看本例中的查询操作} else if (SqlCommandType.SELECT == command.getType()) {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 {// 本例就返回一个结果值,看这里// 封装参数值,最后还是交给SqlSession来处理Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}} else if (SqlCommandType.FLUSH == command.getType()) {result = sqlSession.flushStatements();} else {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;
}

比如说这样一个方法。通过断点看看

    @Autowiredprivate DemoMapper demoMapper;@Testpublic void test() {Demo demo1 = demoMapper.selectByPrimaryKey(1L);System.out.println(demo1.toString());}

我们可以很清楚的看到demoMapper就是一个被代理的类。

经过SqlSessionTemplate-selectOne->DefaultSqlSession-selectOne最后到selectList中

就是从Map<String, MappedStatement> mappedStatements这个变量中去获取的,而这个变量中的值在解析xml文件的时候已经放进去了。

我们都知道在mapper里面写的方法名称,在xml里面的id要一致才可以。Configuration中的mappedStatements是一个map。把xml里面的方法解析后放到这里面。然后这样就把mapper和xml对应起来了。

总结

  • 通过xml的namespace和mapper接口的类路径一致绑定
  • 然后通过在mapper里面写的方法名称和xml中的id一致这样就把xml和mapper绑定起来了

spring-mybatis源码解析相关推荐

  1. mybatis源码解析(一)

    Mybatis 源码解析 (一) 一. ORM框架的作用 实际开发系统时,我们可通过JDBC完成多种数据库操作.这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下: (1)注册数据库驱 ...

  2. Mybatis源码解析《二》

    导语 在前一篇文章Mybatis源码解析<一>中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文 ...

  3. 对标阿里P8的MyBatis源码解析文档,面试/涨薪两不误,已献出膝盖

    移动互联网的特点是大数据.高并发,对服务器往往要求分布式.高性能.高灵活等,而传统模式的Java数据库编程框架已经不再适用了. 在这样的背景下,一个Java的持久框架MyBatis走入了我们的世界,它 ...

  4. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  5. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  6. spring boot 源码解析23-actuate使用及EndPoint解析

    前言 spring boot 中有个很诱人的组件–actuator,可以对spring boot应用做监控,只需在pom文件中加入如下配置即可: <dependency><group ...

  7. Spring AOP源码解析-拦截器链的执行过程

    一.简介 在前面的两篇文章中,分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在得到了 bean 的代理对象,且通知也以合适的方式插在了目标方 ...

  8. mybatis源码解析一 xml解析(解析器)

    最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客.网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象 ...

  9. Mybatis源码解析(一):环境搭建

    Mybatis源码系列文章 手写源码(了解源码整体流程及重要组件) Mybatis源码解析(一):环境搭建 Mybatis源码解析(二):全局配置文件的解析 Mybatis源码解析(三):映射配置文件 ...

  10. mybatis源码解析1_sessionFactory

    注 一下内容都是根据尚硅谷2018年mybatis源码解析做的课程笔记,视频链接: https://www.bilibili.com/video/BV1mW411M737?p=74&share ...

最新文章

  1. 软件测试员,你该如何快速提高自己的测试技术?
  2. 补充一种简单的存储过程分页
  3. 学习PWM的一些总结
  4. MYSQL数据库应用优化
  5. 关于人生倒计时的一个小玩意,纯属业余
  6. 解决设备行业尾款回收问题-深思精锐5时钟锁
  7. 主流影视网站8合一H5视频源码自动更新数据
  8. 《21天学通C语言(第7版)》一6.6 课后研习
  9. 开发IE插件Toolbar
  10. android 雷达扫描动画,Android编程简单实现雷达扫描效果
  11. 推荐几个学习编程的网站
  12. 华为招收mba硕士么_为什么我放弃MBA以获得计算机科学硕士学位
  13. BugKu CTF(杂项篇MISC)—放松一下吧
  14. 电脑首次安装vue2过程/步骤(vue-cli)
  15. 微课程学习平台(微课平台)-特色功能(移动学习解决方案)
  16. 穷举算法——奶牛碑文(cow)
  17. Linux虚拟网络基础——veth pair
  18. Javase;jdk的安装调试;基础语法和变量以及基础数据类型
  19. 高手最爱的5大沟通技巧,管下属、谈客户都能用得上
  20. 数字化推动后市场产业变革,开启汽车后市场新篇章

热门文章

  1. xbox sdk_因此,您只是获得了Xbox Xbox。 怎么办?
  2. QIIME2进阶二_元数据及数据导入QIIME2
  3. Python编程(第4版)pdf
  4. git 的批量克隆 批量切换分支 批量pull
  5. 关于Protel 2004 绘制电路原理图——元件库的建立
  6. 简单工厂/工厂方法/抽象工厂
  7. 手把手的 Numpy 教程
  8. 论文答辩问题准备(学生水平,自己准备用的,不喜勿喷)
  9. liunx邮箱服务搭建 (简洁版)
  10. 自动控制理论的发展历程