1.[mybatis]架构的整体流程

1.ORM框架

目录

1.ORM框架

2.传统JDBC的弊端

3.mybatis的整体流程分析

3.1解析配置文件

3.2 SessionFactory的build

3.3 Configuration的解析

3.3.1 Environment的解析

3.3.2 Mapper的解析

 


myBatis这个ORM框架在这些年中,逐渐的进入大家的视野,也是现在企业开发框架中用的比较多的一种ORM框架,其中hibernate也是一种ORM框架,但是个人觉得hibernate可能太重了,适合非常大的项目使用,而且是对hibernate设计理念以及原理都非常清楚的团队来使用;MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJO(PlainOldJavaObjects,普通老式Java对象)为数据库中的记录。

不管是mybatis还是hibernate,都是一种ORM框架,那么ORM到底是什么意思呢?

ORM的全称是Object Relational Mapping(对象关系映射),我自己的理解就是将数据库中的数据映射到我们的对象中,通过面向对象的方式将数据封装成一个一个的对象;

在传统的JDBC操作数据库的过程中,你从数据库查询出一条数据过后,那么返回的结果集是一个MetaData或者说是一个游标,这个时候如果你想要得到这个数据,必须自己从结果集中取出数据,然后如果要封装到一个对象中,那么你还的一个一个的去手动封装,其实ORM框架的底层原理也就是传统的JDBC操作数据库,然后将数据库返回的数据封装到结果集对象中,完成了数据到对象的映射,所以也就叫做ORM框架,所有的ORM框架,不管是mybatis还是hibernate以及各自的企业中自己研发的框架,包括spring data提供的一些jdbc操作,那么底层必定是传统的JDBC操作,像比如一些封装了底层的数据操作,类似于hibernate和mybatis,那么事物怎么控制?还不是就是传统的JDBC的控制方式,设置事物不自动提交,然后自己来提交,涉及到的api也都是rt.jar中的传统JDBC的操作方式。所以说不管什么ORM框架,那么底层操作数据库的原理都是一模一样的,没有任何区别,因为也绕不开。像mybatis或者hibernate,其实就是提供了一种方式,将数据的操作封装到框架中,而你只需要做你关心的事情,类似于spring也是这种思维,你只需要做你想做的事情,其他无关事物本身的事情由框架来帮你完成,而这样就摆脱了一些琐碎的事情,从开发的角度来说,开发效率高了,从维护的成本来说,成本下降而来,从运行的性能来说,也有提高或者平衡。

 

2.传统JDBC的弊端

 

 

其实传统的JDBC的弊端上面已经说了,传统的jdbc的连接的打开和关闭是由开发者去自行控制的,那么一个团队里面的开发人员万一打开没有关闭,或者对于连接的打开和关闭无法掌握,那么一定会出现连接的问题;这里总结出来几点:

1、jdbc底层没有用连接池、操作数据库需要频繁的创建和关联链接。消耗很大的资源

2、写原生的jdbc代码在java中,一旦我们要修改sql的话,java需要整体编译,不利于系统维护

3、使用PreparedStatement预编译的话对变量进行设置123数字,这样的序号不利于维护

4、返回result结果集也需要硬编码。

 

 

3.mybatis的整体流程分析

mybatis大家用的也比较多,我们都知道mybatis有一个全局的配置文件,就是配置数据库连接或者其他的一些配置,比如缓存的配置,插件的配置等,而这个全局的配置文件会被mybatis加载并且解析,数据源的信息配置也在这个全局的配置文件汇总,所以说我们可以认为谁来解析这个配置文件,那么谁有可能得到数据库的连接信息,也就具有操作数据库的能力;我们看下面的代码

 

InputStream input = Resources.getResourceAsStream("MyBatis.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(input);
SqlSession sqlSession = sessionFactory.openSession();
List<User> list = sqlSession.selectList("com.mybatis.example.mapper.UserMapper.queryAll");
list.forEach(System.out::println);

其中myBatis.xml就是我们的mybatis的全局配置文件

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- autoMappingBehavior should be set in each test case -->

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">

            </transactionManager>
            <!-- 这里的type代表的就是数据域的类型,通过类型找到指定的数据源对象-->
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mydb?characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="111111"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
       <mapper resource="mapping/UserMapper.xml" />
    </mappers>

</configuration>

在mybatis中,myBatis.xml全局配置文件最终户解析到一个对象Configuration中,数据库连接的获取包括事务的获取创建等都在里面;所以说SqlSessionFactoryBuilder来解析这个配置文件得到一个SqlSessionFactory(DefaultSqlSessionFactory),那么SqlSessionFactory就具有包含了Configuration对象的所有信息。

 

3.1解析配置文件

 

/**
 * 这里是构建SqlSessionfactory,构建成功会返回一个默认的SqlSessionFactory=DefaultSqlSessionFactory
 * 这个build方法主要就是读取mybatis的配置文件,里面包含了很多的配置信息,
 * 比如数据源的配置,事物的配置;我这里主要是读取resource下面的MyBatis.xml的配置文件
 * 在这个配置文件主要解析了configuration标签,这个就是mybatis的总的上下文的一个配置文件
 * 也就是说它具有加载数据源、执行sql语句,拿到执行结果,这里涉及了好几个感念,其中configuration解析的标签
 * 在mybatis中也有一个对应的对象Configuration,其中解析的结果就会封装成要一个Configuration对象
 * Configuration:管理配置文件mysql-config.xml配置文件
 * SqlSessionFactory:Session管理工厂接口
 * Session:SqlSession是一个面向用户(程序员)的接口,SqlSession中提供了很多操作数据库的方法
 * Executor:执行器是一个接口(基本执行器,缓存执行器)作用:SqlSession内部通过执行器操作数据库
 * MappedStatement:底层封装的对象,对操作数据库存储封装,包括sql语句、输入输出参数
 * StatementHandler:具体操作数据库相关的handler接口
 * ResultSetHandler:具体操作数据库返回结果的handler接口
 * @param inputStream
 * @return
 */
public SqlSessionFactory build(InputStream inputStream) {
  return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    /**
     * inputStream中是我们的全局上下文配置文件的一个输入流对象
     * environment:是环境信息
     * properties:是引入的配置文件之一
     * 这里会将这几个对象进行封装,其中会将inputStream流创建为一个Documet对象,然后返回一个解析的对象
     * XMLConfigBuilder,所做的事情:
     * 1.将inputstream流创建为一个Document对象,然后封装在对象XPathParser中;
     * 2.new Configuration()对象得到configuration;
     * 3.如果environment不为空,设置全局的environment为传入的environment;
     * 4.如果properties不为空,则将传入的properties设置到configuration中的variables中。
     */
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    /**
     * 这里有两个操作
     * 1.parser.parse()方法就是开始解析Mybatis.xml配置文件,得到一个Configuration对象,而这个
     * Configuration对象就是上面new出来的configuration对象;
     * 2.调用build方法将上下文configuration对象封装成一个DefaultSqlSessionFactory,也就是我们常常
     * 看见的SqlSessionFactory
     * 所以核心的重点是parser.parse()方法,需要重点分析
     */
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

上面的总体流程就是:

1.解析配置文件(myBatis.xml);

2.将每个标签解析都创建一个对象,比如解析environment标签,会解析到一个Environment对象,最后解析完会设置到Configuration对象中,所以我们从配置文件的结构来看是有层级关系的,也即是可以说是继承关系,但是在mybatis的源码实现中,不是采用的继承关系,而是组合关系模式。

3.最后将所有的 标签都解析出来创建一个相对应的对象然后设置到configuration中。

4.最后根据configuration对象创建一个DefaultSqlSessionFactory对象。

5.最后将创建的DefaultSqlSessionFactory返回。

 

3.2 SessionFactory的build

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    /**
     * inputStream中是我们的全局上下文配置文件的一个输入流对象
     * environment:是环境信息
     * properties:是引入的配置文件之一
     * 这里会将这几个对象进行封装,其中会将inputStream流创建为一个Documet对象,然后返回一个解析的对象
     * XMLConfigBuilder,所做的事情:
     * 1.将inputstream流创建为一个Document对象,然后封装在对象XPathParser中;
     * 2.new Configuration()对象得到configuration;
     * 3.如果environment不为空,设置全局的environment为传入的environment;
     * 4.如果properties不为空,则将传入的properties设置到configuration中的variables中。
     */
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    /**
     * 这里有两个操作
     * 1.parser.parse()方法就是开始解析Mybatis.xml配置文件,得到一个Configuration对象,而这个
     * Configuration对象就是上面new出来的configuration对象;
     * 2.调用build方法将上下文configuration对象封装成一个DefaultSqlSessionFactory,也就是我们常常
     * 看见的SqlSessionFactory
     * 所以核心的重点是parser.parse()方法,需要重点分析
     */
    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 的创建涉及了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;
}

public Configuration() {
  /**
   * 这里初始化类型别名注册器
   * typeAliasRegistry主要注册的是比如像SqlsessionFactory的一些类别
   * 事务工厂的类和一些其他的类
   *
   * typeAliasRegistry中的key和value最终都会注册到
   * TypeAliasRegistry类中的map typeAliases中
   * 比如在解析配置文件的configuration中的数据源的时候
   * 通过数据源的type就可以在typeAliases中根据type,比如POOLED
   * 得到一个数据源工厂PooledDataSourceFactory
   */
  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);
  .......

通过构建XMLConfigBuilder 对象将Configuration进行初始化得到一个对象Configuration,然后解析完成过后返回这个对象,最后设置到DefaultSqlSessionFactory中,那么DefaultSqlSessionFactory也就可以调度Configuration对象中的功能。

 

3.3 Configuration的解析

 

public Configuration parse() {
  /**
   * 这个parsed代表的意思就是说mybatis上下文配置文件是否已经被解析过了,如果在一个线程中
   * 被多次解析,那么就要报错,因为不能创建多个Configuration对象,比如像我们的dataSouce,相同
   * 的数据库连接可能在一个系统中存在多个吗,显然是没有必要的,所以这里
   * 用了一个parsed参数来控制,万一你调用了多次的parse方法,那么肯定有一次是失败的
   * 简单来说就是一个XMLConfigBuilder对象只能调用一次parse方法进行解析配置初始化Configuration
   */
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  //如果没有被解析过,下面马上就要解析了,所以设置是否解析过的参数为true,代表已经解析过
  parsed = true;
  //这个就是核心的解析方法,主要解析configuration标签
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    /**
     * 这里是解析的properties,这个是干什么的呢,像在一般的系统中,mybatis的配置文件中常常
     * 会引入一个必然jdbc.properties文件,所以这里就是解析引入了外部的properties文件的
     * 解析了jdbc.properties文件过后,会将mybatis中使用了properties中的参数进行替换
     *
     * 下面的代码其实都是解析mybatis的配置文件,也就是configuration标签的
     * 从下面的代码可以看出这个配置文件的标签是有顺序的,不可以乱写顺序的,比如说properties
     * 这个标签必须要在最开始写,如果写到中间或者最后是要报错的,因为代码就是这样写的
     * 但是从宏观来看,也是必须的,你首先要将引入的文件解析到变量表中,然后下面引用它的变量才能拿的到
     */
    propertiesElement(root.evalNode("properties"));
    /**
     * 解析settigns
     */
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(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"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    /**
     * 解析environments,这个就比较重要了,这里就是去解析我们的数据源那些
     */
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    /**
     * 这里就是解析我们的mappers,mappers就是配置了项目中的mybatis的sql配置文件
     *
     */
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

3.3.1 Environment的解析

/**
 * 这里是解析configuration中的environment标签和子标签的方法
 * @param context
 * @throws Exception
 */
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    /**
     * 这里就是设置environment的值,在configuration标签中需要设置一个
     * default的ID值,这个必须要设置,而且下面的environment中的id值一样
     * 否则就不解析environment标签的内容
     */
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      //获取environment中的id值,和configuration中的default的值对比,如果
      //说两个值是一样的,才能进行解析environment中的值
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
        //这里先拿到事务工厂,事务工厂比如你在environment中配置了transactionManager
        //type=JDBC,那么这里的事务工厂得到就是JdbcTransactionFactory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //数据源工厂,解析标签datasource,就是拿到数据库的连接信息,然后通过反射得到实例对象
        //初始化连接池
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        //通过数据源工厂得到一个数据源DataSource
        DataSource dataSource = dsFactory.getDataSource();
        //通过建造者模式得到一个Environment对象,将获取的事务工厂和数据源信息封装到environmentBuilder对象中
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        //最后将构建好的环境对象Environment设置到configuration中
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

Environment的解析结果就是将Environment标签都解析出来然后放入创建的Environment对象中,然后在设置到全局的configuration对象中。

 

3.3.2 Mapper的解析

 

/**
 * 从官网上我们知道mybatis有好几种配置mapper的方法
 * 其中有
 * resource的方式:
 * <mappers>
 *   <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
 * </mappers>
 * url的方式:
 * <mappers>
 *   <mapper url="file:///var/mappers/PostMapper.xml"/>
 * </mappers>
 * 包的方式:
 * <mappers>
 *   <package name="org.mybatis.builder"/>
 * </mappers>
 *
 * 其中package的方式的优先级最高
 * 其中mappers中的每个mapper都遵循下面的代码逻辑:
 * 1.一个mapper中只能定义package、url、class、resouce的方式之一;
 * 2.其中package的优先级最高,最先解析package,如果package存在,其他的忽略不解析;
 * 3.否则开始解析resource(条件是url和class必须为空,否则报错);
 * 4.解析url和class都一样的,当解析到url或者class的时候,其他两个配置的属性必须要为空。
 *
 * @param parent
 * @throws Exception
 */

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        //解析package的方式,直接将配置的package添加到configuration中
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        //获取resource,url,mapperClass
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        //解析resouce的方式
        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) {
          //解析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.");
        }
      }
    }
  }
}

 

上面的 mapper的解析有好多中方式,其中其实就分了两种,一种是xml文件的解析,一个是类方式的解析,这里分析下xml的解析

 

public void parse() {
  //isResourceLoaded去重的功能 ,代表已经解析过的资源文件
  if (!configuration.isResourceLoaded(resource)) {
    //解析mapper
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}
private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    //解析parameterMap,解析出来过后根据parameterMap的id作为key,解析出来的属性封装成一个对象作为value
    //放入map中添加到Configuration中的parameterMaps
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //解析resultMap,一样的逻辑
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //解析sql标签
    sqlElement(context.evalNodes("/mapper/sql"));
    /**
     * 这里就是解析select 、insert、delete、update的标签
     */
    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);
  }
}

buildStatementFromContext/buildStatementFromContext

/**
 * 这里就是解析<select id="queryAll" resultMap="all" resultType="com.mybatis.example.entity.User" >
 *   这种标签的,当然还包括了insert 、update、delete标签
 * @param list
 * @param requiredDatabaseId
 */
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    //这里的XMLStatementBuilder就是解析xml中声明sql的一个builder
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      //开始解析
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

最后调用 statementParser.parseStatementNode()select|insert|update|delete的标签解析成一个一个的声明对象,放入到configuration中mappedStatements的map中。

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值