我们之前介绍过MappedStatement表示的是XML中的一个SQL。类当中的很多字段都是SQL中对应的属性。我们先来了解一下这个类的属性:

  1. public final class MappedStatement {
  2.  
  3. private String resource;
  4. private Configuration configuration;
  5. //sql的ID
  6. private String id;
  7. //尝试影响驱动程序每次批量返回的结果行数和这个设置值相等
  8. private Integer fetchSize;
  9. //SQL超时时间
  10. private Integer timeout;
  11. //Statement的类型,STATEMENT/PREPARE/CALLABLE
  12. private StatementType statementType;
  13. //结果集类型,FORWARD_ONLY/SCROLL_SENSITIVE/SCROLL_INSENSITIVE
  14. private ResultSetType resultSetType;
  15. //表示解析出来的SQL
  16. private SqlSource sqlSource;
  17. //缓存
  18. private Cache cache;
  19. //已废弃
  20. private ParameterMap parameterMap;
  21. //对应的ResultMap
  22. private List<ResultMap> resultMaps;
  23. private boolean flushCacheRequired;
  24. private boolean useCache;
  25. private boolean resultOrdered;
  26. //SQL类型,INSERT/SELECT/DELETE
  27. private SqlCommandType sqlCommandType;
  28. //和SELECTKEY标签有关
  29. private KeyGenerator keyGenerator;
  30. private String[] keyProperties;
  31. private String[] keyColumns;
  32. private boolean hasNestedResultMaps;
  33. //数据库ID,用来区分不同环境
  34. private String databaseId;
  35. private Log statementLog;
  36. private LanguageDriver lang;
  37. //多结果集时
  38. private String[] resultSets;
  39.  
  40. MappedStatement() {
  41. // constructor disabled
  42. }
  43.  
  44. ...
  45. }

对一些重要的字段我都增加了备注,方便理解。其中真正表示SQL的字段是SqlSource这个对象。

SqlSource接口很简单,只有一个getBound方法:

  1. public interface SqlSource {
  2.  
  3. BoundSql getBoundSql(Object parameterObject);
  4.  
  5. }

它有很多实现,需要我们重点关注的是StaticSqlSource,RawSqlSource和DynamicSqlSource。在正式学习他们前,我们先了解一下Mybatis动态SQL和静态SQL的区别。

动态SQL表示这个SQL节点中含有${}或是其他动态的标签(比如,if,trim,foreach,choose,bind节点等),需要在运行时根据传入的条件才能确定SQL,因此对于动态SQL的MappedStatement的解析过程应该是在运行时。

而静态SQL是不含以上这个节点的SQL,能直接解析得到含有占位符形式的SQL语句,而不需要根据传入的条件确定SQL,因此可以在加载时就完成解析。所在在执行效率上要高于动态SQL。

而DynamicSqlSource和RawSqlSource就分别对应了动态SQL和静态SQL,它们都封装了StaticSqlSource。

我们先从简单的入手,了解静态SQL的解析过程。

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5.  
  6. <mapper namespace="org.apache.ibatis.domain.blog.mappers.AuthorMapper">
  7.  
  8. <select id="selectAllAuthors" resultType="org.apache.ibatis.domain.blog.Author">
  9. select * from author
  10. </select>
  11.  
  12. </mapper>

这是我们要解析的XML文件,mapper节点下只有一个select节点。

  1. public class XmlMapperBuilderTest {
  2.  
  3. @Test
  4. public void shouldSuccessfullyLoadXMLMapperFile() throws Exception {
  5. Configuration configuration = new Configuration();
  6. String resource = "org/apache/ibatis/builder/AuthorMapper.xml";
  7. InputStream inputStream = Resources.getResourceAsStream(resource);
  8. XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  9. builder.parse();
  10. inputStream.close();
  11. }
  12. }

这是我们测试解析过程的代码。我们可以看到解析是由XMLMapperBuilder开始的。我们先了解一下它的字段:

  1. public class XMLMapperBuilder extends BaseBuilder {
  2. //用来解析XML
  3. private final XPathParser parser;
  4. //再解析完成后,用解析所得的属性来帮助创建各个对象
  5. private final MapperBuilderAssistant builderAssistant;
  6. //保存SQL节点
  7. private final Map<String, XNode> sqlFragments;
  8. //...
  9. }

它还从父类中继承了configuration(配置对象),typeAliasRegistry(类型别名注册器)和typeHandlerRegistry(类型处理器注册器)。

接下来看一下它的parse方法:

  1. public void parse() {
  2. //判断是否已经加载过资源
  3. if (!configuration.isResourceLoaded(resource)) {
  4. //从mapper根节点开始解析
  5. configurationElement(parser.evalNode("/mapper"));
  6. //将该资源添加到为已经加载过的缓存中
  7. configuration.addLoadedResource(resource);
  8. //将解析的SQL和接口中的方法绑定
  9. bindMapperForNamespace();
  10. }
  11.  
  12. //对一些未完成解析的节点再解析
  13. parsePendingResultMaps();
  14. parsePendingCacheRefs();
  15. parsePendingStatements();
  16. }

主要的解析过程在configurationElement中:

  1. private void configurationElement(XNode context) {
  2. try {
  3. //解析mapper的namespace属性,并设置
  4. String namespace = context.getStringAttribute("namespace");
  5. if (namespace == null || namespace.equals("")) {
  6. throw new BuilderException("Mapper's namespace cannot be empty");
  7. }
  8. builderAssistant.setCurrentNamespace(namespace);
  9. //解析<cache-ref>节点,它有一个namespace属性,表示引用该命名空间下的缓存
  10. cacheRefElement(context.evalNode("cache-ref"));
  11. //解析<cache>节点,可以设置缓存类型和属性,或是指定自定义的缓存
  12. cacheElement(context.evalNode("cache"));
  13. //已废弃,不再使用
  14. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  15. //解析resultMap节点
  16. resultMapElements(context.evalNodes("/mapper/resultMap"));
  17. //解析<SQL>节点,SQL节点可以使一些SQL片段被复用
  18. sqlElement(context.evalNodes("/mapper/sql"));
  19. //解析SQL语句(select|insert|update|delete节点)
  20. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  21. } catch (Exception e) {
  22. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  23. }
  24. }

我们关注SQL语句的解析过程,上述buildStatementFromContext(List<XNode>)方法会增加dateBaseId的参数,然后调用另一个重载方法:

  1. private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  2. //遍历XNode节点
  3. for (XNode context : list) {
  4. //为每个节点创建XMLStatementBuilder对象,
  5. final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
  6. try {
  7. //解析Node
  8. statementParser.parseStatementNode();
  9. } catch (IncompleteElementException e) {
  10. //对不能完全解析的节点添加到incompleteStatement,在parsePendingStatements方法中再解析
  11. configuration.addIncompleteStatement(statementParser);
  12. }
  13. }
  14. }

先看看XMLStatementBuilder对象:

  1. public class XMLStatementBuilder extends BaseBuilder {
  2.  
  3. private final MapperBuilderAssistant builderAssistant;
  4. private final XNode context;
  5. private final String requiredDatabaseId;
  6. // ...
  7. }

含有的字段相对简单,不再具体解释。直接看parseStatementNode方法:

  1. public void parseStatementNode() {
  2. //获取id
  3. String id = context.getStringAttribute("id");
  4. String databaseId = context.getStringAttribute("databaseId");
  5.  
  6. //验证databaseId是否匹配
  7. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  8. return;
  9. }
  10.  
  11. Integer fetchSize = context.getIntAttribute("fetchSize");
  12. Integer timeout = context.getIntAttribute("timeout");
  13. //已废弃
  14. String parameterMap = context.getStringAttribute("parameterMap");
  15. //参数类型;将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
  16. String parameterType = context.getStringAttribute("parameterType");
  17. Class<?> parameterTypeClass = resolveClass(parameterType);
  18. //结果类型;外部 resultMap 的命名引用。
  19. String resultMap = context.getStringAttribute("resultMap");
  20. //结果类型;表示从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。不能和resultMap同时使用。
  21. String resultType = context.getStringAttribute("resultType");
  22. String lang = context.getStringAttribute("lang");
  23. LanguageDriver langDriver = getLanguageDriver(lang);
  24.  
  25. Class<?> resultTypeClass = resolveClass(resultType);
  26. //结果集类型;FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)。
  27. String resultSetType = context.getStringAttribute("resultSetType");
  28. //STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
  29. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  30. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  31.  
  32. String nodeName = context.getNode().getNodeName();
  33. //SQLCommand类型
  34. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  35. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  36. //flushCache;在执行语句时表示是否刷新缓存
  37. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  38. //是否对该语句进行二级缓存;默认值:对 select 元素为 true。
  39. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  40. //根嵌套结果相关
  41. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  42.  
  43. //引入SQL片段
  44. // Include Fragments before parsing
  45. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  46. includeParser.applyIncludes(context.getNode());
  47.  
  48. // Parse selectKey after includes and remove them.
  49. //处理selectKey
  50. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  51.  
  52. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  53. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  54. //
  55. String resultSets = context.getStringAttribute("resultSets");
  56. String keyProperty = context.getStringAttribute("keyProperty");
  57. String keyColumn = context.getStringAttribute("keyColumn");
  58.  
  59. //设置主键自增的方式
  60. KeyGenerator keyGenerator;
  61. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  62. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  63. if (configuration.hasKeyGenerator(keyStatementId)) {
  64. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  65. } else {
  66. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  67. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  68. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  69. }
  70.  
  71. //通过buildAssistant将解析得到的参数设置构造成MappedStatement对象
  72. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  73. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  74. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  75. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  76. }

将解析得到参数通过BuilderAssistant.addMappedStatement方法,解析得到MappedStatement对象。

上面已经说过sqlsource表示的一个SQL语句,因此我们关注langDriver.createSqlSource这个方法。看XMLLanguageDriver这个实现。

  1. @Override
  2. public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  3. XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  4. return builder.parseScriptNode();
  5. }

可以看到他将创建sqlsource的工作交给了XMLScrpitBuilder(又一个创建者模式的应用)。来看parseScriptNode方法:

  1. public SqlSource parseScriptNode() {
  2. //解析SQL语句节点,创建MixedSqlNode对象
  3. MixedSqlNode rootSqlNode = parseDynamicTags(context);
  4. SqlSource sqlSource = null;
  5. //根据是否是动态的语句,创建DynamicSqlSource或是RawSqlSource对象,并返回
  6. if (isDynamic) {
  7. sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  8. } else {
  9. sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  10. }
  11. return sqlSource;
  12. }

MixedSqlNode是SqlNode的一个实现,包含了各个子节点,用来遍历输出子节点。SqlNode还有很多不同的实现,分别对应不同的节点类型。对应关系如下:

SqlNode实现 对应SQL语句中的类型
TextSqlNode ${}
IfSqlNode If节点
TrimSqlNode/WhereSqlNode/SetSqlNode Trim/Where/Set节点
Foreach节点 foreach标签
ChooseSqlNode节点 choose/when/otherwhise节点
ValDeclSqlNode节点 bind节点
StaticTextSqlNode 不含上述节点

除了StaticTextSqlNode节点外,其余对应的都是动态语句。

因此我们本文的关注点在StaticTextSqlNode。

让我们对应文初sql语句的解析来看一下parseDynamicTags方法,为了便于理解,我将在右边注释出每一步的结果

  1. protected MixedSqlNode parseDynamicTags(XNode node) {// node是我们要解析的SQL语句: <select resultType="org.apache.ibatis.domain.blog.Author" id="selectAllAuthors">select * from author</select>
  2. List<SqlNode> contents = new ArrayList<SqlNode>();
  3. //获取SQL下面的子节点
  4. NodeList children = node.getNode().getChildNodes();//这里的children只有一个节点;
  5. //遍历子节点,解析成对应的sqlNode类型,并添加到contents中
  6. for (int i = 0; i < children.getLength(); i++) {
  7. XNode child = node.newXNode(children.item(i));//第一个child节点就是SQL中的文本数据:select * from author
  8. //如果是文本节点,则先解析成TextSqlNode对象
  9. if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
  10. //获取文本信息
  11. String data = child.getStringBody("");//data:select * from author
  12. //创建TextSqlNode对象
  13. TextSqlNode textSqlNode = new TextSqlNode(data);
  14. //判断是否是动态Sql,其过程会调用GenericTokenParser判断文本中是否含有"${"字符
  15. if (textSqlNode.isDynamic()) {//如果是动态SQL,则直接使用TextSqlNode类型,并将isDynamic标识置为true
  16. contents.add(textSqlNode);
  17. isDynamic = true;
  18. } else {//不是动态sql,则创建StaticTextSqlNode对象,表示静态SQL
  19. contents.add(new StaticTextSqlNode(data));
  20. }
  21. } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { //其他类型的节点,由不同的节点处理器来对应处理成本成不同的SqlNode类型
  22. String nodeName = child.getNode().getNodeName();
  23. NodeHandler handler = nodeHandlerMap.get(nodeName);
  24. if (handler == null) {
  25. throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
  26. }
  27. handler.handleNode(child, contents);
  28. isDynamic = true;
  29. }
  30. }
  31. //用contents构建MixedSqlNode对象
  32. return new MixedSqlNode(contents);
  33. }

上述过程中,我们主要关注静态SQL的解析过程,对于动态SQL的解析将在之后介绍。

得到MixedSqlNode后,静态的SQL会创建出RawSqlSource对象。

看一下RawSqlSource:

  1. public class RawSqlSource implements SqlSource {
  2. //内部封装的sqlSource对象,getBoundSql方法会委托给这个对象
  3. private final SqlSource sqlSource;
  4.  
  5. public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
  6. this(configuration, getSql(configuration, rootSqlNode), parameterType);
  7. }
  8.  
  9. public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
  10. //创建sqlSourceBuilder
  11. SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  12. Class<?> clazz = parameterType == null ? Object.class : parameterType;
  13. //解析sql,创建StaticSqlSource对象
  14. sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
  15. }
  16.  
  17. //获取sql语句
  18. private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
  19. DynamicContext context = new DynamicContext(configuration, null);
  20. //这里的rootSqlNode就是之前得到的MixedSqlNode,它会遍历内部的SqlNode,逐个调用sqlNode的apply方法。StaticTextSqlNode会直接context.appendSql方法
  21. rootSqlNode.apply(context);
  22. return context.getSql();
  23. }
  24.  
  25. @Override
  26. public BoundSql getBoundSql(Object parameterObject) {
  27. return sqlSource.getBoundSql(parameterObject);
  28. }
  29.  
  30. }

代码相对简单,主要的步骤就是(1)通过SqlNode获得原始SQL语句;(2)创建SqlSourceBuilder对象,解析SQL语句,并创建StaticSqlSource对象;(3)将getBoundSql方法委托给内部的staticSqlSource对象。

其中比较关键的一步是解析原始SQL语句,并创建StaticSqlSource对象。因此我们继续看SqlSourceBuilder对象。

  1. public class SqlSourceBuilder extends BaseBuilder {
  2.  
  3. private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
  4.  
  5. public SqlSourceBuilder(Configuration configuration) {
  6. super(configuration);
  7. }
  8.  
  9. public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  10. //创建TokenHandler,用来将原始Sql中的'#{}' 解析成'?'
  11. ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  12. GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  13. //解析原始sql
  14. String sql = parser.parse(originalSql);
  15. //创建出StaticSqlSource对象
  16. return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  17. }
  18.  
  19. private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
  20.  
  21. private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
  22. private Class<?> parameterType;
  23. private MetaObject metaParameters;
  24.  
  25. public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
  26. super(configuration);
  27. this.parameterType = parameterType;
  28. this.metaParameters = configuration.newMetaObject(additionalParameters);
  29. }
  30.  
  31. public List<ParameterMapping> getParameterMappings() {
  32. return parameterMappings;
  33. }
  34.  
  35. @Override
  36. public String handleToken(String content) {
  37. //解析'#{}'中的参数,创建ParameterMapping对象
  38. parameterMappings.add(buildParameterMapping(content));
  39. //将'#{}'替换成'?'
  40. return "?";
  41. }
  42.  
  43. private ParameterMapping buildParameterMapping(String content) {
  44. Map<String, String> propertiesMap = parseParameterMapping(content);
  45. String property = propertiesMap.get("property");
  46. Class<?> propertyType;
  47. if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
  48. propertyType = metaParameters.getGetterType(property);
  49. } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
  50. propertyType = parameterType;
  51. } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
  52. propertyType = java.sql.ResultSet.class;
  53. } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
  54. propertyType = Object.class;
  55. } else {
  56. MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
  57. if (metaClass.hasGetter(property)) {
  58. propertyType = metaClass.getGetterType(property);
  59. } else {
  60. propertyType = Object.class;
  61. }
  62. }
  63. ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
  64. Class<?> javaType = propertyType;
  65. String typeHandlerAlias = null;
  66. for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
  67. String name = entry.getKey();
  68. String value = entry.getValue();
  69. if ("javaType".equals(name)) {
  70. javaType = resolveClass(value);
  71. builder.javaType(javaType);
  72. } else if ("jdbcType".equals(name)) {
  73. builder.jdbcType(resolveJdbcType(value));
  74. } else if ("mode".equals(name)) {
  75. builder.mode(resolveParameterMode(value));
  76. } else if ("numericScale".equals(name)) {
  77. builder.numericScale(Integer.valueOf(value));
  78. } else if ("resultMap".equals(name)) {
  79. builder.resultMapId(value);
  80. } else if ("typeHandler".equals(name)) {
  81. typeHandlerAlias = value;
  82. } else if ("jdbcTypeName".equals(name)) {
  83. builder.jdbcTypeName(value);
  84. } else if ("property".equals(name)) {
  85. // Do Nothing
  86. } else if ("expression".equals(name)) {
  87. throw new BuilderException("Expression based parameters are not supported yet");
  88. } else {
  89. throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + parameterProperties);
  90. }
  91. }
  92. if (typeHandlerAlias != null) {
  93. builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
  94. }
  95. return builder.build();
  96. }
  97.  
  98. private Map<String, String> parseParameterMapping(String content) {
  99. try {
  100. return new ParameterExpression(content);
  101. } catch (BuilderException ex) {
  102. throw ex;
  103. } catch (Exception ex) {
  104. throw new BuilderException("Parsing error was found in mapping #{" + content + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
  105. }
  106. }
  107. }
  108.  
  109. }

parse方法主要分为以下几步:

(1)创建了ParameterMappingTokenHandler对象

(2)将ParameterMappingTokenHandler对象传入GenericTokenParser的构造函数中,创建GenericTokenParser对象

(3)通过GenericTokenParser对象解析原始SQL,这个过程中会将#{}替换成?,并将#{}中的参数,解析形成ParamterMapping对象

(4)用得到的SQL和ParamterMapping对象创建StaticSqlSource对象。

解析完成后回到一开始的XMLMapperBuilder,它会在资源添加到已加载的列表中,并bindMapperForNamespace方法中为创建的MappedStatement添加命名空间。

mybatis源码学习(三):MappedStatement的解析过程的更多相关文章

  1. mybatis源码学习(三)-一级缓存二级缓存

    本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点 1.一级缓存配置信息 2.一级缓存源码学习笔记 3.二级缓存配置信息 4.二级缓存源码 5.一级缓存.二级缓存总结 1.一级缓存配 ...

  2. mybatis源码学习(一) 原生mybatis源码学习

    最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...

  3. mybatis源码学习:一级缓存和二级缓存分析

    目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...

  4. mybatis源码学习:基于动态代理实现查询全过程

    前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...

  5. mybatis源码学习:插件定义+执行流程责任链

    目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...

  6. Mybatis源码学习第六天(核心流程分析)之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

  7. 【mybatis源码学习】mybtias基础组件-占位符解析器

    一.占位符解析器源码 1.占位符解析器实现的目标 通过解析字符串中指定前后缀中的字符,并完成相应的功能. 在mybtias中的应用,主要是为了解析Mapper的xml中的sql语句#{}中的内容,识别 ...

  8. Spring mybatis源码学习指引目录

    前言: 分析了很多方面的mybatis的源码以及与spring结合的源码,但是难免出现错综的现象,为了使源码陶冶更为有序化.清晰化,特作此随笔归纳下分析过的内容.博主也为mybatis官方提供过pul ...

  9. Mybatis源码学习之DataSource(七)_1

    简述 在数据持久层中,数据源是一个非常重要的组件,其性能直接关系到整个数据持久层的性能.在实践中比较常见的第三方数据源组件有Apache Common DBCP.C3P0.Proxool等,MyBat ...

随机推荐

  1. php连接数据库,php连接mysql并查询的几种方式,PHP PDO连接以及预处理

    PHP连接数据库 面向过程 $config = [ 'host'=>'127.0.0.1', //数据库地址 'name'=>'test', //库名 'user'=>'root', ...

  2. 11. SpringCloud实战项目-初始化数据库和表

    SpringCloud实战项目全套学习教程连载中 PassJava 学习教程 简介 PassJava-Learning项目是PassJava(佳必过)项目的学习教程.对架构.业务.技术要点进行讲解. ...

  3. SpringBoot系列(五)Mybatis整合完整详细版

    SpringBoot系列(五)Mybatis整合 目录 mybatis简介 项目创建 entity dao service serviceImpl mapper controller 1. Mybat ...

  4. "着重内容"组件:<strong> —— 快应用组件库H-UI

     <import name="strong" src="../Common/ui/h-ui/text/c_tag_b"></import&g ...

  5. 搭建vue2.0开发环境及手动安装vue-devtools工具

    安装vue脚手架 1.安装node.js,如果安装成功输入 node -v ,查看node版本号,输入npm -v查看npm版本 https://nodejs.org/en/ 2.注册淘宝镜像,定制的 ...

  6. Ubuntu安装Elasticsearch6.3

    本文使用的 Ubuntu 版本信息: Distributor ID: Ubuntu Description: Ubuntu LTS Release: 16.04 Codename: xenial 1. ...

  7. 详解 Map集合

    (请关注 本人"集合总集篇"博文--<详解 集合框架>) 首先,本人来讲解下 Map集合 的特点: Map集合 的特点: 特点: 通过 键 映射到 值的对象 一个 映射 ...

  8. element-ui修改自定义主题

    官方文档:https://element.eleme.cn/#/zh-CN/component/custom-theme 简单更换主题色 打开:在线主题编辑器,仅修改主题色,点击右上角[切换主题色], ...

  9. vue如何添加jquery?

    1.首选通过npm安装jquery? 2.在build/webpack.base.conf文件当中引入jquery <pre>module.exports = { ... resolve: ...

  10. testNG groups 分组测试

    testNG的分组通过xml文件<groups>标签和@Test(group="组名")来实现分组 xml中关于分组的详细介绍,通过groups 定义一个组,通过< ...