Mybatis学习笔记(五)-Mybatis源码分析

Scroll Down

Mybatis学习笔记(五)-Mybatis源码分析

image-20200427114249784

接口层作用:

  • 提供给外部使用的接口API,开发人员通过接口API操作数据库,接口层接到请求后调用数据处理层完成具体的数据处理
  • mybatis和数据库的交互方式有两种
    • 使用传统的mybatis提供的API
    • 使用mapper代理的方式

数据处理层作用:

  • 负责具体的sql查找,sql解析,sql执行与执行结果映射处理等,主要是根据调用的请求完成一次数据库操作

框架支撑层作用:

  • 负责最基础的功能支撑,包括连接管理,事务管理,配置加载和缓存处理,作为mybatis的基础组件,为上层数据处理提供最基础的支撑

Mybatis的主要构件

构件作用
SqlSession作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
ExecutorMyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
StatementHandler封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler负责对用户传递的参数转换成JDBC Statement 所需要的参数。
ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合。
TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换。
MappedStatementMappedStatement维护了一条<select
SqlSource负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回。
BoundSql表示动态生成的SQL语句以及相应的参数信息。
ConfigurationMyBatis所有的配置信息都维持在Configuration对象之中。

image-20200427121252527

mybatis的总体流程

  1. 加载配置文件并初始化,配置来源于配置文件与java代码中的注解,将主配置信息解析并封装到Configuration中,将sql的配置信息加载称为一个mapedstatement对象,存储在内存中,也封装在Configuration中
  2. 接收调用请求,将sql的对应id与参数传给下层的数据处理层进行操作
  3. 处理操作请求
    1. 根据sql的ID查找对应的MapperedStatement对象
    2. 根据传入的参数解析MappedStatement对象,得到最终的执行传入参数
    3. 获取数据库连接,根据得到的最终sql语句和执行传入参数到数据库执行,并得到执行结果
    4. 根据MappedStatement对象中的结果映射配置得到的执行结果进行转换处理,并得到最终的处理结果
    5. 释放连接资源
  4. 返回处理结果

传统调用mybatis 的API形式源码解析

调用示例:

public void test1() throws IOException {
        //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.解析了配置文件,并创建了sqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.生产sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
                                                                 //在进行增删改操作时,要手动提交事务
        //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
        List<User> users = sqlSession.selectList("user.findAll");
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

1.先来看如何解析配置文件并创建一个对应的SqlSessionFacotry的

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        //解析XML,XMLConfigBuilder专门用于解析mybatis配置文件
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //parser.parse()返回的就是Configuration对象,这个对象就是最核心的配置类了
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

Mybatis初始化的时候,将所有的配置信息都会加载到Configuration实例中,下面就是解析

public Configuration parse() {
    //判断是否已经解析过了,解析过就抛异常
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //解析 properties标签,
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //解析 Vfs实现类,
      loadCustomVfs(settings);
      //解析  typeAliases 标签
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析  plugins标签
      pluginElement(root.evalNode("plugins"));
      //解析  objectFactory标签
      objectFactoryElement(root.evalNode("objectFactory"));
       //解析  objectWrapperFactory标签  
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
       //解析  reflectorFactory标签
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //将 setting 标签内容解析到Configuration属性中  
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析  environments 标签
      environmentsElement(root.evalNode("environments"));
       //解析  databaseIdProvider标签
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
       //解析  typeHandlers标签
      typeHandlerElement(root.evalNode("typeHandlers"));
       //解析  mappers标签
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

其中mappers标签就是我们Sql.xml中写的对应的sql标签与内容,解析mapper标签的源码如下:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
         //如果是 packge 类型的,需要将packge下面所有的mapper都进行解析 
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

addMapper方法源码

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

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
      public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }
      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);
         //这里将对应的mapper中的sql语句给绑定到对应的config配置类中
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

}

最终在Configuration类中,将对应所有的标签解析并封装成一个MappedStatement对象,存储在Configuration类中的mappedStatements属性总,mappedStatements是一个HashMap,存储时key = 全限定类名 + 方法名,value 就是对应的MappedStatement对象

MappedStatement对象与Mapper配置文件中的 select/update/insert/delete节点相对应,mapper中配置的标签都封装了此对象中,主要用途用于描述一条Sql语句

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

public void addMappedStatement(MappedStatement ms) {
  mappedStatements.put(ms.getId(), ms);
}