前言

这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到了2次,2次都是非常重要的面试环节,因此自己印象很深刻。这个题目我很早就深入学习了,但是一直没有整理出来,刚好最近一段时间由于工作太忙,大概有半年没有技术文章产出,因此趁着五一有点时间,整理了下分享给大家。另外,估计不少同学应该也注意到了,DAO 接口的全路径名和 XML 文件中的 SQL 的 namespace + id 是一样的。其实,这也是建立关联的根本原因。本文中的源码使用当前最新的版本,即:mybatis-spring 为 2.0.4,mybatis 为 3.5.4,引入这2个 jar 包即可查看到本文的所有代码。

正文

当一个项目中使用了 Spring 和 Mybatis 时,通常会有以下配置。当然现在很多项目应该都是 SpringBoot 了,可能没有以下配置,但是究其底层原理都是类似的,无非是将扫描 bean 等一些工作通过注解来实现。

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">        <property name="basePackage" value="com.joonwhee.open.mapper"/>    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">    <property name="dataSource" ref="dataSource"/>        <property name="mapperLocations" value="classpath:config/mapper/*.xml"/>    <property name="configLocation" value="classpath:config/mybatis/mybatis-config.xml"/>        <property name="typeAliasesPackage" value="com.joonwhee.open.po"/>bean> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">    <property name="driverClassName" value="${driver}"/>    <property name="url" value="${url}"/>    <property name="username" value="${username}"/>    <property name="password" value="${password}"/>bean>

通常我们还会有 DAO 类和 对用的 mapper 文件,如下。

package com.joonwhee.open.mapper; import com.joonwhee.open.po.UserPO; public interface UserPOMapper {    UserPO queryByPrimaryKey(Integer id);}
<?xml version="1.0" encoding="UTF-8" ?><mapper namespace="com.joonwhee.open.mapper.UserPOMapper" >    <resultMap id="BaseResultMap" type="com.joonwhee.open.po.UserPO">        <result column="id" property="id" jdbcType="INTEGER" />        <result column="name" property="name" jdbcType="VARCHAR" />    resultMap>     <select id="queryByPrimaryKey" resultMap="BaseResultMap"            parameterType="java.lang.Integer">        select id, name        from user        where id = #{id,jdbcType=INTEGER}    select>mapper>

1、解析 MapperScannerConfigurer

MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor,会在 Spring 构建 IoC容器的早期被调用重写的 postProcessBeanDefinitionRegistry 方法,参考:Spring IoC:invokeBeanFactoryPostProcessors 详解

@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {  if (this.processPropertyPlaceHolders) {    processPropertyPlaceHolders();  }   // 1.新建一个ClassPathMapperScanner,并填充相应属性  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);  scanner.setAddToConfig(this.addToConfig);  scanner.setAnnotationClass(this.annotationClass);  scanner.setMarkerInterface(this.markerInterface);  scanner.setSqlSessionFactory(this.sqlSessionFactory);  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);  scanner.setResourceLoader(this.applicationContext);  scanner.setBeanNameGenerator(this.nameGenerator);  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);  if (StringUtils.hasText(lazyInitialization)) {    // 2.设置mapper bean是否需要懒加载    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));  }  // 3.注册Filter,因为上面构造函数我们没有使用默认的Filter,  // 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的  scanner.registerFilters();  // 4.扫描basePackage,basePackage可通过",; \t\n"来填写多个,  // ClassPathMapperScanner重写了doScan方法  scanner.scan(      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}

3.注册 Filter,见代码块1。4.扫描 basePackage,这边会走到 ClassPathBeanDefinitionScanner(ClassPathMapperScanner 的父类),然后在执行 “doScan(basePackages)” 时回到 ClassPathMapperScanner 重写的方法,见代码块2。

代码块1:registerFilters

public void registerFilters() {  boolean acceptAllInterfaces = true;   // if specified, use the given annotation and / or marker interface  // 1.如果指定了注解,则将注解添加到includeFilters  if (this.annotationClass != null) {    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));    acceptAllInterfaces = false;  }   // override AssignableTypeFilter to ignore matches on the actual marker interface  // 2.如果指定了标记接口,则将标记接口添加到includeFilters,  // 但这边重写了matchClassName方法,并返回了false,  // 相当于忽略了标记接口上的匹配项,所以该参数目前相当于没有任何作用  if (this.markerInterface != null) {    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {      @Override      protected boolean matchClassName(String className) {        return false;      }    });    acceptAllInterfaces = false;  }   // 3.如果没有指定annotationClass和markerInterface,则  // 添加默认的includeFilters,直接返回true,接受所有类  if (acceptAllInterfaces) {    // default include filter that accepts all classes    addIncludeFilter((metadataReader, metadataReaderFactory) -> true);  }   // exclude package-info.java  // 4.添加默认的excludeFilters,排除以package-info结尾的类  addExcludeFilter((metadataReader, metadataReaderFactory) -> {    String className = metadataReader.getClassMetadata().getClassName();    return className.endsWith("package-info");  });}

通常我们都不会指定 annotationClass 和 markerInterface,也就是会添加默认的 Filter,相当于会接受除了 package-info 结尾的所有类。因此,basePackage 包下的类不需要使用 @Component 注解或 XML 中配置 bean 定义,也会被添加到 IoC 容器中。

代码块2:doScan

@Overridepublic Set doScan(String... basePackages) {  // 1.直接使用父类的方法扫描和注册bean定义,  // 之前在spring中已经介绍过:https://joonwhee.blog.csdn.net/article/details/87477952 代码块5  Set beanDefinitions = super.doScan(basePackages);   if (beanDefinitions.isEmpty()) {    LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)        + "' package. Please check your configuration.");  } else {    // 2.对扫描到的beanDefinitions进行处理,主要4件事:    // 1)将bean的真正接口类添加到通用构造函数参数中    // 2)将beanClass直接设置为MapperFactoryBean.class,    //  结合1,相当于要使用的构造函数是MapperFactoryBean(java.lang.Class)    // 3)添加sqlSessionFactory属性,sqlSessionFactoryBeanName和    //  sqlSessionFactory中,优先使用sqlSessionFactoryBeanName    // 4)添加sqlSessionTemplate属性,同样的,sqlSessionTemplateBeanName    //  优先于sqlSessionTemplate,    processBeanDefinitions(beanDefinitions);  }   return beanDefinitions;}

小结,解析 MapperScannerConfigurer 主要是做了几件事:1)新建扫描器 ClassPathMapperScanner;2)使用 ClassPathMapperScanner 扫描注册 basePackage 包下的所有 bean;3)将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean、为 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate属性。2、解析 SqlSessionFactoryBean对于 SqlSessionFactoryBean 来说,实现了2个接口,InitializingBean 和 FactoryBean,看过我之前 Spring 文章的同学应该对这2个接口不会陌生,简单来说:1)FactoryBean 可以自己定义创建实例对象的方法,只需要实现它的 getObject() 方法;InitializingBean 则是会在 bean 初始化阶段被调用。SqlSessionFactoryBean 重写这两个接口的部分方法代码如下,核心代码就一个方法—— “buildSqlSessionFactory()”。

@Overridepublic SqlSessionFactory getObject() throws Exception {  if (this.sqlSessionFactory == null) {    // 如果之前没有构建,则这边也会调用afterPropertiesSet进行构建操作    afterPropertiesSet();  }   return this.sqlSessionFactory;} @Overridepublic void afterPropertiesSet() throws Exception {  // 省略部分代码  // 构建sqlSessionFactory  this.sqlSessionFactory = buildSqlSessionFactory();}

buildSqlSessionFactory()主要做了几件事:1)对我们配置的参数进行相应解析;2)使用配置的参数构建一个 Configuration;3)使用 Configuration 新建一个 DefaultSqlSessionFactory。这边的核心内容是对于 mapperLocations 的解析,如下代码。

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {   // 省略部分代码   // 5.mapper处理(最重要)  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 {          // 5.1 新建XMLMapperBuilder          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());          // 5.2 解析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.");  }   // 6.使用targetConfiguration构建DefaultSqlSessionFactory  return this.sqlSessionFactoryBuilder.build(targetConfiguration);}

5.2 解析mapper文件,见代码块3。

代码块3: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的mapper    bindMapperForNamespace();  }   parsePendingResultMaps();  parsePendingCacheRefs();  parsePendingStatements();}

1.1 解析mapper文件,见代码4。1.3 绑定namespace的mapper,见代码块6。代码块4:configurationElement

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.解析增删改查节点,封装成Statement    buildStatementFromContext(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(Listlist) {  if (configuration.getDatabaseId() != null) {    buildStatementFromContext(list, configuration.getDatabaseId());  }  // 解析增删改查节点,封装成Statement  buildStatementFromContext(list, null);} private void buildStatementFromContext(Listlist, String requiredDatabaseId) {  for (XNode context : list) {    // 1.构建XMLStatementBuilder    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);    try {      // 2.解析节点      statementParser.parseStatementNode();    } catch (IncompleteElementException e) {      configuration.addIncompleteStatement(statementParser);    }  }}

这边会一直执行到 “statementParser.parseStatementNode();”,见代码块5。这边每个 XNode 都相当于如下的一个 SQL,下面封装的每个 MappedStatement 可以理解就是每个 SQL。

<select id="queryByPrimaryKey" resultMap="BaseResultMap"        parameterType="java.lang.Integer">    select id, name, password, age    from user    where id = #{id,jdbcType=INTEGER}select>

代码块5:parseStatementNode

public void parseStatementNode() {  // 省略所有的属性解析  // 将解析出来的所有参数添加到 mappedStatements 缓存  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,      resultSetTypeEnum, flushCache, useCache, resultOrdered,      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);} // MapperBuilderAssistant.javapublic MappedStatement addMappedStatement(    String id,    SqlSource sqlSource,    StatementType statementType,    SqlCommandType sqlCommandType,    Integer fetchSize,    Integer timeout,    String parameterMap,    Class> parameterType,    String resultMap,    Class> resultType,    ResultSetType resultSetType,    boolean flushCache,    boolean useCache,    boolean resultOrdered,    KeyGenerator keyGenerator,    String keyProperty,    String keyColumn,    String databaseId,    LanguageDriver lang,    String resultSets) {   if (unresolvedCacheRef) {    throw new IncompleteElementException("Cache-ref not yet resolved");  }   // 1.将id填充上namespace,例如:queryByPrimaryKey变成  // com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey  id = applyCurrentNamespace(id, false);  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;  // 2.使用参数构建MappedStatement.Builder  MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)      .resource(resource)      .fetchSize(fetchSize)      .timeout(timeout)      .statementType(statementType)      .keyGenerator(keyGenerator)      .keyProperty(keyProperty)      .keyColumn(keyColumn)      .databaseId(databaseId)      .lang(lang)      .resultOrdered(resultOrdered)      .resultSets(resultSets)      .resultMaps(getStatementResultMaps(resultMap, resultType, id))      .resultSetType(resultSetType)      .flushCacheRequired(valueOrDefault(flushCache, !isSelect))      .useCache(valueOrDefault(useCache, isSelect))      .cache(currentCache);   ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);  if (statementParameterMap != null) {    statementBuilder.parameterMap(statementParameterMap);  }  // 3.使用MappedStatement.Builder构建MappedStatement  MappedStatement statement = statementBuilder.build();  // 4.将MappedStatement 添加到缓存  configuration.addMappedStatement(statement);  return statement;}

该方法会将节点的属性解析后封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。代码块6:bindMapperForNamespace

private void bindMapperForNamespace() {  String namespace = builderAssistant.getCurrentNamespace();  if (namespace != null) {    Class> boundType = null;    try {      // 1.解析namespace对应的绑定类型      boundType = Resources.classForName(namespace);    } catch (ClassNotFoundException e) {      // ignore, bound type is not required    }    if (boundType != null && !configuration.hasMapper(boundType)) {      // Spring may not know the real resource name so we set a flag      // to prevent loading again this resource from the mapper interface      // look at MapperAnnotationBuilder#loadXmlResource      // 2.boundType不为空,并且configuration还没有添加boundType,      // 则将namespace添加到已加载列表,将boundType添加到knownMappers缓存      configuration.addLoadedResource("namespace:" + namespace);      configuration.addMapper(boundType);    }  }} public void addMapper(Classtype) {  mapperRegistry.addMapper(type);} public void addMapper(Classtype) {  if (type.isInterface()) {    if (hasMapper(type)) {      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");    }    boolean loadCompleted = false;    try {      // 将type和以该type为参数构建的MapperProxyFactory作为键值对,      // 放到knownMappers缓存中去      knownMappers.put(type, new MapperProxyFactory<>(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);      }    }  }}

主要是将刚刚解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。小结,解析 SqlSessionFactoryBean 主要做了几件事:1)解析处理所有属性参数构建 Configuration ,使用 Configuration 新建 DefaultSqlSessionFactory;2)解析 mapperLocations 属性的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。3)将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。3、解析 DAO 文件DAO 文件,也就是 basePackage 指定的包下的文件,也就是上文的 interface UserPOMapper 。上文 doScan 中说过,basePackage 包下所有 bean 定义的 beanClass 会被设置成 MapperFactoryBean.class,而 MapperFactoryBean 也是 FactoryBean,因此直接看 MapperFactoryBean 的 getObject 方法。

@Overridepublic T getObject() throws Exception {  // 1.从父类中拿到sqlSessionTemplate,这边的sqlSessionTemplate也是doScan中添加的属性  // 2.通过mapperInterface获取mapper  return getSqlSession().getMapper(this.mapperInterface);} // SqlSessionTemplate@Overridepublic T getMapper(Class type) {  return getConfiguration().getMapper(type, this);} // Configuration.javapublic T getMapper(Class type, SqlSession sqlSession) {  return mapperRegistry.getMapper(type, sqlSession);} // MapperRegistry.javapublic T getMapper(Class type, SqlSession sqlSession) {  // 1.从knownMappers缓存中获取  final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);  if (mapperProxyFactory == null) {    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");  }  try {    // 2.新建实例    return mapperProxyFactory.newInstance(sqlSession);  } catch (Exception e) {    throw new BindingException("Error getting mapper instance. Cause: " + e, e);  }} // MapperProxyFactory.javapublic T newInstance(SqlSession sqlSession) {  // 1.构造一个MapperProxy  final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);  // 2.使用MapperProxy来构建实例对象  return newInstance(mapperProxy);} protected T newInstance(MapperProxy mapperProxy) {  // 使用JDK动态代理来代理要创建的实例对象,InvocationHandler为mapperProxy,  // 因此当我们真正调用时,会走到mapperProxy的invoke方法  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

这边代码用到的 sqlSessionTemplate、mapperInterface 等都是之前添加的属性。小结,解析 DAO 文件 主要做了几件事:1)通过 mapperInterface 从 knownMappers 缓存中获取到 MapperProxyFactory 对象;2)通过 JDK 动态代理创建 MapperProxyFactory 实例对象,InvocationHandler 为 MapperProxy。4、DAO 接口被调用当 DAO 中的接口被调用时,会走到 MapperProxy 的 invoke 方法。

@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  try {    if (Object.class.equals(method.getDeclaringClass())) {      return method.invoke(this, args);    } else {      // 1.创建MapperMethodInvoker      // 2.将method -> MapperMethodInvoker放到methodCache缓存      // 3.调用MapperMethodInvoker的invoke方法      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);    }  } catch (Throwable t) {    throw ExceptionUtil.unwrapThrowable(t);  }} // MapperProxy.javaprivate MapperMethodInvoker cachedInvoker(Method method) throws Throwable {  try {    // 1.放到methodCache缓存,key为method,value为MapperMethodInvoker    return methodCache.computeIfAbsent(method, m -> {      if (m.isDefault()) {        // 2.方法为默认方法,Java8之后,接口允许有默认方法        try {          if (privateLookupInMethod == null) {            return new DefaultMethodInvoker(getMethodHandleJava8(method));          } else {            return new DefaultMethodInvoker(getMethodHandleJava9(method));          }        } catch (IllegalAccessException | InstantiationException | InvocationTargetException            | NoSuchMethodException e) {          throw new RuntimeException(e);        }      } else {        // 3.正常接口会走这边,使用mapperInterface、method、configuration        // 构建一个MapperMethod,封装成PlainMethodInvoker        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));      }    });  } catch (RuntimeException re) {    Throwable cause = re.getCause();    throw cause == null ? re : cause;  }}

3.调用 MapperMethodInvoker 的 invoke 方法,见代码块7。代码块7:invoke

@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {  return mapperMethod.execute(sqlSession, args);} // MapperMethod.javapublic Object execute(SqlSession sqlSession, Object[] args) {  Object result;  // 1.根据命令类型执行来进行相应操作  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;}

这边就比较简单,根据不同的操作类型执行相应的操作,最终将结果返回,见代码块8。

这边的 command 是上文 “new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())” 时创建的。

代码块8:增删改查

// 1.insert@Overridepublic int insert(String statement, Object parameter) {  return update(statement, parameter);} // 2.update@Overridepublic int update(String statement, Object parameter) {  try {    dirty = true;    // 从mappedStatements缓存拿到对应的MappedStatement对象,执行更新操作    MappedStatement ms = configuration.getMappedStatement(statement);    return executor.update(ms, wrapCollection(parameter));  } catch (Exception e) {    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);  } finally {    ErrorContext.instance().reset();  }} // 3.delete@Overridepublic int delete(String statement, Object parameter) {  return update(statement, parameter);} // 4.select,以executeForMany为例private Object executeForMany(SqlSession sqlSession, Object[] args) {  List result;  // 1.参数转换成sql命令参数  Object param = method.convertArgsToSqlCommandParam(args);  if (method.hasRowBounds()) {    RowBounds rowBounds = method.extractRowBounds(args);    result = sqlSession.selectList(command.getName(), param, rowBounds);  } else {    // 2.执行查询操作    result = sqlSession.selectList(command.getName(), param);  }  // 3.处理返回结果  // issue #510 Collections & arrays support  if (!method.getReturnType().isAssignableFrom(result.getClass())) {    if (method.getReturnType().isArray()) {      return convertToArray(result);    } else {      return convertToDeclaredCollection(sqlSession.getConfiguration(), result);    }  }  return result;} @Overridepublic  List selectList(String statement, Object parameter) {  return this.selectList(statement, parameter, RowBounds.DEFAULT);} @Overridepublic  List selectList(String statement, Object parameter, RowBounds rowBounds) {  try {    //从mappedStatements缓存中拿到对应的MappedStatement对象,执行查询操作    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();  }}

可以看出,最终都是从 mappedStatements 缓存中拿到对应的 MappedStatement 对象,执行相应的操作。

这边的增删改查不是直接调用 SqlSession 中的方法,而是调用 SqlSessionTemplate 中的方法,继而通过 sqlSessionProxy 来调用 SqlSession 中的方法。SqlSessionTemplate 中的方法主要是通过 sqlSessionProxy 做了一层动态代理,基本没差别。

总结

整个流程主要是以下几个核心步骤:

1)扫描注册 basePackage 包下的所有 bean,将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean、为 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate属性。

2)解析 mapperLocations 属性的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。并且将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。

3)创建 DAO 的 bean 时,通过 mapperInterface 从 knownMappers 缓存中获取到 MapperProxyFactory 对象,通过 JDK 动态代理创建 MapperProxyFactory 实例对象,InvocationHandler 为 MapperProxy。

4)DAO 中的接口被调用时,通过动态代理,调用 MapperProxy 的 invoke 方法,最终通过 mapperInterface 从 mappedStatements 缓存中拿到对应的 MappedStatement,执行相应的操作。

转发至朋友圈,是对我最大的支持。

朕已阅❤️

.sql文件_面试题:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立关系的?相关推荐

  1. if mybatis tk 多个_面试题:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立关系的?...

    前言 这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到了2次,2次都是非常重要的面试环节,因此自己印象很深刻. 这个题目我很早就深入学习了,但是一直没有整理出来,刚好最近一段时 ...

  2. 阿里面试题:Mybatis中的Dao接口和XML文件里的SQL是如何建立关系的?

    一.解析XML 首先,Mybatis在初始化SqlSessionFactoryBean的时候,找到mapperLocations路径去解析里面所有的XML文件,这里我们重点关注两部分. 1.创建Sql ...

  3. Mybatis中 Dao接口和XML文件的SQL如何建立关联

    一.解析XML: 首先,Mybatis在初始化 SqlSessionFactoryBean 的时候,找到 mapperLocations 路径去解析里面所有的XML文件,这里我们重点关注两部分. 1. ...

  4. Spring整合MyBatis原理之Mapper接口和xml文件的解析

    目录 1. 前言 2. 类 `SqlSessionFactoryBean` 2.1. 实现了 `FactoryBean` 接口的 `getObject()` 2.2. `buildSqlSession ...

  5. python怎样打开加密的文件_如何在Python中解密OpenSSL AES加密的文件?

    拉莫斯之舞 我将通过一些更正重新发布您的代码(我不想掩盖您的版本).当您的代码正常工作时,它不会检测到填充周围的一些错误.特别是,如果提供的解密密钥不正确,则填充逻辑可能会做一些奇怪的事情.如果您同意 ...

  6. matlab分析xml文件_如何在Java中读取XML文件(DOM分析器)

    matlab分析xml文件 Today we will learn how to read the XML file in Java. We will also learn how to parse ...

  7. (转)MyBatis框架的学习(四)——Mapper.xml文件中的输入和输出映射以及动态sql

    http://blog.csdn.net/yerenyuan_pku/article/details/71893689 前面对MyBatis框架的学习中,我们对Mapper.xml映射文件多少有些了解 ...

  8. 为了熟练掌握动态SQL你必须要知道Mybatis中的OGNL表达式

    前言 OGNL是个什么东西?很多刚入门Java的同学会有点陌生.但是在Structs流行的时代OGNL可是必会的数据渲染技术.它全称Object Graph Navigation Language,作 ...

  9. python中利用lxml模块解析xml文件报错XMLSyntaxError: Opening and ending tag mismatch

    今天在代码中第一次使用lxml解析xml文件时出错了, XMLSyntaxError: Opening and ending tag mismatch: keyEffectiveDate line 2 ...

最新文章

  1. CTF web总结--利用mysql日志getshell
  2. git add .出现尚未暂存以备提交的变更
  3. JAVA基础知识(2)--队列的操作
  4. mysql 过滤相同数据库_MySQL数据库查询中的重复记录过滤
  5. 开源等于免费吗?用事实来说话
  6. DVWA-SQL注入(SQL Injection)低/中/高级别
  7. 帝国php数据库备份,解决帝国cms帝国数据库备份王php5.3下500错误
  8. python中的math.floor可以用于整数吗_为什么Python的math.ceil()和math.floor()操作返回浮点数而不是整数?...
  9. 物件捆绑 背包问题 动态规划 求解
  10. 下载verycd的方法下载电驴资源隐藏资源的最新可用方法
  11. 可部署于windows和Linux的即时通讯系统
  12. java那块最难_Java哪块最难学?
  13. Unity内动态影子的各种做法
  14. 清华大学数据挖掘课程幕课习题(第一章)
  15. How Broswer Work
  16. c语言打印日历的程序,简单日历打印(C语言)
  17. 干货!区块链入门、进阶、行业专家观点!1000篇好文帮你破解区块链密码!(中篇)...
  18. luogu2657-Windy数题解--数位DP
  19. Java进阶——如何查看Java字节码
  20. keras自动编码器实现系列之卷积自动编码器

热门文章

  1. 5年前面试题引发的“血案”(番外篇)(总结和乱侃)
  2. C# 面向对象版 Windows挖雷 v0.8 release 非FloodFill算法解
  3. Hibernate基于JDBC的批量删除
  4. Java8采用stream、parallelStream迭代的区别
  5. 命令行执行php脚本中的$argv和$argc配置方法
  6. MySQL复制表-INSERT INTO SELECT
  7. 关于graphviz绘制的点(dot)图在显示时中文乱码问题的解决方法(亲测)
  8. Vue使用全局样式,页面没有发生变化:逗号是中文的,引起错误,样式不变化 也没有报错就是不起作用
  9. Linux之Vmware编码
  10. 计算机英文文献博客,计算机专业外文文献论文翻译.docx