日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

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

發(fā)布時(shí)間:2025/3/11 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 if mybatis tk 多个_面试题:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立关系的?... 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

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

這個(gè)題目我很早就深入學(xué)習(xí)了,但是一直沒(méi)有整理出來(lái),剛好最近一段時(shí)間由于工作太忙,大概有半年沒(méi)有技術(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 了,可能沒(méi)有以下配置,但是究其底層原理都是類似的,無(wú)非是將掃描 bean 等一些工作通過(guò)注解來(lái)實(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, namefrom userwhere id = #{id,jdbcType=INTEGER}</select> </mapper>

1、解析 MapperScannerConfigurer

MapperScannerConfigurer 是一個(gè) BeanDefinitionRegistryPostProcessor,會(huì)在 Spring 構(gòu)建 IoC容器的早期被調(diào)用重寫(xiě)的 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ù)我們沒(méi)有使用默認(rèn)的Filter,// 有兩種Filter,includeFilters:要掃描的;excludeFilters:要排除的scanner.registerFilters();// 4.掃描basePackage,basePackage可通過(guò)",; tn"來(lái)填寫(xiě)多個(gè),// ClassPathMapperScanner重寫(xiě)了doScan方法scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }

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

4.掃描 basePackage,這邊會(huì)走到 ClassPathBeanDefinitionScanner(ClassPathMapperScanner 的父類),然后在執(zhí)行 “doScan(basePackages)” 時(shí)回到 ClassPathMapperScanner 重寫(xiě)的方法,見(jiàn)代碼塊2。

代碼塊1:registerFilters

public void registerFilters() {boolean acceptAllInterfaces = true;// if specified, use the given annotation and / or marker interface// 1.如果指定了注解,則將注解添加到includeFiltersif (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,// 但這邊重寫(xiě)了matchClassName方法,并返回了false,// 相當(dāng)于忽略了標(biāo)記接口上的匹配項(xiàng),所以該參數(shù)目前相當(dāng)于沒(méi)有任何作用if (this.markerInterface != null) {addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {@Overrideprotected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}// 3.如果沒(méi)有指定annotationClass和markerInterface,則// 添加默認(rèn)的includeFilters,直接返回true,接受所有類if (acceptAllInterfaces) {// default include filter that accepts all classesaddIncludeFilter((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)介紹過(guò):Spring IoC源碼學(xué)習(xí):context:component-scan 節(jié)點(diǎn)詳解 代碼塊5Set<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 來(lái)說(shuō),實(shí)現(xiàn)了2個(gè)接口,InitializingBean 和 FactoryBean,看過(guò)我之前 Spring 文章的同學(xué)應(yīng)該對(duì)這2個(gè)接口不會(huì)陌生,簡(jiǎn)單來(lái)說(shuō):1)FactoryBean 可以自己定義創(chuàng)建實(shí)例對(duì)象的方法,只需要實(shí)現(xiàn)它的 getObject() 方法;InitializingBean 則是會(huì)在 bean 初始化階段被調(diào)用。

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

@Override public SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {// 如果之前沒(méi)有構(gòu)建,則這邊也會(huì)調(diào)用afterPropertiesSet進(jìn)行構(gòu)建操作afterPropertiesSet();}return this.sqlSessionFactory; }@Override public void afterPropertiesSet() throws Exception {// 省略部分代碼// 構(gòu)建sqlSessionFactorythis.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 新建XMLMapperBuilderXMLMapperBuilder 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)建DefaultSqlSessionFactoryreturn this.sqlSessionFactoryBuilder.build(targetConfiguration); }

5.2 解析mapper文件,見(jiàn)代碼塊3。

代碼塊3:parse()

public void parse() {// 1.如果resource沒(méi)被加載過(guò)才進(jìn)行加載if (!configuration.isResourceLoaded(resource)) {// 1.1 解析mapper文件configurationElement(parser.evalNode("/mapper"));// 1.2 將resource添加到已加載列表configuration.addLoadedResource(resource);// 1.3 綁定namespace的mapperbindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements(); }

1.1 解析mapper文件,見(jiàn)代碼4。

1.3 綁定namespace的mapper,見(jiàn)代碼塊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),封裝成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());}// 解析增刪改查節(jié)點(diǎn),封裝成StatementbuildStatementFromContext(list, null); }private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {// 1.構(gòu)建XMLStatementBuilderfinal 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();”,見(jiàn)代碼塊5。

這邊每個(gè) XNode 都相當(dāng)于如下的一個(gè) SQL,下面封裝的每個(gè) MappedStatement 可以理解就是每個(gè) SQL。

<select id="queryByPrimaryKey" resultMap="BaseResultMap"parameterType="java.lang.Integer">select id, name, password, agefrom userwhere id = #{id,jdbcType=INTEGER} </select>

代碼塊5:parseStatementNode

public void parseStatementNode() {// 省略所有的屬性解析// 將解析出來(lái)的所有參數(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.queryByPrimaryKeyid = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;// 2.使用參數(shù)構(gòu)建MappedStatement.BuilderMappedStatement.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)建MappedStatementMappedStatement 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還沒(méi)有添加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);}}} }

主要是將剛剛解析過(guò)的 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)將解析過(guò)的 mapper 文件的 namespace 放到 knownMappers 緩存中,key 為 namespace 對(duì)應(yīng)的 class,value 為 MapperProxyFactory。

3、解析 DAO 文件

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

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

@Override public T getObject() throws Exception {// 1.從父類中拿到sqlSessionTemplate,這邊的sqlSessionTemplate也是doScan中添加的屬性// 2.通過(guò)mapperInterface獲取mapperreturn 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è)MapperProxyfinal MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);// 2.使用MapperProxy來(lái)構(gòu)建實(shí)例對(duì)象return newInstance(mapperProxy); }protected T newInstance(MapperProxy<T> mapperProxy) {// 使用JDK動(dòng)態(tài)代理來(lá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)通過(guò) mapperInterface 從 knownMappers 緩存中獲取到 MapperProxyFactory 對(duì)象;

2)通過(guò) 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為MapperMethodInvokerreturn 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,封裝成PlainMethodInvokerreturn 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 方法,見(jiàn)代碼塊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í)行來(lái)進(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; }

這邊就比較簡(jiǎn)單,根據(jù)不同的操作類型執(zhí)行相應(yīng)的操作,最終將結(jié)果返回,見(jiàn)代碼塊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 supportif (!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 中的方法,繼而通過(guò) sqlSessionProxy 來(lái)調(diào)用 SqlSession 中的方法。SqlSessionTemplate 中的方法主要是通過(guò) sqlSessionProxy 做了一層動(dòng)態(tài)代理,基本沒(mé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。并且將解析過(guò)的 mapper 文件的 namespace 放到 knownMappers 緩存中,key 為 namespace 對(duì)應(yīng)的 class,value 為 MapperProxyFactory。

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

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

推薦閱讀

程序員囧輝:字節(jié)、美團(tuán)、快手核心部門(mén)面試總結(jié)(真題解析)?zhuanlan.zhihu.com程序員囧輝:如何準(zhǔn)備好一場(chǎng)大廠面試?zhuanlan.zhihu.com程序員囧輝:跳槽,如何選擇一家公司?zhuanlan.zhihu.com程序員囧輝:如何寫(xiě)一份讓 HR 眼前一亮的簡(jiǎn)歷(附模板)?zhuanlan.zhihu.com程序員囧輝:4 年 Java 經(jīng)驗(yàn)面試總結(jié)、心得體會(huì)?zhuanlan.zhihu.com程序員囧輝:面試阿里,HashMap 這一篇就夠了?zhuanlan.zhihu.com程序員囧輝:面試必問(wèn)的線程池,你懂了嗎??zhuanlan.zhihu.com程序員囧輝:BATJTMD 面試必問(wèn)的 MySQL,你懂了嗎??zhuanlan.zhihu.com程序員囧輝:面試題:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立關(guān)系的??zhuanlan.zhihu.com

總結(jié)

以上是生活随笔為你收集整理的if mybatis tk 多个_面试题:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立关系的?...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。