MyBatis源码分析之——配置解析创建SqlSessionFactory的过程

简介: 大家应该都知道Mybatis源码也是对Jbdc的再一次封装,不管怎么进行包装,还是会有获取链接、preparedStatement、封装参数、执行这些步骤的。

大家应该都知道Mybatis源码也是对Jbdc的再一次封装,不管怎么进行包装,还是会有获取链接、preparedStatement、封装参数、执行这些步骤的。


配置解析过程



String resource = "mybatis-config.xml";
//1.读取resources下面的mybatis-config.xml文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//2.使用SqlSessionFactoryBuilder创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.通过sqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();


1.Resources.getResourceAsStream(resource)读取文件



public static InputStream getResourceAsStream(String resource) throws IOException {
  return getResourceAsStream(null, resource);
} 
//loader赋值为null
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
  InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
  if (in == null) {
    throw new IOException("Could not find resource " + resource);
  } 
  return in;
}
//classLoader为null
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
  return getResourceAsStream(resource, getClassLoaders(classLoader));
} 
//classLoader类加载
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
  for (ClassLoader cl : classLoader) {
    if (null != cl) {
      //加载指定路径文件流
      InputStream returnValue = cl.getResourceAsStream(resource);
      // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
      if (null == returnValue) {
        returnValue = cl.getResourceAsStream("/" + resource);
      } 
      if (null != returnValue) {
        return returnValue;
      }
    }
  } 
  return null;


总结:主要是通过ClassLoader.getResourceAsStream()方法获取指定的classpath路径下的Resource


2.通过SqlSessionFactoryBuilder创建SqlSessionFactory


//SqlSessionFactoryBuilder是一个建造者模式
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
public SqlSessionFactory build(InputStream inputStream) {
  return build(inputStream, null, null);
}
//XMLConfigBuilder也是建造者模式
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    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.
    }
  }
}
//接下来进入XMLConfigBuilder构造函数
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
  this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()),
  environment, props);
}
//接下来进入this后,初始化Configuration
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}
//其中parser.parse()负责解析xml,build(configuration)创建SqlSessionFactory
return build(parser.parse())


(1)parser.parse()解析xml


public Configuration parse() {
  //判断是否重复解析
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  } 
  parsed = true;
  //读取配置文件一级节点configuration
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}



private void parseConfiguration(XNode root) {
  try {
    //properties 标签,用来配置参数信息,比如最常见的数据库连接信息
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    //实体别名两种方式:1.指定单个实体;2.指定包
    typeAliasesElement(root.evalNode("typeAliases"));
    //插件
    pluginElement(root.evalNode("plugins"));
    //用来创建对象(数据库数据映射成java对象时)
    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"));
    //数据库类型和Java数据类型的转换
    typeHandlerElement(root.evalNode("typeHandlers"));
    //这个是对数据库增删改查的解析
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}


总结:parseConfiguration完成的是解析configuration下的标签


private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      //解析<package name=""/>
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        //包路径存到mapperRegistry中
        configuration.addMappers(mapperPackage);
      } else {
        //解析<mapper url="" class="" resource=""></mapper>
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          //读取Mapper.xml文件
          InputStream inputStream = Resources.getResourceAsStream(resource);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream,
          configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream,
          configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }


总结: 通过解析configuration.xml文件,获取其中的Environment、Setting,重要的是将下的所有解析出来之后添加到Configuration,Configuration类似于配置中心,所有的配置信息都在这里。

(2)mapperParser.parse()对 Mapper 映射器的解析



public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    //解析所有的子标签
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //把namespace(接口类型)和工厂类绑定起来
    bindMapperForNamespace();
  }
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
} 
//这里面解析的是Mapper.xml的标签
private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    } 
    builderAssistant.setCurrentNamespace(namespace);
    //对其他命名空间缓存配置的引用
    cacheRefElement(context.evalNode("cache-ref"));
    //对给定命名空间的缓存配置
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //可被其他语句引用的可重用语句块
    sqlElement(context.evalNodes("/mapper/sql"));
    //获得MappedStatement对象(增删改查标签)
    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);
  }
}
//获得MappedStatement对象(增删改查标签)
private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  } 
  buildStatementFromContext(list, null);
}
//获得MappedStatement对象(增删改查标签)
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  //循环增删改查标签
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration,
    builderAssistant, context, requiredDatabaseId);
    try {
      //解析insert/update/select/delete中的标签
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}
public void parseStatementNode() {
  //在命名空间中唯一的标识符,可以被用来引用这条语句
  String id = context.getStringAttribute("id");
  //数据库厂商标识
  String databaseId = context.getStringAttribute("databaseId");
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  } 
  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  //flushCache和useCache都和二级缓存有关
  //将其设置为true后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  //将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true
  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());
  //会传入这条语句的参数类的完全限定名或别名
  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);
  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);
  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);
  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  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;
  } 
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  //从这条语句中返回的期望类型的类的完全限定名或别名
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  //外部resultMap的命名引用
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");
  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");
  } 
  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) {
    statementBuilder.parameterMap(statementParameterMap);
  } 
  MappedStatement statement = statementBuilder.build();
  //持有在configuration中
  configuration.addMappedStatement(statement);
  return statement;
}
public void addMappedStatement(MappedStatement ms){
//ms.getId = mapper.UserMapper.getUserById
//ms = MappedStatement等于每一个增删改查的标签的里的数据
  mappedStatements.put(ms.getId(), ms);
}
//最终存放到mappedStatements中,mappedStatements存放的是一个个的增删改查
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource());


(3)解析bindMapperForNamespace()方法


把 namespace(接口类型)和工厂类绑定起来



private void bindMapperForNamespace() {
  //当前Mapper的命名空间
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      //interface mapper.UserMapper这种
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {} 
    if (boundType != null) {
      if (!configuration.hasMapper(boundType)) {
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }
}
public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
} 
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 {
      //接口类型(key)->工厂类
      knownMappers.put(type, new MapperProxyFactory<>(type));
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }


(4)(configuration)生成SqlSessionFactory对象


总结

XMLMapperBuilder.parse()方法,是对 Mapper 映射器的解析里面有两个方法:

  • configurationElement()解析所有的子标签,最终解析Mapper.xml中的insert/update/delete/select标签的id(全路径)组成key和整个标签和数据连接组成MappedStatement存放到Configuration中的 mappedStatements这个map
  • 里面。
  • bindMapperForNamespace()是把接口类型(interface mapper.UserMapper)和工厂类存到放MapperRegistry中的knownMappers里面

3.SqlSessionFactory的创建

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);


直接把Configuration当做参数,直接new一个DefaultSqlSessionFactory。

相关文章
|
13天前
|
数据采集 消息中间件 监控
Flume数据采集系统设计与配置实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入探讨Apache Flume的数据采集系统设计,涵盖Flume Agent、Source、Channel、Sink的核心概念及其配置实战。通过实例展示了文件日志收集、网络数据接收、命令行实时数据捕获等场景。此外,还讨论了Flume与同类工具的对比、实际项目挑战及解决方案,以及未来发展趋势。提供配置示例帮助理解Flume在数据集成、日志收集中的应用,为面试准备提供扎实的理论与实践支持。
25 1
|
2天前
|
JavaScript IDE 编译器
TypeScript中模块路径解析与配置:深入剖析与最佳实践
【4月更文挑战第23天】本文深入探讨了TypeScript中模块路径解析的原理与配置优化,包括相对路径、Node.js模块解析和路径别名。通过配置`baseUrl`、`paths`、`rootDirs`以及避免裸模块名,可以提升开发效率和代码质量。建议使用路径别名增强代码可读性,保持路径结构一致性,并利用IDE插件辅助开发。正确配置能有效降低维护成本,构建高效可维护的代码库。
|
2天前
|
JSON Java Maven
Javaweb之SpringBootWeb案例之自动配置以及常见方案的详细解析
Javaweb之SpringBootWeb案例之自动配置以及常见方案的详细解析
7 0
Javaweb之SpringBootWeb案例之自动配置以及常见方案的详细解析
|
2天前
|
XML Java 数据库连接
Javaweb之Mybatis的XML配置文件的详细解析
Javaweb之Mybatis的XML配置文件的详细解析
13 0
|
2天前
|
SQL Java 数据库连接
Javaweb之Mybatis的基础操作之新增和更新操作的详细解析
Javaweb之Mybatis的基础操作之新增和更新操作的详细解析
10 0
|
7天前
|
域名解析 网络协议 Linux
TCP/IP协议及配置、IP地址、子网掩码、网关地址、DNS与DHCP介绍
TCP/IP协议及配置、IP地址、子网掩码、网关地址、DNS与DHCP介绍
|
12天前
|
SQL Java 数据库连接
深度解析MyBatis核心:探寻其核心对象的精妙设计
深度解析MyBatis核心:探寻其核心对象的精妙设计
20 1
深度解析MyBatis核心:探寻其核心对象的精妙设计
|
12天前
|
Java 数据库连接 数据库
MyBatis之旅:从零开始的环境搭建与配置
MyBatis之旅:从零开始的环境搭建与配置
25 1
|
Java 数据库连接 mybatis
MyBatis-Spring配置简单了解
在基本的 MyBatis 中,session 工厂可以使用 SqlSessionFactoryBuilder 来创建。而在 MyBatis-spring 中,则使用 SqlSessionFactoryBean 来替代。
850 0
|
1月前
|
SQL Java 数据库连接
挺详细的spring+springmvc+mybatis配置整合|含源代码
挺详细的spring+springmvc+mybatis配置整合|含源代码
41 1

推荐镜像

更多