在日常的開發(fā)工作中恶守,常接觸的持久層框架主要是Hibernate进泼、Mybatis和spring-jdbc,其中spring-jdbc的封裝程度相比之下沒有另外兩個框架高监憎,所以在平時的開發(fā)中渔期,使用Hibernate镊叁、Mybatis的較多柠辞,Hibernate更偏向與對象與關(guān)系的映射团秽,是面向?qū)ο蟮囊粋€很好的體現(xiàn),但是在有些場景中叭首,則不存在那么多的關(guān)系习勤,使用Hibernate必要性就沒那么高,況且相比Mybatis焙格,Hibernate的學(xué)習(xí)成本要稍高一些图毕,上手難度就更大一點(diǎn),所以在我們的日常開發(fā)中眷唉,選用Mybatis的情況更多一些予颤,當(dāng)然,Hibernate框架的強(qiáng)大是不容否定的冬阳,感興趣的朋友可以花點(diǎn)時間去做一下深入的研究蛤虐,在這里我就主要分享一下我在閱讀了部分Mybatis源碼后的一些總結(jié)。
通過源碼來看Mybatis的工作流程
我們這里也拿一個傳統(tǒng)的spring web項目來幫助分析肝陪。雖然現(xiàn)在大多的項目都基于spring-boot在開發(fā)驳庭,但是其根本和傳統(tǒng)的spring是一樣的,只是在某些方面作了一些便利的操作见坑,讓開發(fā)者更簡單的編碼嚷掠。如果要在spring項目中使用Mybatis,首先在我們是spring配置的xml文件中都會配置以下這些相關(guān)配置項:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
<property name="typeAliasesPackage" value="com.hq.entity"></property>
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
helperDialect=oracle
</value>
</property>
</bean>
<!-- <bean class="com.hq.interceptor.MapperInterceptor">
</bean> -->
</array>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bonzer.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
配置主要是數(shù)據(jù)源捏检、xml掃描規(guī)則荞驴,dao接口的掃描包路徑
我們來看一下SqlSessionFactoryBean這個類,它實現(xiàn)了FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>這三個接口贯城,F(xiàn)actoryBean代表可以getObject()獲取對象熊楼,InitializingBean可以在初始化的時候作擴(kuò)展,ApplicationListener代表它本身可以進(jìn)行事件監(jiān)聽,對應(yīng)配置文件鲫骗,我們可以看到它設(shè)置了dataSource犬耻、mapperLocations、typeAliasesPackage和plugins這幾個屬性执泰,這些都對應(yīng)到了SqlSessionFactoryBean中的同名的屬性枕磁,我們暫且看到這個地方,后面用到這些屬性時再作詳細(xì)分析术吝。
然后我們在來看下MapperScannerConfigurer這個類计济,跟上面的SqlSessionFactoryBean一樣,也是設(shè)置一些屬性排苍,具體的功能在后面做詳細(xì)分析沦寂。
SqlSessionFactory的創(chuàng)建
在spring容器中,一切皆bean的思想是不會變的淘衙,從配置的標(biāo)簽上看传藏,我們上面配置的兩個類,同樣也是兩個bean彤守,那么是bean毯侦,就會被spring容器所加載管理。我在spring容器啟動流程一文中介紹了spring bean的加載創(chuàng)建過程具垫,有興趣的可以去讀一讀叫惊。接下來我們回到這兩個bean上來,首先看看SqlSessionFactoryBean做修,在之前的文章中霍狰,我們提到過一種特殊的bean叫FactoryBean,恰好SqlSessionFactoryBean就是這樣的一種bean饰及,根據(jù)實現(xiàn)的接口可以得知蔗坯,這是一個SqlSessionFactory類型的FactoryBean,在之前介紹的bean創(chuàng)建的時候燎含,有一個方法叫做doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)宾濒,該方法中最終創(chuàng)建bean的方法則是調(diào)用object = factory.getObject(),即使用傳入的FactoryBean的getObject()方法來獲取bean的實例屏箍,那么我們回到SqlSessionFactoryBean中來找找看getObejct()方法的實現(xiàn):
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
這里返回的就是一個sqlSessionFactory,進(jìn)入afterPropertiesSet()方法查看绘梦,最終會看到是調(diào)用同類中的一個buildSqlSessionFactory(),由于這個方法代碼過多赴魁,我這里就只取出我們重點(diǎn)關(guān)注的那部分代碼:
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
這里因為是創(chuàng)建實例卸奉,所有代碼塊的configuration是為null的,所以在以上代碼前是有一個new Configuration()的操作颖御,這個configuration則是整個sqlsessionfactory的核心內(nèi)容榄棵,后面的所有操作都是基于這個configuration。代碼片段中的this.mapperLocations就是我們在文章開始處xml配置的mapperLocations屬性,這里我的理解就是加載mapperLocations下的所有xml文件,然后存放到configuration中疹鳄。
xmlMapperBuilder.parse();
進(jìn)入這個parse()方法
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
configurationElement(this.parser.evalNode("/mapper")):解析mapper標(biāo)簽的屬性及子標(biāo)簽的各種屬性拧略,如mapper標(biāo)簽的屬性namespace,子標(biāo)簽如:cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* 等的id瘪弓、type垫蛆、parameter、property腺怯、javaType月褥、jdbcType等屬性,
詳細(xì)操作可以看一下代碼片段:
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
this.sqlElement(context.evalNodes("/mapper/sql"));
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
總之一句話瓢喉,就是解析標(biāo)簽的屬性
configuration.addLoadedResource(this.resource):標(biāo)記該資源已被加載
bindMapperForNamespace():這個就是綁定namespace對應(yīng)的接口類和xml的關(guān)聯(lián)宁赤,這里就為后面接口調(diào)用xml方法做好了準(zhǔn)備。具體實現(xiàn):
String namespace = this.builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException var4) {
}
if (boundType != null && !this.configuration.hasMapper(boundType)) {
this.configuration.addLoadedResource("namespace:" + namespace);
this.configuration.addMapper(boundType);
}
}
this.parsePendingResultMaps():這個方法從名稱上來理解是解析一些等待處理的resultMap栓票,這里可以回過頭來查看一下configurationElement(this.parser.evalNode("/mapper"))中的resultMapElements(context.evalNodes("/mapper/resultMap"))方法决左,在這個方法中有這么一段代碼:
ResultMapResolver resultMapResolver = new ResultMapResolver(this.builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException var14) {
this.configuration.addIncompleteResultMap(resultMapResolver);
throw var14;
}
可以看到,當(dāng)在resultMapResolver.resolve()拋出異常時走贪,會調(diào)用configuration.addIncompleteResultMap(resultMapResolver)方法來存儲這些處理不了的或者說叫做處理異常的resultMap佛猛,parsePendingResultMaps()實際上就是對這些異常的或者說是無法處理的resultMap做一次補(bǔ)救,重新在進(jìn)行一次解析坠狡。
parsePendingCacheRefs():同理继找,即是針對configurationElement方法中的cacheRefElement(context.evalNode("cache-ref"))的補(bǔ)救措施
parsePendingStatements():和上面兩個方法一樣,針對configurationElement方法中的buildStatementFromContext(context.evalNodes("select|insert|update|delete"))的補(bǔ)救措施
以上的所有處理逃沿,都是在組裝configuration婴渡,然后通過new DefaultSqlSessionFactory(Configuration configuration),返回一個SqlSessionFactory對象凯亮,至此边臼,SqlSessionFactory創(chuàng)建完成。
MapperScannerConfigurer
在spring容器啟動的時候假消,我們會調(diào)用AbstractApplicationContext中的refresh()方法柠并,在refresh()方法中有調(diào)用了invokeBeanFactoryPostProcessors(beanFactory),該方法的執(zhí)行在 obtainFreshBeanFactory()后富拗,說明在執(zhí)行invokeBeanFactoryPostProcessors的時候我們的xml已經(jīng)完成了解析臼予,并且對應(yīng)beanDefiniton已經(jīng)生成,在這個基礎(chǔ)上我們進(jìn)入到invokeBeanFactoryPostProcessors方法的實現(xiàn)中來:
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
繼續(xù)進(jìn)入到invokeBeanFactoryPostProcessors方法中啃沪,如圖:
進(jìn)入到MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
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.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
重點(diǎn)關(guān)注scan方法:
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
然后一直來到:
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
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 {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
doScan方法:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
這里主要是解析包路徑下的接口類粘拾,然后解析成為beanDefinition返回,然后把對應(yīng)beanDefinition注冊到容器中谅阿,為后面生成對應(yīng)的bean做準(zhǔn)備半哟。
再來仔細(xì)看一下processBeanDefinitions,
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
主要有這些操作:
1.設(shè)置beanClassName
2.設(shè)置beanClass签餐,把原來的BeanClass的類型替換成了MapperFactoryBean類型寓涨,這個是Mapper接口加載定義階段最重要的一步。是生成代理類的關(guān)鍵氯檐。MapperFactoryBean實現(xiàn)了FactoryBean接口戒良,實現(xiàn)了FactoryBean接口的類型在調(diào)用getBean(beanName)既通過名稱獲取對象時,返回的對象不是本身類型的對象冠摄,而是通過實現(xiàn)接口中的getObject()方法返回的對象糯崎,MapperFactoryBean實現(xiàn)了FactoryBean接口InitializingBean接口,在對象初始化的時候會調(diào)用它的afterPropertiesSet方法河泳,該方法中首先調(diào)用了checkDaoConfig()方法沃呢,MapperFactoryBean重載的checkDaoConfig()如下:
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
configuration.addMapper(this.mapperInterface)方法重點(diǎn)關(guān)注,最終實現(xiàn)是在MapperRegistry中:
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(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);
}
}
}
}
parser.parse()完成了對mapper對應(yīng)xml的解析成MappedStatement拆挥,并添加到了configuration對象中,這里的configuration也就是我們上面提到的new Configuration()創(chuàng)建的那個對象(非常重要)薄霜。
mapper接口的定義在bean加載階段會被替換成MapperFactoryBean類型,在spring容器初始化的時候會給我們生成MapperFactoryBean類型的對象纸兔,在該對象生成的過程中調(diào)用afterPropertiesSet()方法惰瓜,為我們生成了一個MapperProxyFactory類型的對象存放于Configuration里的MapperRegistry對象中,同時解析了mapper接口對應(yīng)的xml文件汉矿,把每一個方法解析成一個MappedStatement對象崎坊,存放于Configuration里mappedStatements這個Map集合中。
緊跟著來看一下MapperFactoryBean的getObject()方法:
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
跟蹤getMapper方法一直到MapperProxyFactory的newInstance(SqlSession sqlSession):
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
豁然開朗洲拇,這里就是創(chuàng)建代理類的地方奈揍,被代理對象為MapperProxy,正是這個MapperProxy,為后面的調(diào)用鏈路赋续,提供了核心的功能打月。
3.添加屬性addToConfig
4.判斷配置的是sqlSessionFactoryBeanName還是sqlSessionFactory,最終都轉(zhuǎn)換為sqlSessionFactory實例(就是我們上面創(chuàng)建的sqlSessionFactory的引用)
5.轉(zhuǎn)換設(shè)置的sqlSessionTemplate
6.設(shè)置自動裝配方式,默認(rèn)是根據(jù)類型裝配
以上就是mybatis對應(yīng)兩個配置項的加載過程梳理蚕捉,這兩個bean在容器啟動后就加入到了spring的容器中去了奏篙,接下來我們繼續(xù)來看我們在使用的時候,調(diào)用的鏈路是如何的迫淹。
Mybatis調(diào)用鏈路解析
service.select():service為代理接口類秘通,select()為執(zhí)行方法,首先就會進(jìn)入到MapperProxy中執(zhí)行invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
mapperMethod.execute(sqlSession, args)是具體執(zhí)行操作的方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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);
}
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ìn)行分析
executeForMany:最終進(jìn)入到selectList方法:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
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();
}
}
先從configuration中找到MappedStatement应民,再使用executor的query方法查詢數(shù)據(jù)庫话原,這里我們看看這個executor是個什么東西夕吻,是如何生成的。
回到MapperProxy中繁仁,在調(diào)用execute方法時傳入了sqlSession參數(shù)涉馅,最后我們在對數(shù)據(jù)庫做操作時,也是使用的這個sqlSession做的查詢黄虱,這個sqlSession根據(jù)我們前面的分析應(yīng)該是一個DefaultSqlSession稚矿,恰好executor正好是這個類中的一個屬性,追溯到代理類創(chuàng)建的地方捻浦,也就是是MapperFactoryBean中晤揣,里面有一個getSqlSession()的操作,實際上是調(diào)用的SqlSessionDaoSupport的getSqlSession(),返回的就是一個sqlSession,在此類中朱灿,查看sqlSession值的來源昧识,總共有兩處:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
其本質(zhì)是一個SqlSessionTemplate類型的,我們再到SqlSessionTemplate去看看它的構(gòu)造方法:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
其中包含了不少的內(nèi)容盗扒,特別留意一下sqlSessionProxy ,這里的sqlSessionProxy實際上是一個代理類滞诺,代理對象是位于本類中的一個內(nèi)部類SqlSessionInterceptor。好了环疼,我們回到上面的查詢方法selectList中來习霹,這個selectList實際上調(diào)用的就是SqlSessionTemplate中的selectList:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
}
既然sqlSessionProxy是代理類,selectList就會調(diào)用SqlSessionInterceptor中的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
進(jìn)入到getSqlSession中炫隶,一直跟蹤到DefaultSqlSessionFactory:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
會發(fā)現(xiàn)淋叶,原來excutor是在這里創(chuàng)建的,并且調(diào)用的configuration里的方法:
final Executor executor = configuration.newExecutor(tx, execType);
進(jìn)入到configuration中看它的實現(xiàn):
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
根據(jù)指定不同的執(zhí)行器類型來創(chuàng)建執(zhí)行器,常用的就是SimpleExecutor伪阶,在這里面重點(diǎn)關(guān)注一下:
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
cacheEnabled是configuration的屬性煞檩,意思就是說我們可以配置這個開關(guān),網(wǎng)上查一下mybatis配置項我們可以得知栅贴,這個是開啟Mybatis二級緩存的開關(guān)斟湃,也就是說,二級緩存使用executor就是CachingExecutor檐薯。轉(zhuǎn)過頭來凝赛,我們來看executor.query方法,如果是使用SimpleExecutor坛缕,則進(jìn)入BaseExecutor執(zhí)行query,這里查詢的邏輯如下:
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
先是從localCache中去取墓猎,有值則handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);然后返回,如果不存在赚楚,則調(diào)用queryFromDatabase即從數(shù)據(jù)庫中查詢毙沾,這個localCache就是Mybatis的一級緩存:
private Map<Object, Object> cache = new HashMap<Object, Object>();
...
public Object getObject(Object key) {
return cache.get(key);
}
其本質(zhì)就是一個HashMap,查詢條件組裝為key,查詢結(jié)果為value宠页,在執(zhí)行查詢獲得結(jié)果后左胞,如果localcache設(shè)置的作用域為STATEMENT寇仓,則會對localcache進(jìn)行clear(),我們常用的即是STATEMENT,所以每次查詢數(shù)據(jù)都是從數(shù)據(jù)庫中獲取的最新數(shù)據(jù)烤宙,沒有延遲遍烦。
如果我們開啟了二級緩存,則是調(diào)用CachingExecutor中的query方法门烂,實現(xiàn)如下:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
首先是從mapperstatement中去獲取一個cache,這個cache位于mapperstatement中乳愉,就說明二級緩存是基于mapperstatement的兄淫,如果cache不為空屯远,則flushCacheIfRequired,這里就是當(dāng)我們開啟二級緩存時,也可以讓他每次獲取數(shù)據(jù)庫最新的數(shù)據(jù)捕虽,則是配置刷新cache,這個配置就是mapperstatement中的flushCacheRequired.
接下來的邏輯就簡單了慨丐,從緩存中取,取到值則直接返回泄私,否則就從數(shù)據(jù)庫去查詢房揭,然后放入緩存中。
這就是mybatis一級緩存和二級緩存的實現(xiàn)方式晌端。
返回了查詢結(jié)果捅暴,則按照原調(diào)用鏈路逐級return,一直到返回給調(diào)用的應(yīng)用咧纠,這就是一個完整的mybatis接口調(diào)用鏈路蓬痒。
總結(jié)
Mybatis的實現(xiàn)方式其核心就是使用了動態(tài)代理來實現(xiàn)各種操作,查詢獲取數(shù)據(jù)庫連接漆羔、mybatis事務(wù)的管理梧奢,都是通過動態(tài)代理來實現(xiàn)了。在實際開發(fā)中演痒,我們接觸的mybatis通用mapper亲轨,mybaits-plus,一級分頁插件PageInterceptor的實現(xiàn)都是在這個基礎(chǔ)上進(jìn)行的擴(kuò)展鸟顺,為開發(fā)者提供了更簡潔的開發(fā)方式惦蚊。