zoukankan      html  css  js  c++  java
  • mybatis源码分析:启动过程

    mybatis在开发中作为一个ORM框架使用的比较多,所谓ORM指的是Object Relation Mapping,直译过来就是对象关系映射,这个映射指的是java中的对象和数据库中的记录的映射,也就是一个java对象映射数据库中的一条记录。了解了mybatis的背景及作用下面看mybatis的使用及从源码分析启动过程。

    一、概述

    要使用mybatis必须要引入mybatis的jar包,由于我这里需要查看源码,使用的mybatis源码作为依赖。首先需要下载源码,可执行从github上下载,mybatis下载下来是maven工程,按照maven导入的方式进行导入即可,详细的步骤在这里不在赘述。

    引入了mybatis的依赖便可以开发mybatis的程序,我这里使用的源码版本为:3-3.4.x版本。

    1、核心配置文件

    mybatis核心配置文件,一般命名为mybatis-config.xml,说是核心配置文件一点也不错,这个文件包含了使用mybatis的时候的所有配置,只有正确加载了此文件,mybatis才可正常工作。下面是mybatis-config.xml文件,

    <?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>
        <settings>
            <!-- 设置日志输出为LOG4J -->
            <setting name="logImpl" value="LOG4J" />
            <!--将以下画线方式命名的数据库列映射到 Java 对象的驼峰式命名属性中-->
            <setting name= "mapUnderscoreToCamelCase" value="true" />
        </settings>
        <!--简化类命名空间 -->
        <typeAliases>
           
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC" />
                <dataSource type="UNPOOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver" />
                    <property name="url"
                        value="jdbc:mysql://127.0.0.1:3306/test" />
                    <property name="username" value="user" />
                    <property name="password" value="user" />
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--常规做法-->
            <mapper resource="cn/com/mybatis/dao/UserMapper.xml"/>
            <mapper resource="cn/com/mybatis/dao/MenuMapper.xml"/>
            <!--第二种做法-->
            <!--  
            <package name="cn.com.mybatis.dao"/>
            -->
        </mappers>
    </configuration>

    上面是一个mybatis-config.xml文件的实例,在configuration标签中配置了mappers、settings、environments等标签,这些标签代表的意思及如何解析在后面会详细分析。

    这里sql的配置方式有注解和映射文件两种方式,这里采用映射文件的方式,所以在mybatis-config.xml文件中配置了Mapper文件,下面看UserMapper.xml文件,

    <?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="cn.com.mybatis.dao.UserMapper">
      <select id="selectUser" resultType="hashmap">
        select * from e_user 
      </select>
    </mapper>

    上面的UserMapper.xml只有一个select标签,另外在mapper标签中配置了namespace属性,这个属性很关键,代表的是一个应映射文件对应的接口。下面看UserMapper接口,

    package cn.com.mybatis.dao;
    
    import java.util.HashMap;
    import java.util.List;
    
    public interface UserMapper {
    
        public List<HashMap> selectUser();
    }

    细心的读者会发现接口中的方法名和映射文件中的select标签的id是一样的,没错这里必须是一致,必须一一对应,至于为什么要保持一致,后面会通过源码分析,并且在一同一个namespace中不能包含同名的方法,也就是映射文件中的id不允许重复。

    有了上面的这些配置,便可以开始mybatis之旅了,下面看下每个文件的位置,

    二、详述

    上面已经把mybatis的环境及代码已经分析了,下面看测试代码,

    package cn.com.mybatis.test;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.List;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import cn.com.mybatis.dao.UserMapper;
    
    public class TestMybatis {
    
        public static void main(String[] args) throws IOException {
            // TODO Auto-generated method stub
            //加载核心配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    //生成一个SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder builder
    = new SqlSessionFactoryBuilder();
    //创建一个SqlSessionFactory对象 SqlSessionFactory factory
    = builder.build(inputStream);
    //获得一个SqlSession对象 SqlSession session
    =factory.openSession();
    //获得一个UserMapper UserMapper userMapper
    =session.getMapper(UserMapper.class); List<HashMap> users=userMapper.selectUser(); System.out.println(users.size()); } }

    上面的代码,即使用mybatis的过程,下面来分析。

    1、读取配置文件

    下面看读取mybatis核心文件的代码,

    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

    这里就一句话,把mybatis-config.xml转化为InputStream对象,这里mybatis-config.xml文件是在类路径下(WEB-INF/classes)下,这里Resources类是如何读取文件,后续详细分析,只要明白这里会获得InputStream就好。

    2、创建SqlSessionFactoryBuilder

    下面需要创建一个SqlSessionFactoryBuilder对象,看这个类名可以猜到应该使用的是建造者模式,

    SqlSessionFactoryBuilder  builder  = new SqlSessionFactoryBuilder();

    看下具体的SqlSessionFactoryBuilder类,下面是其所有的方法,

    可以看到都是build方法,那么其构造方法也就是默认的。在SqlSessionFactoryBuilder类中所有的方法都是build方法,这是标准的建造者模式,可以看到返回值都是SqlSessionFactory。在mybatis中很多地方都使用了建造者模式,后边会进行专门的分析。

    下面看生成SqlSessionFactory,

    SqlSessionFactory  factory  = builder.build(inputStream);

    调用的SqlSessionFactoryBuilder的build(InputStream)方法,

    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
      }

    又调用下面的方法,

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

    在上面的代码中,使用inputStream生成一个XMLConfigBuilder,这里又是一个建造者模式,看XMLConfigBuilder的构造方法,

    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    //调用下面的构造方法
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //初始化父类BaseBuilder类的configuration
    super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }

    下面看其parse()方法,此方法构造的是在这里建造的对象是Configuration对象,

    public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
    //解析mybatis-config.xml文件中的<configuration>标签,把该标签中的内容 parseConfiguration(parser.evalNode(
    "/configuration")); return configuration; }

    从上面的代码中,可以看出调用parseConfiguration方法,这个方法就是解析<configuration>标签,如下,

    private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          //解析properties标签    
          propertiesElement(root.evalNode("properties"));
          //解析settings标签
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          //解析别名标签,例<typeAlias alias="user" type="cn.com.bean.User"/>
          typeAliasesElement(root.evalNode("typeAliases"));
          //解析插件标签
          pluginElement(root.evalNode("plugins"));
          //解析objectFactory标签,此标签的作用是mybatis每次创建结果对象的新实例时都会使用ObjectFactory,如果不设置
          //则默认使用DefaultObjectFactory来创建,设置之后使用设置的
          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>标签
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }

    上面的方法后面会逐一进行分析,主要就是解析核心配置文件mybatis-config.xml中的配置,并放到Configuration对象中。

    再回到上面的build(parse.parse())方法,其定义如下,

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

    从上面的代码,可以看到使用configuration生成一个DefaultSqlSessionFactory对象。

    3、创建SqlSessionFactory

    上面分析到SqlSessionFactoryBuilder最后会返回一个DefaultSqlSessionFactory对象,

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
      }

    可以看到,把Configuration对象直接赋值给了DefaultSqlSessionFactory对象的configuration属性。

    4、创建SqlSession

    SqlSession session=factory.openSession();

    上面的代码调用factory的openSession()方法,也就是DefaultSqlSesssionFactory的openSession()方法,

    @Override
      public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }

    调用了下面的方法,

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            //获得mybatis核心配置文件中的environment信息,包括dataSource id trasacationFactory
          final Environment environment = configuration.getEnvironment();
          //获得transactionFactory,如果environment中没有则使用ManagedTransactionFactory
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          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();
        }
      }

    上面的方法返回了一个DefaultSqlSession对象,具体的过程就是使用上面的参数构造一个DefaultSqlSession对象。

    5、获取Mapper对象

    使用下面的代码获取一个Mapper对象,有了Mapper对象便可以调用方法,进行数据库操作,

    UserMapper userMapper=session.getMapper(UserMapper.class);

    上面的代码从DefaultSqlSession中调用getMapper返回一个UserMapper对象,这里UserMapper是一个代理对象,至于为什么是代理对象,先不分析,先了解其过程,

    @Override
      public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
      }

    可以看出是从DefaultSqlSession的Configuration中获得该Mapper,下面继续看,

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //返回的是下面的方法
    return mapperRegistry.getMapper(type, sqlSession); } //调用此方法 @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 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); } }

    从上面中可以看到从knownMappers中根据type,这里就是UserMapper.class返回一个MapperProxyFactory,最后返回MapperProxyFactory的一个实例,

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

    下面看newInstance方法,

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

    到这里我们可以看到最终返回的是一个代理对象,而且是JDK动态代理的一个对象,从我们只编写了接口也可以猜出这里返回的应该是一个JDK动态代理的类,因为JDK动态代理要求必须有接口。

    6、执行操作

    执行操作,则直接调用其方法即可,

    List<HashMap> users=userMapper.selectUser();

    从上面的分析制定useMapper是代理对象,那么代理类便是上面的MapperProxy类,那么执行selectUser方法,便会执行MapperProxy的invoke方法,那么该类肯定也会实现InvocationHandler接口,下面,

    在看其invoke方法,

    @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          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);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
    //调用的是execute方法
    return mapperMethod.execute(sqlSession, args); }

    从上面可以看到调用的是mapperMethod.execute方法,并且把sqlSession方法作为参数传进去。那也就是说最后调用的sqlSession的方法,下面看,

    可以看到调用的sqlSession的方法,从这里大体可以看出sqlSession是个重要的类。

    三、总结

    上面分析了mybatis的启动过程,包括加载核心配置文件(mybatis-config.xml)、SqlSessionFactory、SqlSession、执行操作数据库方法。这里仅仅分析了其执行过程,很多细节后续会一一分析,像加载配置文件、Configuration类、DefaultSqlSession以及如何通过接口找到对应的Mapper文件等内容。

    有不正之处,欢迎指正。

  • 相关阅读:
    CentOS6.8编译安装lnmp(一)- 依赖库
    PHP7废弃$GLOBALS["HTTP_RAW_POST_DATA"]
    利用python处理自动化任务之查看文件夹大小和内容
    利用python处理自动化任务之批量新建文件夹(2)
    python处理自动化任务之批量新建文件夹
    利用python处理自动化任务之批量修改文件夹的名字
    利用python完成自动化的任务之遍历文件夹修改文件之后并保存备份
    python Matplotlib给坐标轴标签添加文本框
    python-matplotlib添加水平和垂直的直线
    python-matplotlib绘制堆积的条形图
  • 原文地址:https://www.cnblogs.com/teach/p/12697660.html
Copyright © 2011-2022 走看看