mybatis文档
mybatis-config.xml
配置文件以及映射配置文件提供支持。解析占位符:
|
eval 元素的方法,用于获得 Boolean、Short、Integer、Long、Float、Double、String 类型的元素的值。
org.apache.ibatis.builder.xml.XMLMapperEntityResolver实现 EntityResolver 接口,MyBatis 自定义 EntityResolver 实现类,用于加载本地的 mybatis-3-config.dtd 和 mybatis-3-mapper.dtd这两个 DTD 文件。
org.apache.ibatis.reflection.Reflector,反射器,每个 Reflector 对应一个类。Reflector 会缓存反射操作需要的类的信息,例如:构造方法、属性名、setting / getting 方法等等。
org.apache.ibatis.exceptions.IbatisException
,实现 RuntimeException 类,IBatis 的异常基类,已经被废弃
org.apache.ibatis.exceptions.PersistenceException
,继承 IbatisException 类,目前 MyBatis 真正的异常基类。
org.apache.ibatis.parsing.ParsingException
,继承 PersistenceException 类,解析异常。org.apache.ibatis.exceptions.TooManyResultsException
,继承 PersistenceException 类,查询返回过多结果的异常。期望返回一条,实际返回了多条。
数据源是实际开发中常用的组件之一。现在开源的数据源都提供了比较丰富的功能,例如,连接池功能、检测连接状态等,选择性能优秀的数据源组件对于提升 ORM 框架乃至整个应用的性能都是非常重要的。
MyBatis 自身提供了相应的数据源实现,当然 MyBatis 也提供了与第三方数据源集成的接口,这些功能都位于数据源模块之中。
org.apache.ibatis.datasource.DataSourceFactory
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
,实现 DataSourceFactory 接口,非池化的 DataSourceFactory 实现类。
UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。 不同的数据库在性能方面的表现也是不一样的,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:
driver
– 这是 JDBC 驱动的 Java 类的完全限定名(并不是 JDBC 驱动中可能包含的数据源类)。url
– 这是数据库的 JDBC URL 地址。username
– 登录数据库的用户名。password
– 登录数据库的密码。defaultTransactionIsolationLevel
– 默认的连接事务隔离级别。org.apache.ibatis.datasource.pooled.PooledDataSourceFactory,
继承了UnpooledDataSourceFactory
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
poolMaximumActiveConnections
– 在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10poolMaximumIdleConnections
– 任意时间可能存在的空闲连接数。poolMaximumCheckoutTime
– 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)poolTimeToWait
– 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)。poolMaximumLocalBadConnectionTolerance
– 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程. 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections
与 poolMaximumLocalBadConnectionTolerance
之和。 默认值:3 (新增于 3.4.5)poolPingQuery
– 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息。poolPingEnabled
– 是否启用侦测查询。若开启,需要设置 poolPingQuery
属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。poolPingConnectionsNotUsedFor
– 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
,实现 DataSourceFactory 接口,基于 JNDI 的 DataSourceFactory 实现类
JNDI – 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。这种数据源配置只需要两个属性:
initial_context
– 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找。data_source
– 这是引用数据源实例位置的上下文的路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给初始上下文。比如:
env.encoding=UTF8
javax.sql.DataSource
是个神奇的接口,在其上可以衍生出数据连接池、分库分表、读写分离等等功能。
org.apache.ibatis.datasource.unpooled.UnpooledDataSource
org.apache.ibatis.datasource.pooled.PooledDataSource
,实现 DataSource 接口,池化的 DataSource 实现类。
popConnection(String username, String password)
方法,获取 PooledConnection 对象。
pushConnection(PooledConnection conn)
方法,将使用完的连接,添加回连接池中。
pingConnection(PooledConnection conn)
方法,通过向数据库发起 poolPingQuery
语句来发起“ping”操作,以判断数据库连接是否有效。
org.apache.ibatis.transaction.Transaction
,事务接口。org.apache.ibatis.transaction.jdbc.JdbcTransaction
,实现 Transaction 接口,基于 JDBC 的事务实现类。
org.apache.ibatis.transaction.managed.ManagedTransaction
,实现 Transaction 接口,基于容器管理的事务实现类。
org.apache.ibatis.transaction.TransactionFactory
,Transaction 工厂接口。
org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory
,实现 TransactionFactory 接口,JdbcTransaction 工厂实现类。
org.apache.ibatis.transaction.managed.ManagedTransactionFactory
,实现 TransactionFactory 接口,ManagedTransaction 工厂实现类。
org.apache.ibatis.cache.Cache
,缓存容器接口。注意,它是一个容器,有点类似HashMap,可以往其中添加各种缓存。org.apache.ibatis.cache.impl.PerpetualCache,实现 Cache 接口,永不过期的 Cache 实现类,基于 HashMap 实现类。
org.apache.ibatis.cache.decorators.LoggingCache
,实现 Cache 接口,支持打印日志的 Cache 实现类。org.apache.ibatis.cache.decoratorsBlockingCache
,实现 Cache 接口,阻塞的 Cache 实现类。
org.apache.ibatis.type.TypeHandler
,类型转换处理器。
setParameter(...)
方法,是 Java Type => JDBC Type
的过程。
#getResult(...)
方法,是 JDBC Type => Java Type
的过程。
-------------------------------------------------------------------------------------------------
org.apache.ibatis.type.BaseTypeHandler
,实现 TypeHandler 接口,继承 TypeReference 抽象类,TypeHandler 基础抽象类。
TypeHandler 有非常多的子类,当然所有子类都是继承自 BaseTypeHandler 抽象类。
(8)IO 模块
资源加载模块,主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能 。
org.apache.ibatis.io.ClassLoaderWrapper
,ClassLoader 包装器。可使用多个 ClassLoader 加载对应的资源,直到有一成功后返回资源。
getResourceAsStream(String resource, ...)
方法,获得指定资源的 InputStream 对象。
org.apache.ibatis.io.Resources
,Resource 工具类。
etResourceAsStream(String resource)
静态方法,获得指定资源的 InputStream 。
getResourceAsReader(String resource)
静态方法,获得指定资源的 Reader 。
Log4j、 Log4j2、Slf4j 等。
demo:
在调用 SqlSession 相应方法执行数据库操作时,需要指定映射文件中定义的 SQL 节点,如果出现拼写错误,我们只能在运行时才能发现相应的异常。为了尽早发现这种错误,MyBatis 通过 Binding 模块,将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。
值得读者注意的是,开发人员无须编写自定义 Mapper 接口的实现,MyBatis 会自动为其创建动态代理对象。在有些场景中,自定义 Mapper 接口可以完全代替映射配置文件,但有的映射规则和 SQL 语句的定义还是写在映射配置文件中比较方便,例如动态 SQL 语句的定义。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
MyBatis 的初始化流程的入口是 SqlSessionFactoryBuilder
1加载mybatis.conf
InputStream stream = Resources.getResourceAsStream("");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
}
BaseBuilder 是 XMLConfigBuilder 的父类,并且它还有其他的子类。
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析configuration节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
后调用 parseConfiguration(XNode root)
方法
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(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 #631
environmentsElement(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);
}
}
2.加载Mapper映射配置文件
XMLMapperBuilder.java
public void parse() {
// <1> 判断当前 Mapper 是否已经加载过
if (!configuration.isResourceLoaded(resource)) {
// <2> 解析 ` ` 节点
configurationElement(parser.evalNode("/mapper"));
// <3> 标记该 Mapper 已经加载过
configuration.addLoadedResource(resource);
// <4> 绑定 Mapper
bindMapperForNamespace();
}
// <5> 解析待定的 节点
parsePendingResultMaps();
// <6> 解析待定的 节点
parsePendingCacheRefs();
// <7> 解析待定的 SQL 语句的节点
parsePendingStatements();
}
<2>configurationElement(XNode context)
方法,解析
节点。
// XMLMapperBuilder.java
private void configurationElement(XNode context) {
try {
// <1> 获得 namespace 属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// <1> 设置 namespace 属性
builderAssistant.setCurrentNamespace(namespace);
// <2> 解析 节点
cacheRefElement(context.evalNode("cache-ref"));
// <3> 解析 节点
cacheElement(context.evalNode("cache"));
// 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// <4> 解析 节点们
resultMapElements(context.evalNodes("/mapper/resultMap"));
// <5> 解析 节点们
sqlElement(context.evalNodes("/mapper/sql"));
// <6> 解析 节点们
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);
}
}
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
private void cacheElement(XNode context) throws Exception {
if (context != null) {
// <1> 获得负责存储的 Cache 实现类
String type = context.getStringAttribute("type", "PERPETUAL");
Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// <2> 获得负责过期的 Cache 实现类
String eviction = context.getStringAttribute("eviction", "LRU");
Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// <3> 获得 flushInterval、size、readWrite、blocking 属性
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
// <4> 获得 Properties 属性
Properties props = context.getChildrenAsProperties();
// <5> 创建 Cache 对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
3解析resultMap
org.apache.ibatis.builder.ResultMapResolver
,ResultMap 解析器。
4解析sql标签
private void sqlElement(List list) throws Exception {
if(this.configuration.getDatabaseId() != null) {
this.sqlElement(list, this.configuration.getDatabaseId());
}
this.sqlElement(list, (String)null);
}
private void sqlElement(List list, String requiredDatabaseId) throws Exception {、
//遍历所有的sql节点
Iterator i$ = list.iterator();
while(i$.hasNext()) {
XNode context = (XNode)i$.next();
// 获得 databaseId 属性
String databaseId = context.getStringAttribute("databaseId");
//获得完整的 id 属性,格式为 `${namespace}.${id}`
String id = context.getStringAttribute("id");
id = this.builderAssistant.applyCurrentNamespace(id, false);
//判断 databaseId 是否匹配
if(this.databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//添加到 sqlFragments 中
this.sqlFragments.put(id, context);
}
}
}
5.buildStatementFromContext(List
方法,解析 、
、
、
节点们。
private void buildStatementFromContext(List list) {
if(this.configuration.getDatabaseId() != null) {
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
// <1> 遍历 节点们
for (XNode context : list) {
// <1> 创建 XMLStatementBuilder 对象,执行解析
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// <2> 解析失败,添加到 configuration 中
configuration.addIncompleteStatement(statementParser);
}
}
}
(1)遍历 、
、
、
节点们,逐个创建 XMLStatementBuilder 对象,执行解析。
(2) 处,解析失败,调用 Configuration 的addIncompleteStatement(XMLStatementBuilder incompleteStatement)
方法,添加到 configuration
中。
bindMapperForNamespace()
方法,绑定 Mapper 。
private void bindMapperForNamespace() {
//获得 Mapper 映射配置文件对应的 Mapper 接口,实际上类名就是 namespace 。
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();
this.parsePendingChacheRefs();
this.parsePendingStatements();
思路一样的:1)获得对应的集合;2)遍历集合,执行解析;3)执行成功,则移除出集合;4)执行失败,忽略异常。
org.apache.ibatis.builder.MapperBuilderAssistant
,继承 BaseBuilder 抽象类,Mapper 构造器的小助手,提供了一些公用的方法,例如创建 ParameterMap、MappedStatement 对象等等。
org.apache.ibatis.builder.xml.XMLStatementBuilder
,继承 BaseBuilder 抽象类,Statement XML 配置构建器,主要负责解析 Statement 配置,即 、
、
、
标签。
private void buildStatementFromContext(List list, String requiredDatabaseId) {
Iterator i$ = list.iterator();
while(i$.hasNext()) {
XNode context = (XNode)i$.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
// XMLStatementBuilder.java
public void parseStatementNode() {
// <1> 获得 id 属性,编号。
String id = context.getStringAttribute("id");
// <2> 获得 databaseId , 判断 databaseId 是否匹配
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// <3> 获得各种属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
// <4> 获得 lang 对应的 LanguageDriver 对象
LanguageDriver langDriver = getLanguageDriver(lang);
// <5> 获得 resultType 对应的类
Class> resultTypeClass = resolveClass(resultType);
// <6> 获得 resultSet 对应的枚举值
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// <7> 获得 statementType 对应的枚举值
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// <8> 获得 SQL 对应的 SqlCommandType 枚举值
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// <9> 获得各种属性
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 parsing
// <10> 创建 XMLIncludeTransformer 对象,并替换 标签相关的内容
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// <11> 解析 标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
// <12> 创建 SqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// <13> 获得 KeyGenerator 对象
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
// <13.1> 优先,从 configuration 中获得 KeyGenerator 对象。如果存在,意味着是 标签配置的
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
// <13.2> 其次,根据标签属性的情况,判断是否使用对应的 Jdbc3KeyGenerator 或者 NoKeyGenerator 对象
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys", // 优先,基于 useGeneratedKeys 属性判断
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) // 其次,基于全局的 useGeneratedKeys 配置 + 是否为插入语句类型
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 创建 MappedStatement 对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
org.apache.ibatis.builder.xml.XMLIncludeTransformer
,XML
标签的转换器,负责将 SQL 中的
标签转换成对应的
的内容。
|
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
,Mapper 注解构造器,负责解析 Mapper 接口上的注解。
// MapperAnnotationBuilder.java
public void parse() {
// <1> 判断当前 Mapper 接口是否应加载过。
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// <2> 加载对应的 XML Mapper
loadXmlResource();
// <3> 标记该 Mapper 接口已经加载过
configuration.addLoadedResource(resource);
// <4> 设置 namespace 属性
assistant.setCurrentNamespace(type.getName());
// <5> 解析 @CacheNamespace 注解
parseCache();
// <6> 解析 @CacheNamespaceRef 注解
parseCacheRef();
// <7> 遍历每个方法,解析其上的注解
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
// <7.1> 执行解析
parseStatement(method);
}
} catch (IncompleteElementException e) {
// <7.2> 解析失败,添加到 configuration 中
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// <8> 解析待定的方法
parsePendingMethods();
}
loadXmlResource()
方法,加载对应的 XML Mapper
parseStatement(Method method)
方法,解析方法上的 SQL 操作相关的注解。