源码解读第一步我觉着应该从Mybatis如何解析配置文件开始。

1.先不看跟Spring集成如何解析,先看从SqlSessionFactoryBuilder如果解析的。

 String resouce = "conf.xml";
InputStream is = Resources.getResourceAsStream(resouce); // 构建sqlSession工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

SqlSessionFactoryBuilder

   public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
} public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
} public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
//上面那么多同名不同参的方法最后都会进入这个方法
   //支持传入Enviromment.properties实际上是支持动态传入覆盖全局配置xml的内容
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //因为从下面的Build方法可以看出 parser.parse()后Configuration就初始化好了
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
   public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
   }

真正初始化Configuration的类是XMLConfigBuilder

   public Configuration parse() {
    //这一块可以看出来 全局Configuration只会初始化一次,实例化时候是false
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//获取配置文件整个/configuration节点内容
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings")); //初始化配置文件中全局变量
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));//初始化xml中的配置文件,同时讲其中的变量保存起来,因为可能其他地方引用
loadCustomVfs(settings); //加载自定义的VFS实现类? 这个什么用?
typeAliasesElement(root.evalNode("typeAliases")); //加载typeAliases别名初始化
pluginElement(root.evalNode("plugins")); //加载插件,实际上就是拦截器
objectFactoryElement(root.evalNode("objectFactory"));// //加载自定义的对象工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //加载自定义的处理驼峰方式的key的处理器
reflectionFactoryElement(root.evalNode("reflectionFactory")); //加载自定义的反射器 没用过?
settingsElement(settings); //将setting的属性,设置到Configuration对象属性中
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers")); //初始化类型处理器
mapperElement(root.evalNode("mappers")); //处理mappers节点内容,实际上就是初始化MaperStatement
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

因为大部分方法都比较简单,我这里只介绍几个我认为比较重要的。

① typeAliasesElement(root.evalNode("typeAliases")); //加载typeAliases别名初始化

   private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) { //配置的是包名 扫描包里所有的类。放入的key默认是注解的key
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);
}
         //实际上就是存在了TypeAliasRegistry类的一个私有map里面。
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
 public class TypeAliasRegistry {

   private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
  。。。。。。
}

② 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 这里resolveClass实际上就是到TypeAliasResitstry里面找一下,找到了获取class,没有直接用class去反射回去对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
       //获取对象后调用configuration方法。
configuration.addInterceptor(interceptorInstance);
}
}
}

Configuration

  public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}

InterceptorChain

   public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}

这一块就是放Configuration的拦截器链里面添加拦截器。这一块现在知道是在这时候添加的就好了。后面介绍Mybatis的拦截器的时候深入了解。

③ mapperElement(root.evalNode("mappers")); //处理mappers节点内容,实际上就是初始化MaperStatement

   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");
        //xml文件中mapper节点,配置resource,url是执行的mapper.xml文件的位置,所以现在只分析这一块。
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
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);
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.");
}
}
}
}
}

可以看出来resource和url都是通过XMLMapperBuilder来解析的。下面来看下parse方法。

   private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
//前面还有个构造器,是根据传入的inputstream构造XPathParser,parser可以拿到resouce里面的内容
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
} public void parse() {
if (!configuration.isResourceLoaded(resource)) { 是否加载过改文件,
configurationElement(parser.evalNode("/mapper")); 来解析mapper文件内容
configuration.addLoadedResource(resource); 添加加载记录
bindMapperForNamespace();往configration添加命名空间的代理对象
} parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
   private void configurationElement(XNode context) { //解析mapper.xml子节点的内容
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
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")); //解析resultMap 很复杂,后面单独解读
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete")); //初始化这几个类型节点的内容
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
   private void buildStatementFromContext(List<XNode> list) {
因为会有多个节点,所以是一个List<XNode>
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
} private void buildStatementFromContext(List<XNode> 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);
}
}
}

从上面可以看出来最终是由XMLStatementBuilder来解析我们的写的sql部分。

   public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
} 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");
LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 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 parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
//根据前面的语言驱动去获取对应的SqlSource。SqlSource有两种,一种是处理${}一种是处理#{}
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
53   获取节点所有属性内容,调用builderAssistant,实现是MapperBuilderAssistant 在XmlMapperBuilder构造器初始化时候就制定了
54 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
55 fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
56 resultSetTypeEnum, flushCache, useCache, resultOrdered,
57 keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
   public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) { if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} id = applyCurrentNamespace(id, false); //把id加上namespace+"."
boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resulSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
} MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
MappedStatement.Builder 是MappedStatement的内部类。里面有MappedStatement的引用,所有方法都设置内部引用mappedStatement的属性,并返回自身,所以这一块就是类似于setparam()
最终通过build方法 返回对象。 然后调用Congifuration.addMappedStatement保存到Congifuration对象里面。
   public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}

到此Congifuration对象初始话完事了...  全局只有一个。

回过头来刚才还有怎么获取的语言驱动,和MappedStatement的SqlSource怎么初始化的没说。

①获取语言驱动:

16     String lang = context.getStringAttribute("lang");
17 LanguageDriver langDriver = getLanguageDriver(lang);
   private LanguageDriver getLanguageDriver(String lang) {
Class<?> langClass = null;
if (lang != null) {
     // 也是到别名注册器里面去找
langClass = resolveClass(lang);
}
构造助手类去获取的。下面看MapperBuilderAssistant类怎么实现的
return builderAssistant.getLanguageDriver(langClass);
}
   // 从configuration里面找,也有个语言注册器,然后还有个默认的,我们平时不指定的时候都是默认的。 
  public LanguageDriver getLanguageDriver(Class<?> langClass) {
if (langClass != null) {
configuration.getLanguageRegistry().register(langClass);
} else {
      
langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
}
return configuration.getLanguageRegistry().getDriver(langClass);
}

那语言注册器是什么时候注册的呢。 来看Configuration的构造器

   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);
//在最下面 看到了吗。默认的是XMLLanguageDruiver,然后把Raw也注册进去了
31 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
32 languageRegistry.register(RawLanguageDriver.class);
}

②MappedStatement的SqlSource怎么初始化

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
因为我们一般都不配置lang属性,所以看默认的XMLLanguageDriver怎么实现的。
   @Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
XMLScriptBuilder 
   public SqlSource parseScriptNode() {
//主要在这里面判断是否有Dynamic标签 是就是就是${}
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
   List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
//获取每一个node,然后判断是否是isDynamic();
TextSqlNode textSqlNode = new TextSqlNode(data);
9 if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
   public boolean isDynamic() {
//这一块就是典型的面向接口编程, checker接口的实现类有很多, 在parser.parse一定会调用checker实现类的方法。
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
} private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
 public String parse(String text) {
final StringBuilder builder = new StringBuilder();
final StringBuilder expression = new StringBuilder();
if (text != null && text.length() > 0) {
char[] src = text.toCharArray();
int offset = 0;
// search open token
int start = text.indexOf(openToken, offset);
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
expression.setLength(0);
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//这一块实际上就是遍历node文本找到openToken和cloaseToken中间的变量名字传给handler处理
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
}
return builder.toString();
}

下面看下DynamicCheckerTokenParser怎么处理?

   private static class DynamicCheckerTokenParser implements TokenHandler {

     private boolean isDynamic;

     public DynamicCheckerTokenParser() {
// Prevent Synthetic Access
} public boolean isDynamic() {
return isDynamic;
}
  //实现上什么也没做,因为内部类就把父类的变量设置为true,告诉父类这个Node含有DynamicTag就好了。
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
}

这样就好了,如果有标签SqlSource就是DynamicSqlSourcr() 没有就是RawSqlSource():

这里在额外讲一下,TextSqlNode还有一个内部类BindingTokenParser也实现了Tokenhandler接口看下他是怎么实现的,就提前知道了参数值是怎么具体获取的。

   private static class BindingTokenParser implements TokenHandler {

     private DynamicContext context;
private Pattern injectionFilter; public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
this.context = context;
this.injectionFilter = injectionFilter;
} @Override
public String handleToken(String content) {
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
//根据传入的key到parameter里面找 这样就把变量替换掉了。 后面讲sql执行过程再详细讲解
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
checkInjection(srtValue);
return srtValue;
} private void checkInjection(String value) {
if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
}
}
}

Mybatis之Configuration初始化(配置文件.xml的解析)的更多相关文章

  1. MyBatis总结四:配置文件xml详解

    XML 映射配置文件 MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息.文档的顶层结构如下: configuration 配置 ...

  2. Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例

    在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...

  3. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  4. mybatis 的 dao 接口跟 xml 文件里面的 sql 是如何建立关系的?一步步解析

    序言 在开始正文之前,首先解释Dao接口和XML文件里的SQL是如何一一对应的? 一句话讲完就是:mybatis 会先解析这些xml 文件,通过 xml 文件里面的命名空间 (namespace)跟d ...

  5. Mybatis系列全解(四):全网最全!Mybatis配置文件XML全貌详解

    封面:洛小汐 作者:潘潘 做大事和做小事的难度是一样的.两者都会消耗你的时间和精力,所以如果决心做事,就要做大事,要确保你的梦想值得追求,未来的收获可以配得上你的努力. 前言 上一篇文章 <My ...

  6. 《手写Mybatis》第4章:Mapper XML的解析和注册使用

    作者:小傅哥 系列:https://bugstack.cn/md/spring/develop-mybatis/2022-03-20-%E7%AC%AC1%E7%AB%A0%EF%BC%9A%E5%B ...

  7. Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)

    在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...

  8. spring+mybaits xml配置解析----转

    一.项目中spring+mybaits xml配置解析 一般我们会在datasource.xml中进行如下配置,但是其中每个配置项原理和用途是什么,并不是那么清楚,如果不清楚的话,在使用时候就很有可能 ...

  9. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

随机推荐

  1. hydra nodejs 微服务框架简单试用

    hydra 是一个以来redis 的nodejs 微服务框架 安装 需要redis,使用docker 进行运行 redis docker run -d -p 6379:6379 redis 安装yo ...

  2. 为什么 Windows 10 无论怎么设置一分钟后就自动关屏幕?

    为什么 Windows 10 无论怎么设置一分钟后就自动关屏幕? 在设置中设置了很多方法,但不管怎么设置就是不行,不到一分钟一定关屏幕. 开始以为是能源之星引起,查了相关资料说不是,那个能源之星标志只 ...

  3. 什么是HBase(二) 关于HFile分割

    关于HFile的分割,是首先要从HFile的合并说起,上回书讲到memstore会不定期刷HFile,然后这些HFile将会被不定过期的被监控程序进行小合并+大合并(所有的文件,不分column fa ...

  4. CenOS中的yum配置文件CentOS-Base.repo里面的参数都是何含义? souhu CentOS-Base.repo

    souhu  yum服务器CentOS-Base.repo 将$releasever替换为操作系统版本号 # CentOS-Base.repo # # The mirror system uses t ...

  5. fdisk用法(转载)

    Linux下的fdisk功能是极其强大的,用它可以划分出最复杂的分区,下面简要介绍一下它的用法: 对于IDE硬盘,每块盘有一个设备名:对应于主板的四个IDE接口,设备名依次为:/dev/hda,/de ...

  6. Bootstrap的介绍和响应式媒体查询

    Bootstrap的介绍 凡是使用过Bootstrap的开发者,都不在乎做这么两件事情:复制and粘贴.哈哈~,是的使用Bootstrap非常简单,但是在复制粘贴之前,需要先对Bootstrap的用法 ...

  7. Linux安装imagick扩展出现错误:configure: error: not found. Please provide a path to MagickWand-config or Wand-config program.

    在Linux(CentOS)上安装imagick扩展时,遇到如下错误: checking ImageMagick MagickWand API configuration program... che ...

  8. python xlwt操作excel

  9. 102. Binary Tree Level Order Traversal + 103. Binary Tree Zigzag Level Order Traversal + 107. Binary Tree Level Order Traversal II + 637. Average of Levels in Binary Tree

    ▶ 有关将一棵二叉树转化为二位表的题目,一模一样的套路出了四道题 ▶ 第 102 题,简单的转化,[ 3, 9, 20, null, null, 15, 7 ] 转为 [ [ 15, 7 ] , [ ...

  10. C# 子窗体关闭父窗体的简单方法

    当在一个窗体中调用另一个窗体时,涉及到子窗体关闭的同时,父窗体同时关闭. 例如: 在窗体1中,单击按钮调用窗体2,通过this传递 private void button1_Click(object ...