Appearance
接下来研究第二行代码,探究mybatis获取到配置文件输入流后,是如何进行解析并转换为可操作的javaBean对象。
剖析build方法是如何解析配置文件的
java
// 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
接下来分析第二行代码,第二行代码的第一个功能就是将配置文件转换为全局的Configuration对象,这个对象记录全局的一些配置信息;下面是整体的类图,配合后面的解析进行
点击展开Configuration.java
java
public class Configuration {
protected Environment environment;
// 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。默认为false
protected boolean safeRowBoundsEnabled;
// 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false
protected boolean safeResultHandlerEnabled = true;
// 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN
// 到经典 Java 属性名 aColumn 的类似映射。默认false
protected boolean mapUnderscoreToCamelCase;
// 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值false (true in ≤3.4.1)
protected boolean aggressiveLazyLoading;
// 是否允许单一语句返回多结果集(需要兼容驱动)。
protected boolean multipleResultSetsEnabled = true;
// 允许 JDBC 支持自动生成主键,需要驱动兼容。这就是insert时获取mysql自增主键/oracle sequence的开关。
// 注:一般来说,这是希望的结果,应该默认值为true比较合适。
protected boolean useGeneratedKeys;
// 使用列标签代替列名,一般来说,这是希望的结果
protected boolean useColumnLabel = true;
// 是否启用缓存
protected boolean cacheEnabled = true;
// 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,
// 这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。
protected boolean callSettersOnNulls;
// 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,
// 并且加上-parameters选项。(从3.4.1开始)
protected boolean useActualParamName = true;
//当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。
// 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始)
// 注:这里应该拆分为两个参数比较合适, 一个用于结果集,一个用于单记录。
// 通常来说,我们会希望结果集不是null,单记录仍然是null
protected boolean returnInstanceForEmptyRow;
protected boolean shrinkWhitespacesInSql;
// 指定 MyBatis 增加到日志名称的前缀。
protected String logPrefix;
// 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。一般建议指定为slf4j或log4j
protected Class<? extends Log> logImpl;
// 指定VFS的实现, VFS是mybatis提供的用于访问AS内资源的一个简便接口
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
// MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
// 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
// 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,
// 多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// 指定对象的哪个方法触发一次延迟加载。
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
// 设置超时时间,它决定驱动等待数据库响应的秒数。默认不超时
protected Integer defaultStatementTimeout;
// 为驱动的结果集设置默认获取数量。
protected Integer defaultFetchSize;
// SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);
// BATCH 执行器将重用语句并执行批量更新。
protected ResultSetType defaultResultSetType;
// 默认执行器类型
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 指定 MyBatis 应如何自动映射列到字段或属性。
// NONE 表示取消自动映射;
// PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
// FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// 指定发现自动映射目标未知列(或者未知属性类型)的行为。这个值应该设置为WARNING比较合适
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
// settings下的properties属性
protected Properties variables = new Properties();
// 默认的反射器工厂,用于操作属性、构造器方便
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// 对象工厂, 所有的类resultMap类都需要依赖于对象工厂来实例化
protected ObjectFactory objectFactory = new DefaultObjectFactory();
// 对象包装器工厂,主要用来在创建非原生对象,比如增加了某些监控或者特殊属性的代理类
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态
protected boolean lazyLoadingEnabled = false;
// 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。MyBatis 3.3+使用JAVASSIST
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
// MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// mybatis插件列表
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
// 类型注册器, 用于在执行sql语句的出入参映射以及mybatis-config文件里的各种配置
// 比如<transactionManager type="JDBC"/><dataSource type="POOLED">时使用简写
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<>();
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
public Configuration(Environment environment) {
this();
this.environment = environment;
}
}
build方法
java
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// 主要逻辑
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 1.创建XPathParser解析器对象,根据inputStream解析成了document对象 2.创建全局配置对象Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse():使用XPATH解析XML配置文件,将配置文件封装到Configuration对象
// 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
// parse():配置文件就解析完成了
return build(parser.parse());
}
}
}
XMLConfigBuilder构造函数
java
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed; // 是否解析过了
private final XPathParser parser; // 存储的核心数据
private String environment; // 数据库环境配置有多个,记录当前环境是哪一个(xml文件environments的default属性内容)
// 上一段代码先调用这个方法
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// XPathParser基于 Java XPath 解析器,用于解析 MyBatis中的配置文件
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// 然后上一行调用这个构造函数
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 创建Configuration对象,并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
第15行调用的new Configuration()会进行别名注册,这就是为什么配置文件填写JDBC就能找到类的原因。
java
public Configuration() {
//TypeAliasRegistry(类型别名注册器)
// 注册事务工厂的别名
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);
}
XPathParser解析XML文档为Document对象
java
public class XPathParser {
private final Document document; // 第一步解析的内容存放到这里,这里是sqlMapConfig.xml文件的内容
private boolean validation;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
// 解析XML文档为Document对象
this.document = createDocument(new InputSource(inputStream));
}
}
createDocument解析XML文档为Document对象
java
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 进行dtd或者Schema校验
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
// 设置忽略注释为true
factory.setIgnoringComments(true);
// 设置是否忽略元素内容中的空白
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
// NOP
}
});
// 通过dom解析,获取Document对象
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
parse()方法解析配置文件
第一步是判断是否解析过了,如果解析过抛出提示异常;parseConfiguration
方法从根节点开始解析,并封装到
java
public class XMLConfigBuilder extends BaseBuilder {
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// parser.evalNode("/configuration"):通过XPATH解析器,解析configuration根节点
// 从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
}
parser.evalNode
返回解析后的XNode
对象,通过xNode对象可以更方便的遍历获取xml的内容;再来看parseConfiguration方法,他就是用来解析标签,并根据标签内容进行对应初始化操作。
java
public class XMLConfigBuilder extends BaseBuilder {
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
// 解析</properties>标签
propertiesElement(root.evalNode("properties"));
// 解析</settings>标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 解析</typeAliases>标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析</plugins>标签
pluginElement(root.evalNode("plugins"));
// 解析</objectFactory>标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析</objectWrapperFactory>标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析</reflectorFactory>标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析</environments>标签
environmentsElement(root.evalNode("environments"));
// 解析</databaseIdProvider>标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析</typeHandlers>标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析</mappers>标签 加载映射文件流程主入口
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
剖析environments解析过程
配置文件关于environments配置
xml
<environments default="development" >
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url"
value="jdbc:mysql:///qq-card?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
该方法主要用于解析<environments>
节点,获取指定的环境配置信息,并创建对应的事务工厂和数据源对象,最后将Environment对象设置到Configuration中,用于后续的数据库操作。
- 首先判断是否存在
<environments>
节点,如果存在则进行解析,否则跳过。 - 如果未指定environment参数,则从
<environments>
节点的"default"属性中获取默认的环境名称。 - 遍历
<environments>
节点的子节点,即<environment>
节点。 - 对每个
<environment>
节点,获取其"id"属性值,用于指定具体的环境。 - 判断当前解析的环境是否与指定的环境相匹配,如果匹配则继续解析,否则跳过。
- 解析
<transactionManager>
节点,创建对应的事务工厂对象。 - 解析
<dataSource>
节点,创建对应的数据源对象。 - 使用解析得到的事务工厂对象和数据源对象构建Environment.Builder对象。
- 将构建的Environment对象设置到Configuration中。
java
public class XMLConfigBuilder extends BaseBuilder {
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 方法参数如果没有传递environment,则解析sqlMapConfig.xml中的
if (environment == null) {
// 获取default属性值,用于指定默认的环境
environment = context.getStringAttribute("default");
}
// 遍历解析environment节点
for (XNode child : context.getChildren()) {
// 获得id属性值,用于指定具体的环境
String id = child.getStringAttribute("id");
// 判断id和environment值是否相等,用于确定是否是指定的环境
if (isSpecifiedEnvironment(id)) {
// 解析<transactionManager>节点,创建对应的事务工厂对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析<dataSource>节点,创建对应的数据源对象
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建Environment.Builder对象,用于构建Environment对象
Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);
// 将创建的Environment对象设置到Configuration中
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
}
剖析mappers解析过程
配置文件关于mappers配置,使用不同的方式,代码执行流程也不一样
xml
<mappers>
<mapper resource="com/xk857/mapper/UserMapper.xml"></mapper>
<!--<mapper class="com.xk857.mapper.UserMapper"></mapper>-->
<!-- <package name="com.xk857.mapper"/>-->
</mappers>
该方法主要用于解析<mappers>
节点,获取mapper接口和mapper映射文件的配置信息,并将其添加到Configuration对象中,用于后续的数据库操作。
- 首先判断是否存在
<mappers>
节点,如果存在则进行解析,否则跳过。 - 遍历
<mappers>
节点的子节点。 - 如果子节点是
<package>
标签,获取其"name"属性值,表示mapper接口和mapper映射文件对应的package包名。- 将指定包下所有的mapper接口以及它的代理工厂对象存储到一个Map集合中。
- 如果子节点是
<mapper>
标签,获取其"resource"、"url"和"class"属性值。- 如果只指定了"resource"属性,则根据该属性值获取对应的输入流,创建XMLMapperBuilder对象,用于解析mapper映射文件。
- 如果只指定了"url"属性,则根据该属性值获取对应的输入流,创建XMLMapperBuilder对象,用于解析mapper映射文件。
- 如果只指定了"class"属性,则根据该属性值获取mapper接口类型,并将该接口以及其代理对象存储到一个Map集合中。
- 如果同时指定了多个属性,则抛出异常。
java
public class XMLConfigBuilder extends BaseBuilder {
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历<mappers>节点的子节点
for (XNode child : parent.getChildren()) {
// 如果子节点是<package>标签
if ("package".equals(child.getName())) {
// 获取mapper接口和mapper映射文件对应的package包名
String mapperPackage = child.getStringAttribute("name");
// 将包下所有的mapper接口以及它的代理工厂对象存储到一个Map集合中
configuration.addMappers(mapperPackage);
} else if ("<mapper>标签") {
// 获取<mapper>子节点的resource属性
String resource = child.getStringAttribute("resource");
// 获取<mapper>子节点的url属性
String url = child.getStringAttribute("url");
// 获取<mapper>子节点的class属性
String mapperClass = child.getStringAttribute("class");
// 根据不同属性进行解析
if (resource != null && url == null && mapperClass == null) {
// 根据resource属性获取输入流
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
// 创建XMLMapperBuilder对象,用于解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析mapper映射文件
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
// 根据url属性获取输入流
try (InputStream inputStream = Resources.getUrlAsStream(url)) {
// 创建XMLMapperBuilder对象,用于解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
// 解析mapper映射文件
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
// 根据class属性获取mapper接口类型
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 将指定mapper接口以及它的代理对象存储到一个Map集合中
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
}