MyBatis初化

MyBatis初化Configuration流程

示例

    String resource = "mybatis/mybatis-config.xml";

        InputStream inputStream = Resources.getResourceAsStream(resource);

        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sessionFactory.openSession();

        AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class);

        Author byId = authorMapper.getById(110);
        System.out.println("byId:" + byId);

可以看出一切都是从 new SqlSessionFactoryBuilder().build开始的

new SqlSessionFactoryBuilder().build方法

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
//调用下面的build方法最终返回SqlSessionFactory->DefaultSqlSessionFactory
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        //1.构造
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //2.返回DefaultSqlSessionFactory对象
      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.
      }
    }
  }

1.XMLConfigBuilder对象构造


public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    //XPathParser  解析XML的工具类, 对XPath的封装 1.
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
// 调用这个构造方法
 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
     //调用父类的构造方法
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
     //设置Properties中设置的变量
    this.configuration.setVariables(props);
     //设置为没有解析的标识,后面真正解析mybatisConfig.xml配置文件中使用
    this.parsed = false;
     //设置environment
    this.environment = environment;
     //赋值解析器
    this.parser = parser;
  }
//类中的属性
public class XMLConfigBuilder extends BaseBuilder {
 //是否解析
  private boolean parsed;
    //XML解析器XPath
  private final XPathParser parser;
    //环境
  private String environment;
    //反射工厂
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

XMLMapperEntityResolver


public class XMLMapperEntityResolver implements EntityResolver {
 //验证DTD文件使用本地的
  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

  /**
   * Converts a public DTD into a local one
   使用本地的DTD文件来验证XML文件 本地文件位置 上面最后2个属性值
   */
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch (Exception e) {
      throw new SAXException(e.toString());
    }
  }
//解析XML文件返回InputSource
  private InputSource getInputSource(String path, String publicId, String systemId) {
    InputSource source = null;
    if (path != null) {
      try {
        InputStream in = Resources.getResourceAsStream(path);
        source = new InputSource(in);
        source.setPublicId(publicId);
        source.setSystemId(systemId);        
      } catch (IOException e) {
        // ignore, null is ok
      }
    }
    return source;
  }

}

XPathParser

public class XPathParser {
	//解析xml生成文档对象
  private final Document document;
    //是否开启验证
  private boolean validation;
    //实体验证 用于加载本地 文件DTD验证文件 http://mybatis.org/dtd/mybatis-3-config.dtd由于网络有
  private EntityResolver entityResolver;
    //属性变量 mybatis-config.xml properties 标签定义的键位对集合
  private Properties variables;
    //XPATH
  private XPath xpath;
 //inputStream就是我们的mybatis-config.xml文件
 //validation 默认为true 开启验证
    //variables默认为 空
    //entityResolver 为   org.apache.ibatis.builder.xml.XMLMapperEntityResolver  这是Mybatis中的实现
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
     //设置属性值
    commonConstructor(validation, variables, entityResolver);
    //创建Document对象
    this.document = createDocument(new InputSource(inputStream));
  }

commonConstructor

  private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
      //设置是否验证 true
    this.validation = validation;
      //验证DTD文件本地加载
    this.entityResolver = entityResolver;
      //变量
    this.variables = variables;
      //XPath工厂
    XPathFactory factory = XPathFactory.newInstance();
      //构建Xpath对象
    this.xpath = factory.newXPath();
  }

createDocument

private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
        //构造DocumentBuilderFactory对象
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);
	//构造 DocumentBuilder
      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 {
        }
      });
        //返回Document对象
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

XMLConfigBuilder中new Configuration()

 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
     //调用父类的构造方法
    super(new Configuration());

先看super方法

public abstract class BaseBuilder {
    //
  protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;

  public BaseBuilder(Configuration configuration) {
      //赋值
    this.configuration = configuration;
      //别名类型注册器 1.
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
      //typeHandler注册器2.
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

typeAliasRegistry

public class TypeAliasRegistry {

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

  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

可以看到主要是注册JAVA中的类型与Mybatis的数据类型进行绑定

TypeHandlerRegistry 注册一个Mybatis中内置的类型转换扩展

public final class TypeHandlerRegistry {

  private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
  private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
  private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();

  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

  public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    // mybatis-typehandlers-jsr310
    if (Jdk.dateAndTimeApiExists) {
      this.register(Instant.class, InstantTypeHandler.class);
      this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
      this.register(LocalDate.class, LocalDateTypeHandler.class);
      this.register(LocalTime.class, LocalTimeTypeHandler.class);
      this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
      this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
      this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
      this.register(Month.class, MonthTypeHandler.class);
      this.register(Year.class, YearTypeHandler.class);
      this.register(YearMonth.class, YearMonthTypeHandler.class);
      this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
    }

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }

new Configuration() 方法核心方法

public class Configuration {
	//环境
  protected Environment environment;
	//安全SQL是否启用
  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;
	//log前缀
  protected String logPrefix;
    //LOG实现可在Mybatis-config中setting中配置
  protected Class <? extends Log> logImpl;
  protected Class <? extends VFS> vfsImpl;
    //本地Cache范围 在一个SESSION中
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
    //懒加载触发器
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
    //默认超时时间
  protected Integer defaultStatementTimeout;
    //默认批量执行的SQL的数
  protected Integer defaultFetchSize;
    //默认ExecutorType类型ExecutorType.SIMPLE
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
	//Properties标签与文件中变量
  protected Properties variables = new Properties();
    //反射工厂
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
	//是否懒加载 默认false
  protected boolean lazyLoadingEnabled = false;
   //代理工厂
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
	//数据库id
  protected String databaseId;
  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
   */
  protected Class<?> configurationFactory;
//解析mybatisCofnig中mappers标签中注册所有mapper
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    //拦截器链
  protected final InterceptorChain interceptorChain = new InterceptorChain();
    //扩展TypeHandle注册
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    //别名注册
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//MappedStatement
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
    //缓存
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
//加载文件资料如mapper resouce='xxx/xxMapper.xml'
  protected final Set<String> loadedResources = new HashSet<String>();
    //保存sql
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();

  /*
   * 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<String, String>();

  public Configuration(Environment environment) {
    this();
    this.environment = environment;
  }

    //各种别名 与Mybatis中的类绑定
  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);
  }

new Configuration()中会对所有成员变量默认初始化

2. build()方法参数是XMLConfigBuilder

parser.parse()返回Configruration

public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  private final XPathParser parser;
  private String environment;
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
...
     public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
    

parseConfiguration()解析mybatisConfig中的所有标签包含未设置的都会使用Mybatis中的默认设置


  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(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);
    }
  }

propertiesElement对Properties标签中的变量解析

  
public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  private final XPathParser parser;
  private String environment;
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
    ....
private void propertiesElement(XNode context) throws Exception {
      //context<properties resource="mybatis/conn.properties">
	//	... 
	//</properties>
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
        //这里使用是resource mybatis/conn.properties 获取Properties文件中的变量 
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
        //properties标签中设置的变量
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
        //设置值XpathParser中赋值--解析到下面的变量时直接从解析器获取
      parser.setVariables(defaults);
        //为configuration设置Properties 
      configuration.setVariables(defaults);
        //至此Properties文件与Properties标签中的所有变量解析完成
    }
  }

conn.properties文件示例

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis-test?characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
jdbc.user=root
jdbc.password=root

  • 解析完成

    {jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis-test?characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false, jdbc.driver=com.mysql.jdbc.Driver, jdbc.user=root, jdbc.password=root}

    因为我的只有properties文件,没有标签中的只有这些

Properties settings = settingsAsProperties(root.evalNode(“settings”));

解析setting标签

<settings>
    <!--开启二级缓存 默认true需要在xxMapper.xml中同样开启才有效果-->
  <setting name="cacheEnabled" value="true"/>
     <!--开启懒加载 默认false-->
  <setting name="lazyLoadingEnabled" value="true"/>
     <!--当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 true为直接懒加载 false为深度懒加载 默认false 3.4.1之前是true-->
    <setting name="aggressiveLazyLoading" value="false"/>
     <!--开启多结果集-是否允许单一语句返回多结果集-->
  <setting name="multipleResultSetsEnabled" value="true"/>
 <!--使用列标签代替列名。-->
  <setting name="useColumnLabel" value="true"/>
   <!-- 允许 JDBC 支持自动生成主键,需要驱动兼容-->
  <setting name="useGeneratedKeys" value="false"/>
      <!-- 指定 MyBatis 应如何自动映射列到字段或属性。 
NONE 表示取消自动映射;
PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。  默认
FULL 会自动映射任意复杂的结果集(无论是否嵌套)-->
  <setting name="autoMappingBehavior" value="PARTIAL"/>
          <!-- 指定发现自动映射目标未知列(或者未知属性类型)的行为。
NONE: 不做任何反应
WARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN)
FAILING: 映射失败 (抛出 SqlSessionException)-->
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
          <!--配置默认的执行器。
			SIMPLE 就是普通的执行器;  默认
			REUSE 执行器会重用预处理语句(prepared statements); 
			BATCH 执行器将重用语句并执行批量更新 -->
  <setting name="defaultExecutorType" value="SIMPLE"/>
          <!--设置超时时间,它决定驱动等待数据库响应的秒数。没有默认值 -->
  <setting name="defaultStatementTimeout" value="25"/>
          <!--为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖  无-->
  <setting name="defaultFetchSize" value="100"/>
          <!-- 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false 默认False-->
  <setting name="safeRowBoundsEnabled" value="false"/>
          <!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 默认false 
 
-->
  <setting name="mapUnderscoreToCamelCase" value="false"/>
          <!--
MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据 默认 SESSION一个会话中
-->
  <setting name="localCacheScope" value="SESSION"/>
          <!-- 
当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
-->
  <setting name="jdbcTypeForNull" value="OTHER"/>
          <!--指定哪个对象的方法触发一次延迟加载 -->
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

就这解析这一堆东东

具体请看官方文档

configuration

下面都一堆这样的解析为Configuration中的标签解析并赋值

1547449314484

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(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);
    }
  }

主要 看mapperElement()这个解析

Mappers的种配置方式

<!-- 使用相对于类路径的资源引用 xml和Mapper接口可以不在同一个目录下-->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL)  xml和Mapper接口可以不在同一个目录下-->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名   xml和Mapper接口必须在同一个目录下-->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 xml和Mapper接口必须在同一个目录下-->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

以上4种只能同时使用一种不能多种

对Mappers标签下的xxMapper.xml标签解析

  //parent=
//<mappers>
//<mapper resource="mapper/AuthorMapper.xml"/>
//....
//</mappers>
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        //获取所有子节点
      for (XNode child : parent.getChildren()) {
          //示例:child =<mapper resource="mapper/AuthorMapper.xml"/>
          //判断是不是package的配置方式,get节点名称 mapper
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
            //获取属性名称
            //resource方式 resource
          String resource = child.getStringAttribute("resource");
            //url方式 url
          String url = child.getStringAttribute("url");
            //class方式 class
          String mapperClass = child.getStringAttribute("class");
            //resource 最常用 只说这一种
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
              //获取   mapper/AuthorMapper.xml
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              // 这个方法见下面 mapperParser.parse
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
              //url方式
            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方式
            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.");
          }
        }
      }
    }
  }

XMLMapperBuilder 用来解析Mapper.xml的构建器

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
      //和解析Configuration开始里一样的套路 使用Document之类的,设置解析
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

还是使用Xpath解析

看构造方法

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

这里为configuration赋了值

BaseBuilder

public abstract class BaseBuilder {
  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();
  }

MapperBuilderAssistant

public class MapperBuilderAssistant extends BaseBuilder {
//当前的命名空间
  private String currentNamespace;
    //资料
  private final String resource;
    //缓存
  private Cache currentCache;
  private boolean unresolvedCacheRef; // issue #676

XMLMapperBuilder parse方法


public class XMLMapperBuilder extends BaseBuilder {
//解析器
  private final XPathParser parser;
    //
  private final MapperBuilderAssistant builderAssistant;
    //SQL节点
  private final Map<String, XNode> sqlFragments;
    //资源
  private final String resource;
    ...
        //真正解析xxxMapper.xml文件
   public void parse() {
        //示例resource --mapper/AuthorMapper.xml 判断是否已经加载过了是个HashSet在Configuration类中loadedResources BaseBuilder中有configuration上面一步设置的
    if (!configuration.isResourceLoaded(resource)) { //见附2
        //1. 解析mapper parser中已经到mapper/AuthorMapper.xml转换为Document对象 evalNode("/mapper")Xpath语法 见下面附1 ,2获取mapper节点的所有内容
      configurationElement(parser.evalNode("/mapper"));
        //添加到loadedResources中
      configuration.addLoadedResource(resource);
        //绑定Mapper与xm的命名空间
      bindMapperForNamespace();
    }
	//解析ResultMap
    parsePendingResultMaps();
        //解析缓存
    parsePendingCacheRefs();
        //解析select insert之类的标签
    parsePendingStatements();
  }
附1.parse示例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ghgcn.mybatis_demo.mapper.AuthorMapper">

	<resultMap id="BaseResultMap" type="com.ghgcn.mybatis_demo.entity.Author">
		<id column="id" property="id" />
		<result column="username" property="username" />
		<result column="password" property="password" />
		<result column="email" property="email" />
		<result column="bio" property="bio"
			typeHandler="com.ghgcn.mybatis_demo.handler.IntArrayJoinTypeHandler" />
		<result column="favourite_section" property="favouriteSection" />
	</resultMap>
	<resultMap id="BaseResultMap1" type="com.ghgcn.mybatis_demo.entity.Author">
		<id column="id" property="id" />
		<result column="username" property="username" />
		<result column="password" property="password" />
		<result column="email" property="email" />
		<result column="bio" property="bio"
			typeHandler="com.ghgcn.mybatis_demo.handler.IntArrayJoinTypeHandler" />
		<result column="favourite_section" property="favouriteSection" />
		<association property="blog" column="id"
			javaType="com.ghgcn.mybatis_demo.entity.Blog">
			<id column="id" property="id" />
			<result column="author_id" property="authorId" />
			<result column="title" property="title" />
		</association>
	</resultMap>
	<resultMap id="BaseResultMap2" type="com.ghgcn.mybatis_demo.entity.Author">
		<id column="id" property="id" />
		<result column="username" property="username" />
		<result column="password" property="password" />
		<result column="email" property="email" />
		<result column="bio" property="bio"
			typeHandler="com.ghgcn.mybatis_demo.handler.IntArrayJoinTypeHandler" />
		<result column="favourite_section" property="favouriteSection" />
		<association property="blog" column="id"
			javaType="com.ghgcn.mybatis_demo.entity.Blog" select="findBlogById">

		</association>
	</resultMap>
	<!-- 通用查询结果列 -->
	<sql id="Base_Column_List">
		id,username,password,email,bio,favourite_section
	</sql>
	<select id="getById" resultMap="BaseResultMap">
		SELECT
		<include refid="Base_Column_List" />
		FROM author
		WHERE id = #{id}
	</select>
	<insert id="insert" useGeneratedKeys="true" keyProperty="id">
		INSERT
		INTO author (
		username,
		password,
		email,
		bio,
		favourite_section
		)
		VALUES
		(
		#{username},
		#{password},
		#{email},
		#{bio},
		#{favouriteSection}
		);

	</insert>

	<update id="updateById">

		update author
		<set>
			<if test="username!=null and username !=''">
				username=#{username},
			</if>
			<if test="password!=null and password !=''">
				password=#{password},
			</if>
			<if test="email!=null and email !=''">
				email=#{email},
			</if>
			<if test="bio!=null and bio !=''">
				bio=#{bio},
			</if>
			<if test="favouriteSection!=null and favouriteSection !=''">
				favourite_section=#{favouriteSection},
			</if>
		</set>
		<where>
			id=#{id}
		</where>
	</update>
	<select id="getList" resultMap="BaseResultMap1">
		SELECT
		author.*,
		blog.*
		FROM
		author
		LEFT JOIN blog on author.id = blog.author_id
	</select>
	<select id="getList2" resultMap="BaseResultMap2">
		SELECT
		<include refid="Base_Column_List" />
		FROM author
	</select>
	<select id="findBlogById" resultType="com.ghgcn.mybatis_demo.entity.Blog" parameterType="Integer">
		SELECT
		id,
		author_id,
		title
		FROM
		blog
		WHERE
		author_id = #{id}
	</select>
</mapper>

configurationElement方法

1547451254329

  private void configurationElement(XNode context) {
      //context 是xml见上图
    try {
        //com.ghgcn.mybatis_demo.mapper.AuthorMapper
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
          //如果命名空间为空抛出异常,Mybatis要求必有命名空间
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
        //设置命名空间 MapperBuilderAssistantMapper构造助理类
      builderAssistant.setCurrentNamespace(namespace);
        //解析有没有开启缓存连接
      cacheRefElement(context.evalNode("cache-ref"));
        //解析有没有二级缓存标签 这里NULL
      cacheElement(context.evalNode("cache"));
        //解析有没有parameterMap 上面的xml没有
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        //解析resultMap有
      resultMapElements(context.evalNodes("/mapper/resultMap"));
       //解析sql标签 这里没如果XML中有SQL标签就会在这里解析
      sqlElement(context.evalNodes("/mapper/sql"));
        //解析select|insert|update|delete标签
      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);
    }
  }
cacheRefElement(context.evalNode(“cache-ref”));
参照缓存

回想一下上一节内容, 这个特殊命名空间的唯一缓存会被使用或者刷新相同命名空间内 的语句。也许将来的某个时候,你会想在命名空间中共享相同的缓存配置和实例。在这样的 情况下你可以使用 cache-ref 元素来引用另外一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>
<cache-ref namespace=""/>解析这种标签

1547452578855

  private void cacheRefElement(XNode context) {
      //不是空的情况下
    if (context != null) {
        //添加CacheRef属性和其中的命名空间
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
        //缓存
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
          //建立连接
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

字面上看就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句将会被缓存。
  • 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
  • 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
  • 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
  • 示例值

    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。

可用的收回策略有:

  • LRU – 最近最少使用的:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

默认的是 LRU。

flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

使用自定义缓存

除了这些自定义缓存的方式, 你也可以通过实现你自己的缓存或为其他第三方缓存方案 创建适配器来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

这个示 例展 示了 如何 使用 一个 自定义 的缓 存实 现。type 属 性指 定的 类必 须实现 org.mybatis.cache.Cache 接口。这个接口是 MyBatis 框架中很多复杂的接口之一,但是简单 给定它做什么就行。

使用Cache接口

package org.apache.ibatis.cache
public interface Cache {

  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);
  
  void clear();

  int getSize();

  ReadWriteLock getReadWriteLock();

}

从3.4.2版本开始,MyBatis已经支持在所有属性设置完毕以后可以调用一个初始化方法。如果你想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口。

public interface InitializingObject {
  void initialize() throws Exception;
}

记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。因此,所有 在相同命名空间的语句正如绑定的缓存一样。 语句可以修改和缓存交互的方式, 或在语句的 语句的基础上使用两种简单的属性来完全排除它们。默认情况下,语句可以这样来配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
cacheElement(context.evalNode(“cache”));
  private void cacheElement(XNode context) throws Exception {
      //不是空的情况
    if (context != null) {
        //获取类型
      String type = context.getStringAttribute("type", "PERPETUAL");
        //缓存的类型
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        //哪种缓存方式
      String eviction = context.getStringAttribute("eviction", "LRU");
        //
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
        //获取大小
      Integer size = context.getIntAttribute("size");
        //是否只读
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
        //使用新的缓存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
<parameterMap type="" id=""></parameterMap>
//主要是来解析 parameterMap的标签,没有用过,看样子和resultMap类似
private void parameterMapElement(List<XNode> list) throws Exception {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = resolveParameterMode(mode);
        Class<?> javaTypeClass = resolveClass(javaType);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        @SuppressWarnings("unchecked")
        Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }
resultMapElements(context.evalNodes("/mapper/resultMap"));

1547453350596

<resultMap id="BaseResultMap" type="com.ghgcn.mybatis_demo.entity.Author">
		<id column="id" property="id" />
		<result column="username" property="username" />
		<result column="password" property="password" />
		<result column="email" property="email" />
		<result column="bio" property="bio"
			typeHandler="com.ghgcn.mybatis_demo.handler.IntArrayJoinTypeHandler" />
		<result column="favourite_section" property="favouriteSection" />
	</resultMap>

  private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

对所有resultMap标签解析

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //获取ResultMap的id
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    //获取类型 result映射的类的类型全路径  com.ghgcn.mybatis_demo.entity.Author
    //	<resultMap id="BaseResultMap" type="com.ghgcn.mybatis_demo.entity.Author"> type
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    //获取继承的resultMap 
    String extend = resultMapNode.getStringAttribute("extends");
    //获取autoMapping autoMapping	如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性 建立不设置 获取不到就是null 不理会
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //反射类
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    //添加到
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

ResultMap的类型

  • 获取resultMap的所有子节点

    1547453817877

buildResultMappingFromContext方法

解析

	<resultMap id="BaseResultMap" type="com.ghgcn.mybatis_demo.entity.Author">
		<id column="id" property="id" />
		<result column="username" property="username" />
		<result column="password" property="password" />
		<result column="email" property="email" />
		<result column="bio" property="bio"
			typeHandler="com.ghgcn.mybatis_demo.handler.IntArrayJoinTypeHandler" />
		<result column="favourite_section" property="favouriteSection" />
	</resultMap>

解析

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap",
        processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }
  • 一个ResultMap解析完
[
ResultMapping{property='id', column='id', javaType=class java.lang.Integer, jdbcType=null, nestedResultMapId='null', nestedQueryId='null', notNullColumns=[], columnPrefix='null', flags=[ID], composites=[], resultSet='null', foreignColumn='null', lazy=true}, 

ResultMapping{property='username', column='username', javaType=class java.lang.String, jdbcType=null, nestedResultMapId='null', nestedQueryId='null', notNullColumns=[], columnPrefix='null', flags=[], composites=[], resultSet='null', foreignColumn='null', lazy=true}, 

ResultMapping{property='password', column='password', javaType=class java.lang.String, jdbcType=null, nestedResultMapId='null', nestedQueryId='null', notNullColumns=[], columnPrefix='null', flags=[], composites=[], resultSet='null', foreignColumn='null', lazy=true}, 

ResultMapping{property='email', column='email', javaType=class java.lang.String, jdbcType=null, nestedResultMapId='null', nestedQueryId='null', notNullColumns=[], columnPrefix='null', flags=[], composites=[], resultSet='null', foreignColumn='null', lazy=true}, ResultMapping{property='bio', column='bio', javaType=class [Ljava.lang.Integer;, jdbcType=null, nestedResultMapId='null', nestedQueryId='null', notNullColumns=[], columnPrefix='null', flags=[], composites=[], resultSet='null', foreignColumn='null', lazy=true}, ResultMapping{property='favouriteSection', column='favourite_section', javaType=class java.lang.String, jdbcType=null, nestedResultMapId='null', nestedQueryId='null', notNullColumns=[], columnPrefix='null', flags=[], composites=[], resultSet='null', foreignColumn='null', lazy=true}
]

都有property , column,javaType属性

每一列都是一个集合元素生成ResultMapResolver对象


  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);

public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
  }
sqlElement(context.evalNodes("/mapper/sql")); 解析节点把SQL取出 根据id
  private void sqlElement(List<XNode> list) throws Exception {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        sqlFragments.put(id, context);
      }
    }
  }

buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));

1547454453063

  private void buildStatementFromContext(List<XNode> list) {
    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);
      }
    }
  }

解析


public void parseStatementNode() {
    //获取select update delete insert方法的id名称
    String id = context.getStringAttribute("id");
    //获取数据库id默认为null
    String databaseId = context.getStringAttribute("databaseId");
	//判断数据库是不是必须要有databaseId
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
	//获取属性fetchSize 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // timeout超里时间 
    Integer timeout = context.getIntAttribute("timeout");
    //参数Map
    String parameterMap = context.getStringAttribute("parameterMap");
    //属性参数类型
    String parameterType = context.getStringAttribute("parameterType");
    //获取参数类型的具体类型反射
    Class<?> parameterTypeClass = resolveClass(parameterType);
    //获取结果集映射
    String resultMap = context.getStringAttribute("resultMap");
    //返回类型
    String resultType = context.getStringAttribute("resultType");
    //获取lang
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
	//获取返加类型反射 可以是别名,会先打别名找不到就反射
    Class<?> resultTypeClass = resolveClass(resultType);
    //resultSetType
    String resultSetType = context.getStringAttribute("resultSetType");
    //statementType statementType	STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
	//获取节点名称select insert update delete这种
    String nodeName = context.getNode().getNodeName();
    //获取枚举
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    //判断是不是select
    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);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    //解析sql 把一些变量标签替换
    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))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
	//添加到
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        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");
    }
 //例如:com.ghgcn.mybatis_demo.mapper.AuthorMapper.updateById 
    id = applyCurrentNamespace(id, false);
    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)
        .resultSets(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) {
        //替换ParameterMap
      statementBuilder.parameterMap(statementParameterMap);
    }
  //构建MappedStatement
    MappedStatement statement = statementBuilder.build();
    //将statement添加到configuration中
    configuration中.addMappedStatement(statement添加到);
    return statement;
  }
public class Configuration {

...

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");


public void addMappedStatement(MappedStatement ms) {
    //xml中标签的全距径为key   com.ghgcn.mybatis_demo.mapper.AuthorMapper.updateById 这种一个方法一条记录
    mappedStatements.put(ms.getId(), ms);
  }

附2 加载Mapper.xml的集合上面的一loadedResources

Configuration类中  
protected final Set<String> loadedResources = new HashSet<String>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");


  public boolean isResourceLoaded(String resource) {
    return loadedResources.contains(resource);
  }

configuration.addLoadedResource(resource);

  protected final Set<String> loadedResources = new HashSet<String>();
   configuration.addLoadedResource(resource);
  public void addLoadedResource(String resource) {
      //loadedResources添加这个资源
    loadedResources.add(resource);
  }

1547456305488

bindMapperForNamespace ();方法绑定命名空间

 bindMapperForNamespace()
     
      private void bindMapperForNamespace() {
     //当前的命名空间
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
          //获取命名空间对应的类反射
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
        //判断是不是空 绝对不能是空
      if (boundType != null) {
          //判断这个Map集合是否有这个
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
            //添加到Set中
          configuration.addLoadedResource("namespace:" + namespace);
            //添加到Map中
          configuration.addMapper(boundType);
        }
      }
    }
  }

Configuration 类

  
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

  public boolean hasMapper(Class<?> type) {
    return mapperRegistry.hasMapper(type);
  }

MappperRegistry

public class MapperRegistry {

  private final Configuration config;
    //key命名空间的对象的类如com.ghgcn.mybatis_demo.mapper.AuthorMapper全路径
    //value是生成的代理工厂
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

就是维护了一个Map

这样就完成了Class与代理的映射

MapperProxyFactory

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

添加addMapper

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
          //使用上面的代理工厂
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
          //注解建造者
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
          //解析xml
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

build 方法

 public SqlSessionFactory build(Configuration config) {
     //构造参数传入Configuration,所以DefaultSqlSessionFactory持有Configuration所有属性
    return new DefaultSqlSessionFactory(config);
  }

至此返回 DefaultSqlSessionFactory是SqlSessionFactory的一个实现

1547447600692

MyBatis GetMapper流程

SqlSession sqlSession = sessionFactory.openSession();

        AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class);

        authorMapper.getById(101);

一切从这里开始

openSession public class DefaultSqlSessionFactory implements SqlSessionFactory

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
	//这个方法
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        //从configuration中获取environment
      final Environment environment = configuration.getEnvironment();
        //获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        //构建事务默认是JdbcTransaction
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //获取执行器默认是SIMPLE
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

JdbcTransactionFactory

public class JdbcTransactionFactory implements TransactionFactory {

  @Override
  public void setProperties(Properties props) {
  }

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

###  final Executor executor = configuration.newExecutor(tx, execType);

  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;
  }

返回 return new DefaultSqlSession(configuration, executor, autoCommit);

 public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

执行器 final Executor executor = configuration.newExecutor(tx, execType);

 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;
  }

SimpleExecutor

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }
    
    //super BaseExecutor
      protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }
    
    
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
//配置的插件里面取出插件,然后用插件的plugin方法去生成代理对象
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}
AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class);
public class DefaultSqlSession implements SqlSession {
...
     @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
    

configuration->getMapper 这个就是Configuration加载时的Mapkey接口 VALUE是代理工厂

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }


1547459225694

1547459252937

以上都是在configuration类中

MapperRegistry类中才是真正的Map 在解析Mapper标签时做了映射关系
public class MapperRegistry {

  private final Configuration config;
    // key Mapper接口  VALUE是代理工厂
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
//==这个是核心方法==
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     //根据Key值获取Value
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
      // 代理工厂是空就抛出异常
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        //使用代理工厂生成代理对象的实例
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  
  ...
}

1547459720339

mapperProxyFactory.newInstance(sqlSession);
public class MapperProxyFactory<T> {
	//代理的接口
  private final Class<T> mapperInterface;
    //方法与Mapper方法
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }
// 2. JDK动态代理
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
	//1.生成MapperProxy对象
  public T newInstance(SqlSession sqlSession) {
      //见下面JDK动态代理的实现
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

MapperProxy Jdk动态代理
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //判断是不理Object类
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
      //缓存Mapper方法 真正调用Mapper方法时会调用个invoke方法
    final MapperMethod mapperMethod = cachedMapperMethod(method);
      //执行
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

  @UsesJava7
  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }
}

这回Mapper代理对象

1547460158424

调用newInstance生成JDK动态代理对象

1547460092498

MyBatis 执行SQL流程

		//这里返回的是代理对象
        AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class);
		//这个执行SQL
        authorMapper.getById(101);

org.apache.ibatis.binding.MapperProxy invode中打断点

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //判断是不理Object类
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
      //缓存Mapper方法 真正调用Mapper方法时会调用个invoke方法
    final MapperMethod mapperMethod = cachedMapperMethod(method);
      //执行
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
    ...
}

1547460375631

方法是命名空间全名+方法名

1547460411508

参数

1547460430241

判断是否为Object类


  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //判断 是否为Object类
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
          //判断是否为默认方法
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
      //下面这2个方法才是最重要的
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
cachedMapperMethod
  private final Map<Method, MapperMethod> methodCache;
....
private MapperMethod cachedMapperMethod(Method method) {
      //从缓存中获取是否有这个方法
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
        //如果是空就把方法缓存起来,一级缓存
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
        //放入缓存
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }



MapperMethod构造方法
public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
    ...
        

1547460735624

1547460765159

方法签名

mapperMethod.execute(sqlSession, args);
 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
            //转换参数
          Object param = method.convertArgsToSqlCommandParam(args);
            //底层还是执行sqlSession.selectOne
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

1547462964334

1547462947262

convertArgsToSqlCommandParam

public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }

参数处理器


public class ParamNameResolver {
    //通用参数前缀
      private static final String GENERIC_NAME_PREFIX = "param";
    //Map
      private final SortedMap<Integer, String> names;
//是否有注解@Param 
  private boolean hasParamAnnotation;
    
    public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
    } else {
      final Map<String, Object> param = new ParamMap<Object>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }
}
selectOne
org.apache.ibatis.session.defaults.DefaultSqlSession

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }
  
 @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        //根据statement获取MappedStatement  
        //statement是 com.ghgcn.mybatis_demo.mapper.AuthorMapper.getById 这种类型,在解析Mappers节点时在Configuration中的有保存对应关系与SQL
      MappedStatement ms = configuration.getMappedStatement(statement);
        //直正执行开始
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

MappedStatement
public final class MappedStatement {

  private String resource;
  private Configuration configuration;
    // SELECT UPDATE 标签的ID 方法名
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
    //中包含SQL语句
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

  MappedStatement() {
    // constructor disabled
  }

  public static class Builder {
    private MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
      mappedStatement.resultMaps = new ArrayList<ResultMap>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {
        logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
    }

query方法 org.apache.ibatis.executor.BaseExecutor

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
      //获取绑定的SQL及参数
    BoundSql boundSql = ms.getBoundSql(parameter);
      //建立缓存
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
      //查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

1547464018724

query org.apache.ibatis.executor.CachingExecutor
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
      //这里才是执行
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

真正

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
        //判断有没有resultHandler 本地缓存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
          //有就直接返回 一级缓存
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
          //这里执行SQL
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
queryFromDatabase org.apache.ibatis.executor.BaseExecutor


  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
      //放入缓存
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        //调用子类的org.apache.ibatis.executor.SimpleExecutor
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        //移除缓存
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

1547464430344

org.apache.ibatis.executor.SimpleExecutor doQuery模版方法



  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        //获取Configuration
      Configuration configuration = ms.getConfiguration();
        //创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        //预编译
      stmt = prepareStatement(handler, ms.getStatementLog());
        //执行
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
prepareStatement org.apache.ibatis.executor.SimpleExecutor

激动人心的时刻到来了

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
      //JDBC中的
    Statement stmt;
      //JDBC中的连接
    Connection connection = getConnection(statementLog);
      //
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
handler.prepare
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
        //设置属性
      statement = instantiateStatement(connection);
        //超时时间
      setStatementTimeout(statement, transactionTimeout);
        //批量的数量
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
instantiateStatement
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
          //预编译SQL
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
            //预编译SQL
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
          //预编译SQL
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
          //预编译SQL
      return connection.prepareStatement(sql);
    }
  }
设置参数
  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

return handler.query(stmt, resultHandler);

最终执行SQL
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }
返回类型映射resultSetHandler. handleResultSets(ps)
@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
	//对ResultSet循环通过反射来获取值
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

文章目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值