記錄個(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)的操作。