MyBatis 源码解读 一讲到底
创始人
2024-02-27 18:20:12
0

序言

前面大概的介绍了一下mybatis的一些东西,那么现在我们来解析一下Mybatis的源码,就从demo开始

 @Testpublic void TestExample() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession session = sqlSessionFactory.openSession();try {BlogMapper mapper = session.getMapper(BlogMapper.class);BlogExample example = new BlogExample();BlogExample.Criteria criteria = example.createCriteria();criteria.andBidEqualTo(1);List list = mapper.selectByExample(example);System.out.println(list);} finally {session.close();}}

第一步,我们通过建造者模式创建一个工厂类,配置文件的解析就是在这一步完成的,包括 mybatis-config.xml 和 Mapper 适配器文件。

问题:解析的时候怎么解析的,做了什么,产生了什么对象,结果存放到了哪里。解析的结果决定着我们后面有什么对象可以使用,和到哪里去取。

第二步,通过 SqlSessionFactory 创建一个 SqlSession。

问题:SqlSession 是用来操作数据库的,返回了什么实现类,除了 SqlSession,还创建了什么对象,创建了什么环境?

第三步,获得一个 Mapper 对象。

问题:Mapper 是一个接口,没有实现类,是不能被实例化的,那获取到的这个Mapper 对象是什么对象?为什么要从 SqlSession 里面去获取?为什么传进去一个接口,然后还要用接口类型来接收?

第四步,调用接口方法。

问题:我们的接口没有创建实现类,为什么可以调用它的方法?那它调用的是什么方法?它又是根据什么找到我们要执行的 SQL 的?也就是接口方法怎么和 XML 映射器里面的 StatementID 关联起来的?此外,我们的方法参数是怎么转换成 SQL 参数的?获取到的结果集是怎么转换成对象的?

下面我们带着问题,来看源码,并且绘制时序图。

庖丁解牛

一、配置解析过程

首先我们要清楚的是配置解析的过程全部只解析两种文件 。 一个 是mybatis-config.xml 全局配置文件。另外就是可能有很多个的 Mapper.xml 文件,也包括在 Mapper 接口类上面定义的注解。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

首先我们 new 了一个 SqlSessionFactoryBuilder,非常明显的建造者模式,它里面定义了很多个 build 方法的重载,最终返回的是一个 SqlSessionFactory 对象(单例模式)。我们点进去build 方法。

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {if (inputStream != null) {inputStream.close();}} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(new Configuration());ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props);this.parsed = false;this.environment = environment;this.parser = parser;}

这里面创建了一个 XMLConfigBuilder 对象(Configuration 对象也是这个时候创建的)

XMLConfigBuilder

XMLConfigBuilder 是抽象类 BaseBuilder 的一个子类,专门用来解析全局配置文件

public class XMLConfigBuilder extends BaseBuilder 

根据我们解析的文件流,这里后面两个参数都是空的,创建了一个 parser。这里有两步,第一步是调用 parser 的 parse()方法,它会返回一个 Configuration类。

  public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}

我们先看一下 parse()方法:

  public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;}

首先会检查是不是已经解析过,也就是说在应用的生命周期里面,config 配置文件只需要解析一次,生成的 Configuration 对象也会存在应用的整个生命周期中。接下来就是 parseConfiguration 方法:

  private void parseConfiguration(XNode root) {try {// issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

这下面有十几个方法,对应着 config 文件里面的所有一级标签。

问题:MyBatis 全局配置文件的顺序可以颠倒吗?

先说答案,不能如果颠倒,那么启动Mybatis就会异常!!!

propertiesElement()
private void propertiesElement(XNode context) throws Exception {if (context != null) {Properties defaults = context.getChildrenAsProperties();String resource = context.getStringAttribute("resource");String url = context.getStringAttribute("url");if (resource != null && url != null) {throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");}if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}Properties vars = configuration.getVariables();if (vars != null) {defaults.putAll(vars);}parser.setVariables(defaults);configuration.setVariables(defaults);}}

第一个是解析标签,读取我们引入的外部配置文件。这里面又有两种类型,一种是放在 resource 目录下的,是相对路径,一种是写的绝对路径的。解析的最终结果就是我们会把所有的配置信息放到名为 defaults 的 Properties 对象里面,最后把XPathParser 和 Configuration 的 Properties 属性都设置成我们填充后的 Properties对象。

 parser.setVariables(defaults);configuration.setVariables(defaults);
Properties settings = settingsAsProperties(root.evalNode(“settings”));
  private Properties settingsAsProperties(XNode context) {if (context == null) {return new Properties();}Properties props = context.getChildrenAsProperties();// Check that all settings are known to the configuration classMetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);for (Object key : props.keySet()) {if (!metaConfig.hasSetter(String.valueOf(key))) {throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");}}return props;}
 public Properties getChildrenAsProperties() {Properties properties = new Properties();for (XNode child : getChildren()) {String name = child.getStringAttribute("name");String value = child.getStringAttribute("value");if (name != null && value != null) {properties.setProperty(name, value);}}return properties;}

第二个,我们把标签也解析成了一个 Properties 对象,对于标签的子标签的处理在后面。

loadCustomVfs(settings);
 private void loadCustomVfs(Properties props) throws ClassNotFoundException {String value = props.getProperty("vfsImpl");if (value != null) {String[] clazzes = value.split(",");for (String clazz : clazzes) {if (!clazz.isEmpty()) {@SuppressWarnings("unchecked")Class vfsImpl = (Class)Resources.classForName(clazz);configuration.setVfsImpl(vfsImpl);}}}}

loadCustomVfs 是获取 Vitual File System 的自定义实现类,比如我们要读取本地文件,或者 FTP 远程文件的时候,就可以用到自定义的 VFS 类。我们根据标签里面的标签,生成了一个抽象类 VFS 的子类,并且赋值到 Configuration中。

    static VFS createVFS() {// Try the user implementations first, then the built-insList> impls = new ArrayList<>();impls.addAll(USER_IMPLEMENTATIONS);impls.addAll(Arrays.asList((Class[]) IMPLEMENTATIONS));// Try each implementation class until a valid one is foundVFS vfs = null;for (int i = 0; vfs == null || !vfs.isValid(); i++) {Class impl = impls.get(i);try {vfs = impl.getDeclaredConstructor().newInstance();if (!vfs.isValid() && log.isDebugEnabled()) {log.debug("VFS implementation " + impl.getName()+ " is not valid in this environment.");}} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {log.error("Failed to instantiate " + impl, e);return null;}}if (log.isDebugEnabled()) {log.debug("Using VFS adapter " + vfs.getClass().getName());}return vfs;}}
loadCustomLogImpl(settings);
  private void loadCustomLogImpl(Properties props) {Class logImpl = resolveClass(props.getProperty("logImpl"));configuration.setLogImpl(logImpl);}

loadCustomLogImpl 是根据标签获取日志的实现类,我们可以用到很多的日志的方案,包括 LOG4J,LOG4J2,SLF4J 等等。这里生成了一个 Log 接口的实现类,并且赋值到 Configuration中。

typeAliasesElement(root.evalNode(“typeAliases”));
  private void typeAliasesElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String typeAliasPackage = child.getStringAttribute("name");configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);} else {String alias = child.getStringAttribute("alias");String type = child.getStringAttribute("type");try {Class clazz = Resources.classForName(type);if (alias == null) {typeAliasRegistry.registerAlias(clazz);} else {typeAliasRegistry.registerAlias(alias, clazz);}} catch (ClassNotFoundException e) {throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);}}}}}

接下来,我们解析标签,它有两种定义方式,一种是直接定义一个类的别名,一种就是指定一个包,那么这个 package 下面所有的类的名字就会成为这个类全路径的别名。类的别名和类的关系,我们放在一个 TypeAliasRegistry 对象里面。

 public void registerAliases(String packageName, Class superType) {ResolverUtil> resolverUtil = new ResolverUtil<>();resolverUtil.find(new ResolverUtil.IsA(superType), packageName);Set>> typeSet = resolverUtil.getClasses();for (Class type : typeSet) {// Ignore inner classes and interfaces (including package-info.java)// Skip also inner classes. See issue #6if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {registerAlias(type);}}}
  public void registerAlias(Class type) {String alias = type.getSimpleName();Alias aliasAnnotation = type.getAnnotation(Alias.class);if (aliasAnnotation != null) {alias = aliasAnnotation.value();}registerAlias(alias, type);}public void registerAlias(String alias, Class value) {if (alias == null) {throw new TypeException("The parameter alias cannot be null");}// issue #748String key = alias.toLowerCase(Locale.ENGLISH);if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");}typeAliases.put(key, value);}
private final Map> typeAliases = new HashMap<>();
pluginElement(root.evalNode(“plugins”));
  private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();interceptorInstance.setProperties(properties);configuration.addInterceptor(interceptorInstance);}}
  public void addInterceptor(Interceptor interceptor) {interceptorChain.addInterceptor(interceptor);}

接下来就是解析标签,比如 Pagehelper 的翻页插件,或者我们自定义的插件。标签里面只有标签,标签里面只有标签。标签解析完以后,会生成一个 Interceptor 对象,并且添加到 Configuration 的InterceptorChain 属性里面,它是一个 List。

objectFactoryElement(root.evalNode(“objectFactory”));
  private void objectFactoryElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");Properties properties = context.getChildrenAsProperties();ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();factory.setProperties(properties);configuration.setObjectFactory(factory);}}

其实就是实例化objectFactory,设置到 Configuration 的属性里面。

objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”));
  private void objectWrapperFactoryElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();configuration.setObjectWrapperFactory(factory);}}

和objectFactoryElement差不多,就是实例化ObjectWrapperFactory,同样设置到 Configuration 的属性里面。

reflectorFactoryElement(root.evalNode(“reflectorFactory”));
  private void reflectorFactoryElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();configuration.setReflectorFactory(factory);}}

解析 reflectorFactory 标签,生成 ReflectorFactory 对象

settingsElement
  private void settingsElement(Properties props) {configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));configuration.setLogPrefix(props.getProperty("logPrefix"));configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));configuration.setArgNameBasedConstructorAutoMapping(booleanValueOf(props.getProperty("argNameBasedConstructorAutoMapping"), false));configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));configuration.setNullableOnForEach(booleanValueOf(props.getProperty("nullableOnForEach"), false));}

这里就是对标签里面所有子标签的处理了,前面我们已经把子标签全部转换成了 Properties 对象,所以在这里处理 Properties 对象就可以了。二级标签里面有很多的配置,比如二级缓存,延迟加载,自动生成主键这些。需要注意的是,我们之前提到的所有的默认值,都是在这里赋值的。所有的值,都会赋值到 Configuration 的属性里面去。

environmentsElement
 private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {environment = context.getStringAttribute("default");}for (XNode child : context.getChildren()) {String id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) {TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));DataSource dataSource = dsFactory.getDataSource();Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);configuration.setEnvironment(environmentBuilder.build());break;}}}}

这一步是解析标签。一个 environment 就是对应一个数据源,所以在这里我们会根据配置的创建一个事务工厂,根据标签创建一个数据源,最后把这两个对象设置成 Environment 对象的属性,放到 Configuration 里面。
回答了前面的问题:数据源工厂和数据源在哪里创建。
先记下这个问题:数据源和事务工厂在哪里会用到?

databaseIdProviderElement
private void databaseIdProviderElement(XNode context) throws Exception {DatabaseIdProvider databaseIdProvider = null;if (context != null) {String type = context.getStringAttribute("type");// awful patch to keep backward compatibilityif ("VENDOR".equals(type)) {type = "DB_VENDOR";}Properties properties = context.getChildrenAsProperties();databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();databaseIdProvider.setProperties(properties);}Environment environment = configuration.getEnvironment();if (environment != null && databaseIdProvider != null) {String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());configuration.setDatabaseId(databaseId);}}

解析 databaseIdProvider 标签,生成 DatabaseIdProvider 对象(用来支持不同厂商的数据库)

typeHandlerElement
  private void typeHandlerElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String typeHandlerPackage = child.getStringAttribute("name");typeHandlerRegistry.register(typeHandlerPackage);} else {String javaTypeName = child.getStringAttribute("javaType");String jdbcTypeName = child.getStringAttribute("jdbcType");String handlerTypeName = child.getStringAttribute("handler");Class javaTypeClass = resolveClass(javaTypeName);JdbcType jdbcType = resolveJdbcType(jdbcTypeName);Class typeHandlerClass = resolveClass(handlerTypeName);if (javaTypeClass != null) {if (jdbcType == null) {typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);} else {typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);}} else {typeHandlerRegistry.register(typeHandlerClass);}}}}}

跟 TypeAlias 一样,TypeHandler 有两种配置方式,一种是单独配置一个类,一种是指定一个 package。最后我们得到的是javaTypeName 和 jdbcTypeName ,以及用来做相互映射的 TypeHandler 之间的映射关系。最后存放在 TypeHandlerRegistry 对象里面。

mapperElement
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);try(InputStream inputStream = Resources.getResourceAsStream(resource)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);try(InputStream inputStream = Resources.getUrlAsStream(url)){XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url == null && mapperClass != null) {Class mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}

首先会判断是不是接口,只有接口才解析;然后判断是不是已经注册了,单个 Mapper重复注册会抛出异常。

  public void parse() {if (!configuration.isResourceLoaded(resource)) {configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}
public void parse() {if (!configuration.isResourceLoaded(resource)) {configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}

XMLMapperBuilder.parse()方法,是对 Mapper 映射器的解析。里面有两个方法:
configurationElement()解析所有的子标签 ,

private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));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);}}

其中buildStatementFromContext()最终获得 MappedStatement 对象。

  private void buildStatementFromContext(List list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null);}
 private void buildStatementFromContext(List list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}}
public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());String parameterType = context.getStringAttribute("parameterType");Class parameterTypeClass = resolveClass(parameterType);String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre:  and  were parsed and removed)KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String resultType = context.getStringAttribute("resultType");Class resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

用namespace绑定Mapper

  private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class boundType = null;try {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#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}}

无论是按 package 扫描,还是按接口扫描,最后都会调用到 MapperRegistry 的addMapper()方法。MapperRegistry 里面维护的其实是一个 Map 容器,存储接口和代理工厂的映射关系。

configuration.addMapper(boundType);public  void addMapper(Class type) {mapperRegistry.addMapper(type);}

问题:为什么要放一个代理工厂呢?代理工厂用来干什么?

除了映射器文件,在这里也会去解析 Mapper 接口方法上的注解。在 addMapper()方法里面创建了一个 MapperAnnotationBuilder,我们点进去看一下 parse()方法。

public  void addMapper(Class 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<>(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);}}}}
 public void parse() {String resource = type.toString();if (!configuration.isResourceLoaded(resource)) {loadXmlResource();configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());parseCache();parseCacheRef();for (Method method : type.getMethods()) {if (!canHaveStatement(method)) {continue;}if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()&& method.getAnnotation(ResultMap.class) == null) {parseResultMap(method);}try {parseStatement(method);} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();}

parseCache() 和 parseCacheRef() 方 法 其 实 是 对 @CacheNamespace 和@CacheNamespaceRef 这两个注解的处理。

 private void parseCache() {CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);if (cacheDomain != null) {Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();Properties props = convertToProperties(cacheDomain.properties());assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);}}
  private void parseCacheRef() {CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);if (cacheDomainRef != null) {Class refType = cacheDomainRef.value();String refName = cacheDomainRef.name();if (refType == void.class && refName.isEmpty()) {throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");}if (refType != void.class && !refName.isEmpty()) {throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");}String namespace = (refType != void.class) ? refType.getName() : refName;try {assistant.useCacheRef(namespace);} catch (IncompleteElementException e) {configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));}}}

讲一下@CacheNamespace和@CacheNamespaceRef这俩

@CacheNamespace,作用于mapper接口上面,是用来实现二级缓存的。
这个的问题是在于,可以缓存接口的sql,但是没有办法办法缓存xml文件的,你开启了则反之,那么你可能会说,就TMD不能双管齐下,还真TMD不行。
那么如何同时去满足这个问题呢?
@CacheNamespaceRef 来解决这个问题,也就是在接口上使用这个注解,把接口上的@CacheNamespace注解替换成@CacheNamespaceRef ,同时的话xml文件里面使用cache标签,

顺便说一下,如果某个SQL不想被缓存,可以单独处理一下:useCache = false.SQL走的是注解形式:@Options(useCache=false)
如果你走的是xml,你在注解上使用这个注解,将不会起效。

parseStatement()方法里面的各种 getAnnotation(),都是对注解的解析,比如@Options,@SelectKey,@ResultMap 等等。

  void parseStatement(Method method) {final Class parameterTypeClass = getParameterType(method);final LanguageDriver languageDriver = getLanguageDriver(method);getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);final String mappedStatementId = type.getName() + "." + method.getName();final KeyGenerator keyGenerator;String keyProperty = null;String keyColumn = null;if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {// first check for SelectKey annotation - that overrides everything elseSelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class).map(x -> (SelectKey)x.getAnnotation()).orElse(null);if (selectKey != null) {keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);keyProperty = selectKey.keyProperty();} else if (options == null) {keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;} else {keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;keyProperty = options.keyProperty();keyColumn = options.keyColumn();}} else {keyGenerator = NoKeyGenerator.INSTANCE;}Integer fetchSize = null;Integer timeout = null;StatementType statementType = StatementType.PREPARED;ResultSetType resultSetType = configuration.getDefaultResultSetType();boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = !isSelect;boolean useCache = isSelect;if (options != null) {if (FlushCachePolicy.TRUE.equals(options.flushCache())) {flushCache = true;} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {flushCache = false;}useCache = options.useCache();fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348timeout = options.timeout() > -1 ? options.timeout() : null;statementType = options.statementType();if (options.resultSetType() != ResultSetType.DEFAULT) {resultSetType = options.resultSetType();}}String resultMapId = null;if (isSelect) {ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);if (resultMapAnnotation != null) {resultMapId = String.join(",", resultMapAnnotation.value());} else {resultMapId = generateResultMapName(method);}}assistant.addMappedStatement(mappedStatementId,sqlSource,statementType,sqlCommandType,fetchSize,timeout,// ParameterMapIDnull,parameterTypeClass,resultMapId,getReturnType(method),resultSetType,flushCache,useCache,// TODO gcode issue #577false,keyGenerator,keyProperty,keyColumn,statementAnnotation.getDatabaseId(),languageDriver,// ResultSetsoptions != null ? nullOrEmpty(options.resultSets()) : null);});}

最后同样会解析成 MappedStatement 对象,也就是说在 XML 中配置,和使用注解配置,最后起到一样的效果

如果注册没有完成,还要从 Map 里面 remove 掉。

  if (!loadCompleted) {knownMappers.remove(type);}

在这一步,我们主要完成了 config 配置文件、Mapper 文件、Mapper 接口上的注解的解析。我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。最后,返回了一个 DefaultSqlSessionFactory,里面持有了Configuration 的实例。

二、会话创建过程

这是第二步,我们跟数据库的每一次连接,都需要创建一个会话,我们用openSession()方法来创建。

  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {try {boolean autoCommit;try {autoCommit = connection.getAutoCommit();} catch (SQLException e) {// Failover to true, as most poor drivers// or databases won't support transactionsautoCommit = true;}final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);final Transaction tx = transactionFactory.newTransaction(connection);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

这个会话里面,需要包含一个 Executor 用来执行 SQL。Executor 又要指定事务类型和执行器的类型。所以我们会先从 Configuration 里面拿到 Enviroment,Enviroment 里面就有事务工厂。

1、创建 Transaction
      final Transaction tx = transactionFactory.newTransaction(connection);

如果配置的是 JDBC,则会使用 Connection 对象的 commit()、rollback()、close()管理事务。
如果配置成 MANAGED,会把事务交给容器来管理,比如 JBOSS,Weblogic。因
为我们跑的是本地程序,如果配置成 MANAGE 不会有任何事务。
如果是 Spring + MyBatis , 则没有必要配置 , 因为我们会直接在applicationContext.xml 里面配置数据源和事务管理器,覆盖 MyBatis 的配置。

2、创建 Executor
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : 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;}

我们知道,Executor 的基本类型有三种:SIMPLE、BATCH、REUSE,默认是 SIMPLE(settingsElement()读取默认值),他们都继承了抽象类 BaseExecutor。

SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。

ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map 内,供下一次使用。简言之,就是重复使用 Statement 对象。

BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行executeBatch()批处理。与 JDBC 批处理相同。

如果配置了 cacheEnabled=ture,会用装饰器模式对 executor 进行包装:newCachingExecutor(executor)

回答了前面的问题:数据源和事务工厂在哪里会用到——创建执行器的时候。

最终返回 DefaultSqlSession,属性包括 Configuration、Executor 对象。

总结:创建会话的过程,我们获得了一个 DefaultSqlSession,里面包含了一个Executor,它是 SQL 的执行者。

return new DefaultSqlSession(configuration, executor, autoCommit);

三、获得 Mapper 对象

现在我们已经有一个 DefaultSqlSession 了,必须找到 Mapper.xml 里面定义的Statement ID,才能执行对应的 SQL 语句。

找到 Statement ID 有两种方式:一种是直接调用 session 的方法,在参数里面传入Statement ID,这种方式属于硬编码,我们没办法知道有多少处调用,修改起来也很麻烦。另一个问题是如果参数传入错误,在编译阶段也是不会报错的,不利于预先发现问题。

Blog blog = (Blog) session.selectOne("com.anxin.mapper.BlogMapper.selectBlogById", 1);

所以在 MyBatis 后期的版本提供了第二种方式,就是定义一个接口,然后再调用Mapper 接口的方法。

由于我们的接口名称跟 Mapper.xml 的 namespace 是对应的,接口的方法跟statement ID 也都是对应的,所以根据方法就能找到对应的要执行的 SQL。

BlogMapper mapper = session.getMapper(BlogMapper.class);

在这里我们主要研究一下 Mapper 对象是怎么获得的,它的本质是什么。
DefaultSqlSession 的 getMapper()方法,调用了 Configuration 的 getMapper()方法。

public  T getMapper(Class type) {return configuration.getMapper(type, this);}

我们知道,在解析 mapper 标签和 Mapper.xml 的时候已经把接口类型和类型对应的 MapperProxyFactory 放到了一个 Map 中。获取 Mapper 代理对象,实际上是从Map 中获取对应的工厂类后,调用以下方法创建对象:

  @SuppressWarnings("unchecked")public  T getMapper(Class type, SqlSession sqlSession) {final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}

最终通过代理模式返回代理对象:

  public T newInstance(SqlSession sqlSession) {final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}
  protected T newInstance(MapperProxy mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

回答了前面的问题:为什么要保存一个工厂类,它是用来创建代理对象的。

JDK 动态代理和 MyBatis 用到的 JDK 动态代理有什么区别?
JDK 动态代理代理,在实现了 InvocationHandler 的代理类里面,需要传入一个被代理对象的实现类。
MyBatis 的动态代理,不需要实现类的原因:我们只需要根据接口类型+方法的名称,就可以找到Statement ID 了,而唯一要做的一件事情也是这件,所以不需要实现类。在 MapperProxy里面直接执行逻辑(也就是执行 SQL)就可以。
先记下这个问题:在代理类中为什么要持有一个 SqlSession?

四、执行 SQL

由于所有的 Mapper 都是 MapperProxy 代理对象,所以任意的方法都是执行MapperProxy 的 invoke()方法。

问题 1:我们引入 MapperProxy 为了解决什么问题?硬编码和编译时检查问题。它需要做的事情是:根据方法查找 Statement ID 的问题。
问题 2:这里没有实现类,进入到 invoke 方法的时候做了什么事情?它是怎么找到我们要执行的SQL的?

MapperProxy.invoke()

 @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {return cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}

获取缓存

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {return MapUtil.computeIfAbsent(methodCache, method, m -> {if (m.isDefault()) {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 {return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}}
 @Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);}

MapperMethod 里 面 主 要 有 两 个 属 性 , 一 个 是 SqlCommand , 一 个 是MethodSignature,这两个都是 MapperMethod 的内部类。另外定义了多个 execute()方法。

DefaultSqlSession.selectOne()

  @Overridepublic  T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.List list = this.selectList(statement, parameter);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}}

在 SelectList()中,我们先根据 command name(Statement ID)从 Configuration中拿到 MappedStatement,这个 ms 上面有我们在 xml 中配置的所有属性,包括 id、statementType、sqlSource、useCache、入参、出参等等。然后执行了 Executor 的 query()方法。

  private  List selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {try {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, handler);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

SIMPLE/REUSE/BATCH,还有一种包装类型,CachingExecutor。那么在这里到底会选择哪一种执行器呢?我们要回过头去看看 DefaultSqlSession 在初始化的时候是怎么赋值的,这个就是我们的会话创建过程。如果启用了二级缓存,就会先调用 CachingExecutor 的 query()方法,里面有缓存相关的操作,然后才是再调用基本类型的执行器,比如默认的 SimpleExecutor。

  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

在没有开启二级缓存的情况下,先会走到 BaseExecutor 的 query()方法(否则会先走到 CachingExecutor)。

@Overridepublic  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}

创建 CacheKey

@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new ExecutorException("Executor was closed.");}CacheKey cacheKey = new CacheKey();cacheKey.update(ms.getId());cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());cacheKey.update(boundSql.getSql());List parameterMappings = boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();// mimic DefaultParameterHandler logicfor (ParameterMapping parameterMapping : parameterMappings) {if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}cacheKey.update(value);}}if (configuration.getEnvironment() != null) {// issue #176cacheKey.update(configuration.getEnvironment().getId());}return cacheKey;}
if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}

queryStack 用于记录查询栈,防止递归查询重复处理缓存。
flushCache=true 的时候,会先清理本地缓存(一级缓存):clearLocalCache();
如果没有缓存,会从数据库查询:queryFromDatabase(),如果 LocalCacheScope == STATEMENT,会清理本地缓存。

 @Overridepublic  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List list;try {queryStack++;list = resultHandler == null ? (List) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}

从数据库查询

  private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}

先在缓存用占位符占位。执行查询后,移除占位符,放入数据

执行 Executor 的 doQuery();默认是 SimpleExecutor

  @Overridepublic  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}

StatementHandler 里面包含了处理参数的 ParameterHandler 和处理结果集的ResultSetHandler。

public abstract class BaseStatementHandler implements StatementHandler {protected final Configuration configuration;protected final ObjectFactory objectFactory;protected final TypeHandlerRegistry typeHandlerRegistry;protected final ResultSetHandler resultSetHandler;protected final ParameterHandler parameterHandler;protected final Executor executor;protected final MappedStatement mappedStatement;protected final RowBounds rowBounds;protected BoundSql boundSql;......}

这两个对象都是在上面 new 的时候创建的。

this.parameterHandler = configuration.newParameterHandler(mappedStatement,parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement,rowBounds, parameterHandler, resultHandler, boundSql);

这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。

statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);

创建 Statement

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;}

用 new 出来的 StatementHandler 创建 Statement 对象——prepareStatement(),方法对语句进行预编译,处理参数。

 handler.parameterize(stmt);

执行的 StatementHandler 的 query()方法

  @Overridepublic  List query(Statement statement, ResultHandler resultHandler) throws SQLException {return delegate.query(statement, resultHandler);}

RoutingStatementHandler 的 query()方法。delegate 委派,最终执行 PreparedStatementHandler 的 query()方法。

执行 PreparedStatement 的 execute()方法

 @Overridepublic  List query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.handleResultSets(ps);}

ResultSetHandler 处理结果集

   List handleResultSets(Statement stmt) throws SQLException;

问题:怎么把 ResultSet 转换成 List?
ResultSetHandler 只有一个实现类:DefaultResultSetHandler。也就是执行DefaultResultSetHandler 的 handleResultSets ()方法。首先我们会先拿到第一个结果集,如果没有配置一个查询返回多个结果集的情况,一般只有一个结果集。如果下面的这个 while 循环我们也不用,就是执行一次。然后会调用 handleResultSet()方法

结束语

MyBatis 核心源码就差不多到这里了,时序图大概如下。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

相关内容

热门资讯

修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
【前端】‘??‘与‘||‘有什... 0 问题 经常写const data = res.data.a ?? ''或者const d...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
牛客计算器的改良(Python... 文章目录1.题目描述2.输入描述:3.输出描述:4.示例15.分析6.代码7.结语 链接࿱...
正大杯|市调大赛|2023备赛... 关键信息 同时随着精细化养宠趋势的深入,宠物消费类目日渐丰富。 本报告通过 Niuco...
QGIS下载在线地图(Goog... 前言 国内外有很多在线地图下载软件,但功能单一,基本上只能下载数据&#x...