在日常的开发工作中,常接触的持久层框架主要是Hibernate、Mybatis和spring-jdbc,其中spring-jdbc的封装程度相比之下没有另外两个框架高,所以在平时的开发中,使用Hibernate、Mybatis的较多,Hibernate更偏向与对象与关系的映射,是面向对象的一个很好的体现,但是在有些场景中,则不存在那么多的关系,使用Hibernate必要性就没那么高,况且相比Mybatis,Hibernate的学习成本要稍高一些,上手难度就更大一点,所以在我们的日常开发中,选用Mybatis的情况更多一些,当然,Hibernate框架的强大是不容否定的,感兴趣的朋友可以花点时间去做一下深入的研究,在这里我就主要分享一下我在阅读了部分Mybatis源码后的一些总结。
通过源码来看Mybatis的工作流程
我们这里也拿一个传统的spring web项目来帮助分析。虽然现在大多的项目都基于spring-boot在开发,但是其根本和传统的spring是一样的,只是在某些方面作了一些便利的操作,让开发者更简单的编码。如果要在spring项目中使用Mybatis,首先在我们是spring配置的xml文件中都会配置以下这些相关配置项:
<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>
配置主要是数据源、xml扫描规则,dao接口的扫描包路径
我们来看一下SqlSessionFactoryBean这个类,它实现了FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>这三个接口,FactoryBean代表可以getObject()获取对象,InitializingBean可以在初始化的时候作扩展,ApplicationListener代表它本身可以进行事件监听,对应配置文件,我们可以看到它设置了dataSource、mapperLocations、typeAliasesPackage和plugins这几个属性,这些都对应到了SqlSessionFactoryBean中的同名的属性,我们暂且看到这个地方,后面用到这些属性时再作详细分析。
然后我们在来看下MapperScannerConfigurer这个类,跟上面的SqlSessionFactoryBean一样,也是设置一些属性,具体的功能在后面做详细分析。
SqlSessionFactory的创建
在spring容器中,一切皆bean的思想是不会变的,从配置的标签上看,我们上面配置的两个类,同样也是两个bean,那么是bean,就会被spring容器所加载管理。我在spring容器启动流程一文中介绍了spring bean的加载创建过程,有兴趣的可以去读一读。接下来我们回到这两个bean上来,首先看看SqlSessionFactoryBean,在之前的文章中,我们提到过一种特殊的bean叫FactoryBean,恰好SqlSessionFactoryBean就是这样的一种bean,根据实现的接口可以得知,这是一个SqlSessionFactory类型的FactoryBean,在之前介绍的bean创建的时候,有一个方法叫做doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName),该方法中最终创建bean的方法则是调用object = factory.getObject(),即使用传入的FactoryBean的getObject()方法来获取bean的实例,那么我们回到SqlSessionFactoryBean中来找找看getObejct()方法的实现:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
这里返回的就是一个sqlSessionFactory,进入afterPropertiesSet()方法查看,最终会看到是调用同类中的一个buildSqlSessionFactory(),由于这个方法代码过多,我这里就只取出我们重点关注的那部分代码:
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 + "'");
}
}
这里因为是创建实例,所有代码块的configuration是为null的,所以在以上代码前是有一个new Configuration()的操作,这个configuration则是整个sqlsessionfactory的核心内容,后面的所有操作都是基于这个configuration。代码片段中的this.mapperLocations就是我们在文章开始处xml配置的mapperLocations属性,这里我的理解就是加载mapperLocations下的所有xml文件,然后存放到configuration中。
xmlMapperBuilder.parse();
进入这个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标签的属性及子标签的各种属性,如mapper标签的属性namespace,子标签如:cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* 等的id、type、parameter、property、javaType、jdbcType等属性,
详细操作可以看一下代码片段:
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");
}
总之一句话,就是解析标签的属性
configuration.addLoadedResource(this.resource):标记该资源已被加载
bindMapperForNamespace():这个就是绑定namespace对应的接口类和xml的关联,这里就为后面接口调用xml方法做好了准备。具体实现:
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;
}
可以看到,当在resultMapResolver.resolve()抛出异常时,会调用configuration.addIncompleteResultMap(resultMapResolver)方法来存储这些处理不了的或者说叫做处理异常的resultMap,parsePendingResultMaps()实际上就是对这些异常的或者说是无法处理的resultMap做一次补救,重新在进行一次解析。
parsePendingCacheRefs():同理,即是针对configurationElement方法中的cacheRefElement(context.evalNode("cache-ref"))的补救措施
parsePendingStatements():和上面两个方法一样,针对configurationElement方法中的buildStatementFromContext(context.evalNodes("select|insert|update|delete"))的补救措施
以上的所有处理,都是在组装configuration,然后通过new DefaultSqlSessionFactory(Configuration configuration),返回一个SqlSessionFactory对象,至此,SqlSessionFactory创建完成。
MapperScannerConfigurer
在spring容器启动的时候,我们会调用AbstractApplicationContext中的refresh()方法,在refresh()方法中有调用了invokeBeanFactoryPostProcessors(beanFactory),该方法的执行在 obtainFreshBeanFactory()后,说明在执行invokeBeanFactoryPostProcessors的时候我们的xml已经完成了解析,并且对应beanDefiniton已经生成,在这个基础上我们进入到invokeBeanFactoryPostProcessors方法的实现中来:
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
继续进入到invokeBeanFactoryPostProcessors方法中,如图:
进入到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));
}
重点关注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返回,然后把对应beanDefinition注册到容器中,为后面生成对应的bean做准备。
再来仔细看一下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.设置beanClassName
2.设置beanClass,把原来的BeanClass的类型替换成了MapperFactoryBean类型,这个是Mapper接口加载定义阶段最重要的一步。是生成代理类的关键。MapperFactoryBean实现了FactoryBean接口,实现了FactoryBean接口的类型在调用getBean(beanName)既通过名称获取对象时,返回的对象不是本身类型的对象,而是通过实现接口中的getObject()方法返回的对象,MapperFactoryBean实现了FactoryBean接口InitializingBean接口,在对象初始化的时候会调用它的afterPropertiesSet方法,该方法中首先调用了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)方法重点关注,最终实现是在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对应xml的解析成MappedStatement,并添加到了configuration对象中,这里的configuration也就是我们上面提到的new Configuration()创建的那个对象(非常重要)。
mapper接口的定义在bean加载阶段会被替换成MapperFactoryBean类型,在spring容器初始化的时候会给我们生成MapperFactoryBean类型的对象,在该对象生成的过程中调用afterPropertiesSet()方法,为我们生成了一个MapperProxyFactory类型的对象存放于Configuration里的MapperRegistry对象中,同时解析了mapper接口对应的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);
}
豁然开朗,这里就是创建代理类的地方,被代理对象为MapperProxy,正是这个MapperProxy,为后面的调用链路,提供了核心的功能。
3.添加属性addToConfig
4.判断配置的是sqlSessionFactoryBeanName还是sqlSessionFactory,最终都转换为sqlSessionFactory实例(就是我们上面创建的sqlSessionFactory的引用)
5.转换设置的sqlSessionTemplate
6.设置自动装配方式,默认是根据类型装配
以上就是mybatis对应两个配置项的加载过程梳理,这两个bean在容器启动后就加入到了spring的容器中去了,接下来我们继续来看我们在使用的时候,调用的链路是如何的。
Mybatis调用链路解析
service.select():service为代理接口类,select()为执行方法,首先就会进入到MapperProxy中执行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)是具体执行操作的方法:
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;
}
这里会判断是具体的操作类型是增、删、改还是查,这里以查询列表为例进行分析
executeForMany:最终进入到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方法查询数据库,这里我们看看这个executor是个什么东西,是如何生成的。
回到MapperProxy中,在调用execute方法时传入了sqlSession参数,最后我们在对数据库做操作时,也是使用的这个sqlSession做的查询,这个sqlSession根据我们前面的分析应该是一个DefaultSqlSession,恰好executor正好是这个类中的一个属性,追溯到代理类创建的地方,也就是是MapperFactoryBean中,里面有一个getSqlSession()的操作,实际上是调用的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;
}
其本质是一个SqlSessionTemplate类型的,我们再到SqlSessionTemplate去看看它的构造方法:
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());
}
其中包含了不少的内容,特别留意一下sqlSessionProxy ,这里的sqlSessionProxy实际上是一个代理类,代理对象是位于本类中的一个内部类SqlSessionInterceptor。好了,我们回到上面的查询方法selectList中来,这个selectList实际上调用的就是SqlSessionTemplate中的selectList:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
}
既然sqlSessionProxy是代理类,selectList就会调用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);
}
}
}
进入到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();
}
}
会发现,原来excutor是在这里创建的,并且调用的configuration里的方法:
final Executor executor = configuration.newExecutor(tx, execType);
进入到configuration中看它的实现:
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;
}
根据指定不同的执行器类型来创建执行器,常用的就是SimpleExecutor,在这里面重点关注一下:
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
cacheEnabled是configuration的属性,意思就是说我们可以配置这个开关,网上查一下mybatis配置项我们可以得知,这个是开启Mybatis二级缓存的开关,也就是说,二级缓存使用executor就是CachingExecutor。转过头来,我们来看executor.query方法,如果是使用SimpleExecutor,则进入BaseExecutor执行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);然后返回,如果不存在,则调用queryFromDatabase即从数据库中查询,这个localCache就是Mybatis的一级缓存:
private Map<Object, Object> cache = new HashMap<Object, Object>();
...
public Object getObject(Object key) {
return cache.get(key);
}
其本质就是一个HashMap,查询条件组装为key,查询结果为value,在执行查询获得结果后,如果localcache设置的作用域为STATEMENT,则会对localcache进行clear(),我们常用的即是STATEMENT,所以每次查询数据都是从数据库中获取的最新数据,没有延迟。
如果我们开启了二级缓存,则是调用CachingExecutor中的query方法,实现如下:
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,这里就是当我们开启二级缓存时,也可以让他每次获取数据库最新的数据,则是配置刷新cache,这个配置就是mapperstatement中的flushCacheRequired.
接下来的逻辑就简单了,从缓存中取,取到值则直接返回,否则就从数据库去查询,然后放入缓存中。
这就是mybatis一级缓存和二级缓存的实现方式。
返回了查询结果,则按照原调用链路逐级return,一直到返回给调用的应用,这就是一个完整的mybatis接口调用链路。
总结
Mybatis的实现方式其核心就是使用了动态代理来实现各种操作,查询获取数据库连接、mybatis事务的管理,都是通过动态代理来实现了。在实际开发中,我们接触的mybatis通用mapper,mybaits-plus,一级分页插件PageInterceptor的实现都是在这个基础上进行的扩展,为开发者提供了更简洁的开发方式。