MyBatis启动流程与Configuration配置体系

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析DNS,个人版 1个月
简介: MyBatis启动流程大致的过程如下:加载配置XML文件读取mybatis的dtd描述文件,并且解析xml标签通过读取的XML配置信息生成对应的全局配置对象,以及生成需要mapper的SQL映射。创建 SqlSessionFactory 完成之后,使用 SqlSessionFactory 创建 Session。Congfiguration:是Mybatis初始化过程的核心对象,mybatis中几乎全部的配置信息会保存到Configuration中,全局生效。XMLConfigBuilder:用于创建Configuration,解析MyBatis配置文件中 configura

MyBatis启动流程
大致的过程如下:

加载配置XML文件
读取mybatis的dtd描述文件,并且解析xml标签
通过读取的XML配置信息生成对应的全局配置对象,以及生成需要mapper的SQL映射。
创建 SqlSessionFactory 完成之后,使用 SqlSessionFactory 创建 Session。

Congfiguration:是Mybatis初始化过程的核心对象,mybatis中几乎全部的配置信息会保存到Configuration中,全局生效。
XMLConfigBuilder:用于创建Configuration,解析MyBatis配置文件中 configuration 节点的配置信息并填充到 Configuration 对象中
XMLMapperEntityResolver :包含于 XMLConfigBuilder中,用于读取本地DTD文件。
XPathParser :XPath解析器,对JDK的类做了封装,包含于 XMLConfigBuilder中。XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。
Document :包含于XPathParser 中,方便后续结合 XPathParser 读取配置文件内容。
创建 SQLSessionFactory
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

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

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
// 准备阶段:将配置文件加载到内存中并生成document对象,然后创建初始化Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析document对象并生成 SqlSessionFactory
//parser.parse()返回的就是一个Configuration对象,对应的就是下面的方法
return build(parser.parse());
}

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
我们创建的SQLSessionFactory其实是DefaultSqlSessionFactory,而他的创建需要一个Configuration 对象

创建XMLConfigBuilder
创建 SqlSessionFactory 需要:

依赖XMLConfigBuilder 来读取并且解析配置文件,将配置文件转化为Document对象
初始化Configuration 对象并且根据配置文件填充属性。
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) {
// 调用父类初始化configuration
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
// 将Properties全部设置到configuration里面去
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
// 绑定 XPathParser
this.parser = parser;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
初始化 Configuration 时,会将要用到的 class 和其别名注册到 typeAliases 这个map中。

private final Map> typeAliases = new HashMap<>();
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
创建 XPathParser
XPathParser 里面包含了很多重要的对象,包括用于读取本地DTD文件的XMLMapperEntityResolver 对象、Xpath对象、存储xml文件的Document对象以及properties标签定义的键值对集合对象variables。

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
// 初始化属性
commonConstructor(validation, variables, entityResolver);
// 读取xml文件,保存为 document
this.document = createDocument(new InputSource(inputStream));
}
public class XPathParser {
private final Document document;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
解析并设置 Configuration 中的属性
MyBatis的配置解析都是基于 标签下

我们来看一个配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

// XMLConfigBuilder
public Configuration parse() {
// 根据parsed变量的值判断是否已经完成了对mybatis-config.xml配置文件的解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在mybatis-config.xml配置文件中查找节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我们继续进入这个parseConfiguration方法:

private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析properties标签,并set到Configration对象中
//在properties配置属性后,在Mybatis的配置文件中就可以使用${key}的形式使用了。
propertiesElement(root.evalNode("properties"));

  //解析setting标签的配置
  Properties settings = settingsAsProperties(root.evalNode("settings"));
  //添加vfs的自定义实现,这个功能不怎么用
  loadCustomVfs(settings);

  //配置类的别名,配置后就可以用别名来替代全限定名
  //mybatis默认设置了很多别名,参考附录部分
  typeAliasesElement(root.evalNode("typeAliases"));

  //解析拦截器和拦截器的属性,set到Configration的interceptorChain中
  //MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
  //Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    //ParameterHandler (getParameterObject, setParameters)
    //ResultSetHandler (handleResultSets, handleOutputParameters)
    //StatementHandler (prepare, parameterize, batch, update, query)
  pluginElement(root.evalNode("plugins"));

  //Mybatis创建对象是会使用objectFactory来创建对象,一般情况下不会自己配置这个objectFactory,使用系统默认的objectFactory就好了
  objectFactoryElement(root.evalNode("objectFactory"));
  objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  reflectorFactoryElement(root.evalNode("reflectorFactory"));

  //设置在setting标签中配置的配置
  settingsElement(settings);

  //解析环境信息,包括事物管理器和数据源,SqlSessionFactoryBuilder在解析时需要指定环境id,如果不指定的话,会选择默认的环境;
  //最后将这些信息set到Configration的Environment属性里面
  environmentsElement(root.evalNode("environments"));

  //
  databaseIdProviderElement(root.evalNode("databaseIdProvider"));

  //无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。解析typeHandler。
  typeHandlerElement(root.evalNode("typeHandlers"));
  //解析Mapper
  mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
  throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
配置文件的解析需要 xpath的表达式和 document 对象, 根据标签名称匹配到节点, 取出对应的value, 找到节点返回XNode对象, 然后给Configuration设置对应的值

//XPathParser
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
1
2
3
4
我们来具体的看看几个重要节点的解析:

解析 envirmonent 节点

例如我们的配置文件中:











1
2
3
4
5
6
7
8
9
10
11
12
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 未指定XMLConfigBuilder.environment字段,则使用default属性
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍历子节点
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 与XmlConfigBuilder.environment字段匹配
if (isSpecifiedEnvironment(id)) {
// 创建TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创建DataSourceFactory和DataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建Environment
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 将Environment对象记录到Configuration.environment字段中
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
解析Mappers标签




1
2
3
如果节点名称等于package

会自动扫描包路径下的所有mapper,
configuration中的mapperRegistry会add对应的package路径
将最终mapper对应的MapperProxyFactory 添加到knowMappers中
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 处理mapper子节点
for (XNode child : parent.getChildren()) {
// package子节点
if ("package".equals(child.getName())) {
// 自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
// 扫描指定的包,并向mapperRegistry注册mapper接口
configuration.addMappers(mapperPackage);
} else {

    // 直接指定Mapper属性
    // 获取mapper节点的resource、url、class属性,三个属性互斥
    String resource = child.getStringAttribute("resource");
    String url = child.getStringAttribute("url");
    String mapperClass = child.getStringAttribute("class");
    // 如果mapper节点指定了resource或者url属性,则创建XmlMapperBuilder对象,并通过该对象解析resource或者url属性指定的mapper配置文件
    if (resource != null && url == null && mapperClass == null) {
      // 使用类路径
      ErrorContext.instance().resource(resource);
      try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
        // 创建XMLMapperBuilder对象,解析映射配置文件
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
        mapperParser.parse();
      }
    } else if (resource == null && url != null && mapperClass == null) {
      // 使用绝对url路径
      ErrorContext.instance().resource(url);
      try(InputStream inputStream = Resources.getUrlAsStream(url)){
        // 创建XMLMapperBuilder对象,解析映射配置文件
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
        mapperParser.parse();
      }
    } else if (resource == null && url == null && mapperClass != null) {
      // 如果mapper节点指定了class属性,则向MapperRegistry注册该mapper接口
      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.");
    }
  }
}

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

//MapperRegistry
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
// 查找包下所有父类是 Objects.class 的类
ResolverUtil> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
// MapperRegistry.addMapper()
public void addMapper(Class type) {
……
boolean loadCompleted = false;
// 将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
knownMappers.put(type, new MapperProxyFactory<>(type));

// 对被注解接口的解析逻辑
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在添加完mapper映射关系后,会开始对Mapper接口中相关注解的解析工作

会读取mapper class对应的mapper xml文件, 并且在读取后对文件中的内容进行解析
变量mapper class每一个方法, 读取标识的注解进行解析, 如resultMap
public void parse() {
String resource = type.toString();
// 检测是否已经加载过该接口
if (!configuration.isResourceLoaded(resource)) {
// 检测是否加载过对应的映射配置文件,如果未加载,则创建XMLMapperBuilder对象解析对应的mapper映射xml文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析@CacheNamespace注解
parseCache();
// 解析@CacheNamespaceRef注解
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 {
// 解析@SelectKey,@ResultMap等注解,并创建MappedStatement对象
parseStatement(method);
} catch (IncompleteElementException e) {
// 如果解析过程出现IncompleteElementException异常,可能是引用了未解析的注解,此处将出现异常的方法添加到incompleteMethod集合中保存
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 遍历incompleteMethods集合中记录的未解析的方法,并重新进行解析
parsePendingMethods();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
loadXmlResource方法会调用parse方法对象xml的mapper中内容进行解析。

public void parse() {
// 判断是否已经加载过该映射文件
if (!configuration.isResourceLoaded(resource)) {
// 处理mapper节点
configurationElement(parser.evalNode("/mapper"));
// 将resource添加到Configuration.loadedResources集合中保存,他是hashset类型的集合,其中记录了已经加载过的映射文件
configuration.addLoadedResource(resource);
// 绑定映射器到namespace
bindMapperForNamespace();
}

// 处理ConfigurationElement方法中解析失败的resultMap节点
parsePendingResultMaps();
// 处理ConfigurationElement方法中解析失败的cache-ref节点
parsePendingCacheRefs();
// 处理ConfigurationElement方法中解析失败的SQL语句节点
parsePendingStatements();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
configurationElement方法: 对sql, resultMap, parameterMap等节点进行解析:

private void configurationElement(XNode context) {
try {
// 获取mapper节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置MapperBuilderAssistant的currentNamespace字段,记录当前命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析cache-ref节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析cache节点
cacheElement(context.evalNode("cache"));
// 解析parameterMap节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析resultMap节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析select、update、insert、delete等SQL节点
// 就在这里开始创建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);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
截至目前Configuration 创建完毕, 返回创建好的SqlSessionFactory,我们总结一下:

openSession的过程
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);
//根据获取的执行器创建SqlSession
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();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//interceptorChain生成代理类,具体参见Plugin这个类的方法
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;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Executor分成两大类,一类是CacheExecutor,另一类是普通Executor。
普通Executor又分为三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

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批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
CacheExecutor其实是封装了普通的Executor,和普通的区别是在查询前先会查询缓存中是否存在结果,如果存在就使用缓存中的结果,如果不存在还是使用普通的Executor进行查询,再将查询出来的结果存入缓存。

Configuration概述
Configuration 是整个MyBatis的配置体系集中管理中心,前文所说的Executor、StatementHandler、Cache、MappedStatement…等绝大部分组件都是由它直接或间接的创建和管理。此外影响这些组件行为的属性配置也是由它进行保存和维护。如cacheEnabled、lazyLoadingEnabled … 等。所以说它MyBatis的大管家很形象。

Configuration是MyBatis中极其重要的一个类,它代表MyBatis的全局配置信息。

Configuration中包含了很多关键信息,主要包括:

environments:存储了多个环境信息,每个环境都有transactionFactory和dataSource;
mappers:存储了所有mapper文件的位置信息;
mappedStatements:存储了所有mappedStatement的信息,每个mappedStatement代表一条SQL语句;
resultMaps:存储了所有resultMap的信息,resultMap代表结果集映射;
parameterMap:旧版本使用,已废弃;
keyGenerators:存储了所有keyGenerator的信息,keyGenerator用于生成主键;
typeHandlers:存储了所有typeHandler的信息,typeHandler用于javaType和jdbcType之间的转换;
objectFactory:对象工厂,用于实例化目标对象;
objectWrapperFactory:对象包装工厂,用于给目标对象创建代理对象;
interceptors:存储了所有Interceptor的信息,Interceptor用于拦截器执行SQL;
databaseIdProvider:根据databaseId获取对应的mappedStatement;
logImpl:日志工厂;
cache:缓存命名空间;
等等。
Configuration保存的所有这些信息构成了MyBatis一次会话(SqlSession)的全部配置环境。每开启一个新的 SqlSession 都会根据 Configuration 来构建出 SqlSession 需要的所有信息。

所以简而言之,Configuration存储了MyBatis的全局配置信息,代表了一个MyBatis配置环境,它包含了SQL映射语句、结果集映射、缓存机制等等信息,这些信息构成了一个MyBatis一次会话需要的所有配置内容。

当我们调用SqlSessionFactoryBuilder.build(reader)构建SqlSessionFactory时,底层会根据配置文件来构建一个Configuration对象。

当我们调用openSession()开启一个会话时,SqlSession由一个Configuration以及执行器(Executor)组成,这个Configuration就是前面构建的全局配置信息。

所以可以看出,Configuration对MyBatis来说意义重大,它包含了MyBatis会话所需要的全部配置资源和环境信息。没有Configuration,则根本无法构建SqlSession,也就无法工作了。

Configuration的核心作用与配置来源
Configuration 配置来源有三项:

Mybatis-config.xml 启动文件,全局配置、全局组件都是来源于此。
Mapper.xml SQL映射(MappedStatement) \结果集映射(ResultMapper)都来源于此。
@Annotation SQL映射与结果集映射的另一种表达形式。

然后我们再说说Configuration的核心作用:

存储全局配置信息,其来源于settings(设置)
初始化并维护全局基础组件
typeAliases(类型别名)
typeHandlers(类型处理器)
plugins(插件)
environments(环境配置)
cache(二级缓存空间)
初始化并维护MappedStatement
组件构造器,并基于插件进行增强
newExecutor(执行器)
newStatementHandler(JDBC处理器)
newResultSetHandler(结果集处理器)
newParameterHandler(参数处理器)
这里我们为什么要使用Configuration进行Executor的创建?原因如下:

1、根据配置的类型创建

2、是否开启缓存

3、使用interceptorChain引入插件

这样做到了统一的包装,标准化创建组件。

其他几个组件的创建也是一样:

我们来说说几个组件:

mapperRegistry

用来注册mapper接口,并生成其动态代理对象。
caches

缓存,应用级跨会话的,所有的缓存装载好了之后都会放在Configuration里面。然后mappedStatement会与我们的缓存做一个关联操作,并且他们俩之间是1:1的关系。

TypeAliasRegistry

TypeAliasRegistry是MyBatis中用于保存类型别名的注册中心。
在MyBatis中,我们可以使用标签来为Java类型配置别名,如:


1
这会使User成为com.someapp.model.User类型的别名,在后续映射文件中可以直接使用User来代表那个类型。

那么,MyBatis是如何实现这个类型别名功能的呢?

这就要依赖TypeAliasRegistry了

TypeAliasRegistry中保存了所有的类型别名配置。当我们的SQL映射文件被解析时,MyBatis会查找其中的配置,并将找到的类型别名注册到这个注册中心。
然后,在后续的解析过程中,每当MyBatis遇到一个类型时,它会首先检查TypeAliasRegistry是否存在该类型的别名,如果存在则使用该别名,否则使用全限定名。
所以,TypeAliasRegistry的作用就是用来保存类型别名配置,并让MyBatis在需要时可以查找并使用这些别名。它实现了MyBatis的类型别名功能。

TypeAliasRegistry作为MyBatis的核心组件之一,主要有以下作用:

保存类型别名配置:MyBatis可以在其中注册从映射文件中解析得到的类型别名,以备后续查找和使用。
查找别名:在MyBatis的解析过程中,每当需要一个类型时,MyBatis会首先在TypeAliasRegistry中查找该类型的别名。如果存在,则使用别名,否则使用全限定名。
简化配置:通过类型别名,可以简化MyBatis的映射配置。我们无需在每次使用一个类型时都输入其完整的限定名,只需要使用MyBatis为其配置的别名即可,这提高了配置的可读性和效率。
减少解析次数:每次MyBatis使用一个别名而非全限定名,可以减少一次对那个包的解析,这可以稍微提高MyBatis的解析性能。
配置元素
Configuration 配置信息来源于xml和注解,每个文件和注解都是由若干个配置元素组成,并呈现嵌套关系,总体关系如下图所示:

关于各配置的使用请参见官网给出文档:https://mybatis.org/mybatis-3/zh/configuration.html#properties

元素承载
无论是XML还是我注解这些配置元素最弱都要被转换成JAVA配置属性或对象组件来承载。其对应关系如下:

全配置(config.xml) 由Configuration对象属性承载
sql映射 或@Select 等由MappedStatement对象承载
缓存 或@CacheNamespace 由Cache对象承载
结果集映射 由ResultMap 对象承载

配置文件解析
XML文件解析流程
配置文件解析需要我们分开讨论,首先来分析XML解析过程。xml配置解析其底层使用dom4j先解析成一棵节点树,然后根据不同的节点类型与去匹配不同的解析器。最终解析成特定组件。

解析器的基类是BaseBuilder 其内部包含全局的Configuration 对象,这么做的用意是所有要解析的组件最后都要集中归属至Configuration。接下来了解一下每个解析器的作用:

XMLConfigBuilder :解析config.xml文件,会直接创建一个Configuration对象,用于解析全局配置 。
XMLMapperBuilder :解析Mapper.xml文件,内容包含 等
MapperBuilderAssistant:Mapper.xml解析辅助,在一个Mapper.xml中Cache是对Statement(sql声明)共享的,共享组件的分配即由该解析实现。
XMLStatementBuilder:SQL映射解析 即 元素解析成MapperStatement。
SqlSourceBuilder:Sql数据源解析,将声明的SQL解析可执行的SQL。
XMLScriptBuilder:解析动态SQL数据源当中所设置 SqlNode脚本集。

整体解析流程是从XmlConfigBuilder 开始,然后逐步向内解析,直到解析完所有节点。我们通过一个MappedStatement 解析过程即可了解到其整体解析流程。

流程说明:

【XmlConfigBuilder】 接收一个config.xml 输入流,然后创建一个空Configuration对象

【XmlConfigBuilder】解析全局配置

【XmlConfigBuilder】mapperElements解析,通过Resource或url 指定mapper.xml文件

【XmlMapperBuilder】解析缓存、结果集配置等公共配置
【XmlMapperBuilder】解析Sql映射
【XMLScriptBuilder】解析生成SQL数据源,包括动态脚本
【XmlMapperBuilder】构建Statement
【MapperBuilderAssistant】设置缓存并添加至Configuration
注解配置解析
注解解析底层实现是通过反射获取Mapper接口当中注解元素实现。有两种方式:

一种是直接指定接口名
一种是指定包名然后自动扫描包下所有的接口类
这些逻辑均由Mapper注册器(MapperRegistry)实现。其接收一个接口类参数,并基于该参数创建针对该接口的动态代理工厂,然后解析内部方法注解生成每个MapperStatement 最后添加至Configuration 完成解析。

想了解更多可参考文章:
MyBatis 核心配置综述之 Configuration详解
Mybatis主配置—Configuration
————————————————
版权声明:本文为CSDN博主「十八岁讨厌编程」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zyb18507175502/article/details/131105326

目录
相关文章
|
6天前
|
安全 Java 数据库连接
后端框架的学习----mybatis框架(3、配置解析)
这篇文章详细介绍了MyBatis框架的核心配置文件解析,包括环境配置、属性配置、类型别名设置、映射器注册以及SqlSessionFactory和SqlSession的生命周期和作用域管理。
后端框架的学习----mybatis框架(3、配置解析)
|
3月前
|
SQL 安全 BI
基于jeecg-boot的nbcio-boot因升级mybatis-plus到3.5.3.1和JSQLParser 到4.6而引起的在线报表配置报错处理
基于jeecg-boot的nbcio-boot因升级mybatis-plus到3.5.3.1和JSQLParser 到4.6而引起的在线报表配置报错处理
104 0
|
16天前
|
缓存 Java 数据库连接
mybatis1.常见配置
本文介绍了MyBatis框架中的常见配置及其加载顺序。配置可通过`properties`元素、资源文件或方法参数传递,其中方法参数传递的属性具有最高优先级。文章列举了几个重要的配置项,如`cacheEnabled`用于全局开启或关闭缓存功能;`lazyLoadingEnabled`控制对象的延迟加载行为;`useGeneratedKeys`允许JDBC支持自动生成主键;`defaultExecutorType`设定默认执行器类型等。此外,还介绍了多环境配置方法,通过`environments`元素可定义不同环境下的数据库连接信息,并可根据需求动态选择加载特定环境
|
23天前
|
SQL Java 数据库连接
idea中配置mybatis 映射文件模版及 mybatis plus 自定义sql
idea中配置mybatis 映射文件模版及 mybatis plus 自定义sql
38 3
|
2月前
|
SQL 人工智能 Java
mybatis-plus配置sql拦截器实现完整sql打印
_shigen_ 博主分享了如何在MyBatis-Plus中打印完整SQL,包括更新和查询操作。默认日志打印的SQL用?代替参数,但通过自定义`SqlInterceptor`可以显示详细信息。代码示例展示了拦截器如何替换?以显示实际参数,并计算执行时间。配置中添加拦截器以启用此功能。文章提到了分页查询时的限制,以及对AI在编程辅助方面的思考。
142 5
mybatis-plus配置sql拦截器实现完整sql打印
|
1月前
|
Java 数据库连接 mybatis
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
|
2月前
|
XML 前端开发 Java
Mybatis-Plus乐观锁配置
Mybatis-Plus乐观锁配置
27 1
|
2月前
|
Java Spring
mybatisplus的typeAliasesPackage 配置
【6月更文挑战第20天】mybatisplus的typeAliasesPackage 配置
222 3
若依修改,集成mybatisplus报错,若依集成mybatisplus,总是找不到映射是怎么回事只要是用mp的方法就找报,改成mybatisPlus配置一定要改
若依修改,集成mybatisplus报错,若依集成mybatisplus,总是找不到映射是怎么回事只要是用mp的方法就找报,改成mybatisPlus配置一定要改
接口模板,文本常用的接口Controller层,常用的controller层模板,Mybatisplus的相关配置
接口模板,文本常用的接口Controller层,常用的controller层模板,Mybatisplus的相关配置