面試題:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立關(guān)系的?

記錄個(gè)人在 Java 學(xué)習(xí)路上的一些學(xué)習(xí)、思考、經(jīng)驗(yàn)和總結(jié)。如果覺得有幫助,歡迎關(guān)注我的技術(shù)博客和微信公眾號(hào)。

前言

這是 mybatis 比較常問到的面試題,我自己在以前的面試過程中被問到了2次,2次都是非常重要的面試環(huán)節(jié),因此自己印象很深刻。

這個(gè)題目我很早就深入學(xué)習(xí)了,但是一直沒有整理出來,剛好最近一段時(shí)間由于工作太忙,大概有半年沒有技術(shù)文章產(chǎn)出,因此趁著五一有點(diǎn)時(shí)間,整理了下分享給大家。

另外,估計(jì)不少同學(xué)應(yīng)該也注意到了,DAO 接口的全路徑名和 XML 文件中的 SQL 的 namespace + id 是一樣的。其實(shí),這也是建立關(guān)聯(lián)的根本原因。

本文中的源碼使用當(dāng)前最新的版本,即:mybatis-spring 為 2.0.4,mybatis 為 3.5.4,引入這2個(gè) jar 包即可查看到本文的所有代碼。

正文

當(dāng)一個(gè)項(xiàng)目中使用了 Spring 和 Mybatis 時(shí),通常會(huì)有以下配置。當(dāng)然現(xiàn)在很多項(xiàng)目應(yīng)該都是 SpringBoot 了,可能沒有以下配置,但是究其底層原理都是類似的,無非是將掃描 bean 等一些工作通過注解來實(shí)現(xiàn)。

<!-- DAO接口所在包名,Spring會(huì)自動(dòng)查找其下的類 -->

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

    <!--basePackage指定要掃描的包,在此包之下的映射器都會(huì)被搜索到??芍付ǘ鄠€(gè)包,包與包之間用逗號(hào)或分號(hào)分隔-->

    <property name="basePackage" value="com.joonwhee.open.mapper"/>

    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

</bean>

<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

    <property name="dataSource" ref="dataSource"/>

    <!-- 自動(dòng)掃描mapping.xml文件 -->

    <property name="mapperLocations" value="classpath:config/mapper/*.xml"/>

    <property name="configLocation" value="classpath:config/mybatis/mybatis-config.xml"/>

    <!--Entity package -->

    <property name="typeAliasesPackage" value="com.joonwhee.open.po"/>

</bean>

<!-- dataSource -->

<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>

通常我們還會(huì)有 DAO 類和 對(duì)用的 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" ?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<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 是一個(gè) BeanDefinitionRegistryPostProcessor,會(huì)在 Spring 構(gòu)建 IoC容器的早期被調(diào)用重寫的 postProcessBeanDefinitionRegistry 方法,參考:Spring IoC:invokeBeanFactoryPostProcessors 詳解


@Override

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

  if (this.processPropertyPlaceHolders) {

    processPropertyPlaceHolders();

  }

  // 1.新建一個(gè)ClassPathMapperScanner,并填充相應(yīng)屬性

  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.設(shè)置mapper bean是否需要懶加載

    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));

  }

  // 3.注冊(cè)Filter,因?yàn)樯厦鏄?gòu)造函數(shù)我們沒有使用默認(rèn)的Filter,

  // 有兩種Filter,includeFilters:要掃描的;excludeFilters:要排除的

  scanner.registerFilters();

  // 4.掃描basePackage,basePackage可通過",; \t\n"來填寫多個(gè),

  // ClassPathMapperScanner重寫了doScan方法

  scanner.scan(

      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

}

3.注冊(cè) Filter,見代碼塊1。

4.掃描 basePackage,這邊會(huì)走到 ClassPathBeanDefinitionScanner(ClassPathMapperScanner 的父類),然后在執(zhí)行 “doScan(basePackages)” 時(shí)回到 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.如果指定了標(biāo)記接口,則將標(biāo)記接口添加到includeFilters,

  // 但這邊重寫了matchClassName方法,并返回了false,

  // 相當(dāng)于忽略了標(biāo)記接口上的匹配項(xiàng),所以該參數(shù)目前相當(dāng)于沒有任何作用

  if (this.markerInterface != null) {

    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {

      @Override

      protected boolean matchClassName(String className) {

        return false;

      }

    });

    acceptAllInterfaces = false;

  }

  // 3.如果沒有指定annotationClass和markerInterface,則

  // 添加默認(rèn)的includeFilters,直接返回true,接受所有類

  if (acceptAllInterfaces) {

    // default include filter that accepts all classes

    addIncludeFilter((metadataReader, metadataReaderFactory) -> true);

  }

  // exclude package-info.java

  // 4.添加默認(rèn)的excludeFilters,排除以package-info結(jié)尾的類

  addExcludeFilter((metadataReader, metadataReaderFactory) -> {

    String className = metadataReader.getClassMetadata().getClassName();

    return className.endsWith("package-info");

  });

}

通常我們都不會(huì)指定 annotationClass 和 markerInterface,也就是會(huì)添加默認(rèn)的 Filter,相當(dāng)于會(huì)接受除了 package-info 結(jié)尾的所有類。因此,basePackage 包下的類不需要使用 @Component 注解或 XML 中配置 bean 定義,也會(huì)被添加到 IoC 容器中。

代碼塊2:doScan


@Override

public Set<BeanDefinitionHolder> doScan(String... basePackages) {

  // 1.直接使用父類的方法掃描和注冊(cè)bean定義,

  // 之前在spring中已經(jīng)介紹過:https://joonwhee.blog.csdn.net/article/details/87477952 代碼塊5

  Set<BeanDefinitionHolder> 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.對(duì)掃描到的beanDefinitions進(jìn)行處理,主要4件事:

    // 1)將bean的真正接口類添加到通用構(gòu)造函數(shù)參數(shù)中

    // 2)將beanClass直接設(shè)置為MapperFactoryBean.class,

    //  結(jié)合1,相當(dāng)于要使用的構(gòu)造函數(shù)是MapperFactoryBean(java.lang.Class<T>)

    // 3)添加sqlSessionFactory屬性,sqlSessionFactoryBeanName和

    //  sqlSessionFactory中,優(yōu)先使用sqlSessionFactoryBeanName

    // 4)添加sqlSessionTemplate屬性,同樣的,sqlSessionTemplateBeanName

    //  優(yōu)先于sqlSessionTemplate,

    processBeanDefinitions(beanDefinitions);

  }

  return beanDefinitions;

}

小結(jié),解析 MapperScannerConfigurer 主要是做了幾件事:

1)新建掃描器 ClassPathMapperScanner;

2)使用 ClassPathMapperScanner 掃描注冊(cè) basePackage 包下的所有 bean;

3)將 basePackage 包下的所有 bean 進(jìn)行一些特殊處理:beanClass 設(shè)置為 MapperFactoryBean、bean 的真正接口類作為構(gòu)造函數(shù)參數(shù)傳入 MapperFactoryBean、為 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate屬性。

2、解析 SqlSessionFactoryBean

對(duì)于 SqlSessionFactoryBean 來說,實(shí)現(xiàn)了2個(gè)接口,InitializingBean 和 FactoryBean,看過我之前 Spring 文章的同學(xué)應(yīng)該對(duì)這2個(gè)接口不會(huì)陌生,簡單來說:1)FactoryBean 可以自己定義創(chuàng)建實(shí)例對(duì)象的方法,只需要實(shí)現(xiàn)它的 getObject() 方法;InitializingBean 則是會(huì)在 bean 初始化階段被調(diào)用。

SqlSessionFactoryBean 重寫這兩個(gè)接口的部分方法代碼如下,核心代碼就一個(gè)方法—— “buildSqlSessionFactory()”。


@Override

public SqlSessionFactory getObject() throws Exception {

  if (this.sqlSessionFactory == null) {

    // 如果之前沒有構(gòu)建,則這邊也會(huì)調(diào)用afterPropertiesSet進(jìn)行構(gòu)建操作

    afterPropertiesSet();

  }

  return this.sqlSessionFactory;

}

@Override

public void afterPropertiesSet() throws Exception {

  // 省略部分代碼

  // 構(gòu)建sqlSessionFactory

  this.sqlSessionFactory = buildSqlSessionFactory();

}

buildSqlSessionFactory()

主要做了幾件事:1)對(duì)我們配置的參數(shù)進(jìn)行相應(yīng)解析;2)使用配置的參數(shù)構(gòu)建一個(gè) Configuration;3)使用 Configuration 新建一個(gè) DefaultSqlSessionFactory。

這邊的核心內(nèi)容是對(duì)于 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構(gòu)建DefaultSqlSessionFactory

  return this.sqlSessionFactoryBuilder.build(targetConfiguration);

}

5.2 解析mapper文件,見代碼塊3。

代碼塊3:parse()


public void parse() {

  // 1.如果resource沒被加載過才進(jìn)行加載

  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.設(shè)置currentNamespace屬性

    builderAssistant.setCurrentNamespace(namespace);

    // 3.解析parameterMap、resultMap、sql等節(jié)點(diǎn)

    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.解析增刪改查節(jié)點(diǎn),封裝成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(List<XNode> list) {

  if (configuration.getDatabaseId() != null) {

    buildStatementFromContext(list, configuration.getDatabaseId());

  }

  // 解析增刪改查節(jié)點(diǎn),封裝成Statement

  buildStatementFromContext(list, null);

}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {

  for (XNode context : list) {

    // 1.構(gòu)建XMLStatementBuilder

    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);

    try {

      // 2.解析節(jié)點(diǎn)

      statementParser.parseStatementNode();

    } catch (IncompleteElementException e) {

      configuration.addIncompleteStatement(statementParser);

    }

  }

}

這邊會(huì)一直執(zhí)行到 “statementParser.parseStatementNode();”,見代碼塊5。

這邊每個(gè) XNode 都相當(dāng)于如下的一個(gè) SQL,下面封裝的每個(gè) MappedStatement 可以理解就是每個(gè) 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() {

  // 省略所有的屬性解析

  // 將解析出來的所有參數(shù)添加到 mappedStatements 緩存

  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,

      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,

      resultSetTypeEnum, flushCache, useCache, resultOrdered,

      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

}

// MapperBuilderAssistant.java

public 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.使用參數(shù)構(gòu)建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構(gòu)建MappedStatement

  MappedStatement statement = statementBuilder.build();

  // 4.將MappedStatement 添加到緩存

  configuration.addMappedStatement(statement);

  return statement;

}

該方法會(huì)將節(jié)點(diǎn)的屬性解析后封裝成 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對(duì)應(yīng)的綁定類型

      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 <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 {

      // 將type和以該type為參數(shù)構(gòu)建的MapperProxyFactory作為鍵值對(duì),

      // 放到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 對(duì)應(yīng)的 class,value 為 MapperProxyFactory。

小結(jié),解析 SqlSessionFactoryBean 主要做了幾件事:

1)解析處理所有屬性參數(shù)構(gòu)建 Configuration ,使用 Configuration 新建 DefaultSqlSessionFactory;

2)解析 mapperLocations 屬性的 mapper 文件,將 mapper 文件中的每個(gè) SQL 封裝成 MappedStatement,放到 mappedStatements 緩存中,key 為 id,例如:com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey,value 為 MappedStatement。

3)將解析過的 mapper 文件的 namespace 放到 knownMappers 緩存中,key 為 namespace 對(duì)應(yīng)的 class,value 為 MapperProxyFactory。

3、解析 DAO 文件

DAO 文件,也就是 basePackage 指定的包下的文件,也就是上文的 interface UserPOMapper 。

上文 doScan 中說過,basePackage 包下所有 bean 定義的 beanClass 會(huì)被設(shè)置成 MapperFactoryBean.class,而 MapperFactoryBean 也是 FactoryBean,因此直接看 MapperFactoryBean 的 getObject 方法。

@Override

public T getObject() throws Exception {

  // 1.從父類中拿到sqlSessionTemplate,這邊的sqlSessionTemplate也是doScan中添加的屬性

  // 2.通過mapperInterface獲取mapper

  return getSqlSession().getMapper(this.mapperInterface);

}

// SqlSessionTemplate

@Override

public <T> T getMapper(Class<T> type) {

  return getConfiguration().getMapper(type, this);

}

// Configuration.java

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {

  return mapperRegistry.getMapper(type, sqlSession);

}

// MapperRegistry.java

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {

  // 1.從knownMappers緩存中獲取

  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

  if (mapperProxyFactory == null) {

    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");

  }

  try {

    // 2.新建實(shí)例

    return mapperProxyFactory.newInstance(sqlSession);

  } catch (Exception e) {

    throw new BindingException("Error getting mapper instance. Cause: " + e, e);

  }

}

// MapperProxyFactory.java

public T newInstance(SqlSession sqlSession) {

  // 1.構(gòu)造一個(gè)MapperProxy

  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);

  // 2.使用MapperProxy來構(gòu)建實(shí)例對(duì)象

  return newInstance(mapperProxy);

}

protected T newInstance(MapperProxy<T> mapperProxy) {

  // 使用JDK動(dòng)態(tài)代理來代理要?jiǎng)?chuàng)建的實(shí)例對(duì)象,InvocationHandler為mapperProxy,

  // 因此當(dāng)我們真正調(diào)用時(shí),會(huì)走到mapperProxy的invoke方法

  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

}

這邊代碼用到的 sqlSessionTemplate、mapperInterface 等都是之前添加的屬性。

小結(jié),解析 DAO 文件 主要做了幾件事:

1)通過 mapperInterface 從 knownMappers 緩存中獲取到 MapperProxyFactory 對(duì)象;

2)通過 JDK 動(dòng)態(tài)代理創(chuàng)建 MapperProxyFactory 實(shí)例對(duì)象,InvocationHandler 為 MapperProxy。

4、DAO 接口被調(diào)用

當(dāng) DAO 中的接口被調(diào)用時(shí),會(huì)走到 MapperProxy 的 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 {

      // 1.創(chuàng)建MapperMethodInvoker

      // 2.將method -> MapperMethodInvoker放到methodCache緩存

      // 3.調(diào)用MapperMethodInvoker的invoke方法

      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);

    }

  } catch (Throwable t) {

    throw ExceptionUtil.unwrapThrowable(t);

  }

}

// MapperProxy.java

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {

  try {

    // 1.放到methodCache緩存,key為method,value為MapperMethodInvoker

    return methodCache.computeIfAbsent(method, m -> {

      if (m.isDefault()) {

        // 2.方法為默認(rèn)方法,Java8之后,接口允許有默認(rèn)方法

        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.正常接口會(huì)走這邊,使用mapperInterface、method、configuration

        // 構(gòu)建一個(gè)MapperMethod,封裝成PlainMethodInvoker

        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

      }

    });

  } catch (RuntimeException re) {

    Throwable cause = re.getCause();

    throw cause == null ? re : cause;

  }

}

3.調(diào)用 MapperMethodInvoker 的 invoke 方法,見代碼塊7。

代碼塊7:invoke


@Override

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {

  return mapperMethod.execute(sqlSession, args);

}

// MapperMethod.java

public Object execute(SqlSession sqlSession, Object[] args) {

  Object result;

  // 1.根據(jù)命令類型執(zhí)行來進(jìn)行相應(yīng)操作

  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;

}

這邊就比較簡單,根據(jù)不同的操作類型執(zhí)行相應(yīng)的操作,最終將結(jié)果返回,見代碼塊8。

這邊的 command 是上文 “new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())” 時(shí)創(chuàng)建的。

代碼塊8:增刪改查


// 1.insert

@Override

public int insert(String statement, Object parameter) {

  return update(statement, parameter);

}

// 2.update

@Override

public int update(String statement, Object parameter) {

  try {

    dirty = true;

    // 從mappedStatements緩存拿到對(duì)應(yīng)的MappedStatement對(duì)象,執(zhí)行更新操作

    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

@Override

public int delete(String statement, Object parameter) {

  return update(statement, parameter);

}

// 4.select,以executeForMany為例

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {

  List<E> result;

  // 1.參數(shù)轉(zhuǎn)換成sql命令參數(shù)

  Object param = method.convertArgsToSqlCommandParam(args);

  if (method.hasRowBounds()) {

    RowBounds rowBounds = method.extractRowBounds(args);

    result = sqlSession.selectList(command.getName(), param, rowBounds);

  } else {

    // 2.執(zhí)行查詢操作

    result = sqlSession.selectList(command.getName(), param);

  }

  // 3.處理返回結(jié)果

  // 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;

}

@Override

public <E> List<E> selectList(String statement, Object parameter) {

  return this.selectList(statement, parameter, RowBounds.DEFAULT);

}

@Override

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {

  try {

    //從mappedStatements緩存中拿到對(duì)應(yīng)的MappedStatement對(duì)象,執(zhí)行查詢操作

    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 緩存中拿到對(duì)應(yīng)的 MappedStatement 對(duì)象,執(zhí)行相應(yīng)的操作。

這邊的增刪改查不是直接調(diào)用 SqlSession 中的方法,而是調(diào)用 SqlSessionTemplate 中的方法,繼而通過 sqlSessionProxy 來調(diào)用 SqlSession 中的方法。SqlSessionTemplate 中的方法主要是通過 sqlSessionProxy 做了一層動(dòng)態(tài)代理,基本沒差別。

總結(jié)

整個(gè)流程主要是以下幾個(gè)核心步驟:

1)掃描注冊(cè) basePackage 包下的所有 bean,將 basePackage 包下的所有 bean 進(jìn)行一些特殊處理:beanClass 設(shè)置為 MapperFactoryBean、bean 的真正接口類作為構(gòu)造函數(shù)參數(shù)傳入 MapperFactoryBean、為 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate屬性。

2)解析 mapperLocations 屬性的 mapper 文件,將 mapper 文件中的每個(gè) SQL 封裝成 MappedStatement,放到 mappedStatements 緩存中,key 為 id,例如:com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey,value 為 MappedStatement。并且將解析過的 mapper 文件的 namespace 放到 knownMappers 緩存中,key 為 namespace 對(duì)應(yīng)的 class,value 為 MapperProxyFactory。

3)創(chuàng)建 DAO 的 bean 時(shí),通過 mapperInterface 從 knownMappers 緩存中獲取到 MapperProxyFactory 對(duì)象,通過 JDK 動(dòng)態(tài)代理創(chuàng)建 MapperProxyFactory 實(shí)例對(duì)象,InvocationHandler 為 MapperProxy。

4)DAO 中的接口被調(diào)用時(shí),通過動(dòng)態(tài)代理,調(diào)用 MapperProxy 的 invoke 方法,最終通過 mapperInterface 從 mappedStatements 緩存中拿到對(duì)應(yīng)的 MappedStatement,執(zhí)行相應(yīng)的操作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容