MyBatis原理分析(源码+调式步骤)

前期准备

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.ith</groupId>
    <artifactId>mybatis-demo4-design</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

实体类:

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
}

dao接口:

public interface IUserDao {
    List<User> findAll();
}

SqlMapConfig.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">

<!--mybatis的主配置文件-->

<configuration>
    <environments default="mysql">
        <!--配置mysql环境-->
        <environment id="mysql">
            <!--配置事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源(数据库连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!--指定映射配置文件的位置-->
    <mappers>
        <mapper resource="cn/ith/dao/IUserDao.xml"/>
    </mappers>
</configuration>

IUserDao.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.ith.dao.IUserDao">
    <!--配置查询所有-->
    <select id="findAll" resultType="cn.ith.domain.User">
        select * from user
    </select>
</mapper>

原理分析前应具备的知识

JDBC原生写法简述和示例

DriverManager:驱动管理对象
作用:

  • 注册驱动(告诉程序使用那个驱动jar包)
  • 获取数据库连接

Connection:数据库连接对象
作用:

  • 获取执行sql的对象
  • 管理事务
    开始事务 setAutoCommit(false)
    提交事务 commit()
    回滚事务 rollback()

ResultSet:结果集对象

Statement:执行sql的对象

  • boolean execute(String sql):可以执行任意sql(很少用)
  • int executeUpdate(String sql):执行DML(insert、update、delete)、DDL(create、alter、drop)
  • ResultSet executeQuery(String sql):select

PreparedStatement:执行sql的对象

示例

//查询
public static void test()  throws Exception {
	Connection con = null;
    ResultSet result = null;
    PreparedStatement pstmt = null;
    try{
        Class.forName("com.mysql.jdbc.Driver");
        con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false", "root", "root");
        
        String sql = "select * from t where name = ?";
        pstmt = con.prepareStatement(sql);
        pstmt.setString(1, "张三");
        result = pstmt.executeQuery();

        while(result.next()){
            int id = result.getInt(1);
            String name = result.getString("name");

            System.out.println(id);
            System.out.println(name);
        }


    }catch (ClassNotFoundException e){
        e.printStackTrace();
    }catch (SQLException e) {
        e.printStackTrace();
    }finally {
        if(result != null) {
            try{
                result.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
        if(pstmt != null) {
            try{
                pstmt.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
        if(con != null) {
            try{
                con.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}

MyBatis原理分析

总体流程和用到的类/接口

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。【来自MyBatis官网】

  • SqlSessionFactoryBuilder类 :用于创建SqlSessionFactory的工厂类
  • SqlSessionFactory接口 :用于创建SqlSession的工厂类
  • SqlSession接口 :这才是和数据库进行交互的核心类。SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。就是test2中的写法,调用SqlSession提供的selectList方法。不过现在有了一种更简洁的方式——test1的写法:Mapper接口的方式。

MyBatis执行流程分为六个步骤,在下面的代码中展示:

public class MyBaitsTest {
	@Test
    public static void test1(String[] args) throws Exception{
        //1读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //4使用SqlSession创建Dao接口的代理对象
        IUserDao userDao = session.getMapper(IUserDao.class);
        //5使用代理对象执行方法
        List<User> users = userDao.findAll();
        users.stream().forEach(System.out::println);
        //6释放资源
        session.close();
        in.close();
    }
	//或者这种写法
	@Test
    public static void test2(String[] args) throws Exception{
       //1读取配置文件
       InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
       //2创建SqlSessionFactory工厂
       SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
       SqlSessionFactory factory = builder.build(in);
       //3使用工厂生产SqlSession对象
       SqlSession session = factory.openSession();
//        //4使用SqlSession创建Dao接口的代理对象
//        IUserDao userDao = session.getMapper(IUserDao.class);
//        //5使用代理对象执行方法
//        List<User> users = userDao.findAll();
       List<User> users = session.selectList("cn.ith.dao.IUserDao.findAll");
       users.stream().forEach(System.out::println);
       //6释放资源
       session.close();
       in.close();
   }
}

开始断点调试分析源码(先使用传统的test2方式做分析)

初始化配置信息

SqlSessionFactory factory = builder.build(in);打断点进入build方法中,看看SqlSessionFactoryBuilder的实例对象是如何创建SqlSessionFactory对象的。
源码

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

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
  	//解析配置文件(SqlMapConfig.xml和IUserDao.xml)
    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.
    }
  }
}
//从这里可以看出,调用build方法,会最终返回一个DefaultSqlSessionFactory对象
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

Configuration简介
Configuration对象的属性包含了SqlMapConfig.xml和IUserDao.xml中的所有配置项信息。下面的dataSource中的四个关于数据库连接的参数是不是很眼熟呢?对,这就是我们在SqlMapConfig.xml中配置的信息。
在这里插入图片描述
还有mappedStatements属性(MappedStatement类类型,和Configuration一样,是一个很重要的类,后面做介绍),就是我们在IUserDao.xml中的配置信息
在这里插入图片描述
也就是说,调用build()方法,主要做了两件事:解析配置文件,存入Configuration对象中;返回一个DefaultSqlSessionFactory对象。
初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部的属性中。

DefaultSqlSessionFactory和SqlSessionFactory介绍
DefaultSqlSessionFactory类是SqlSessionFactory接口的实现类。DefaultSqlSessionFactory类中主要包含一个Configuration类型的对象属性和多个重载的openSession()方法。

现在,已经拿到了SqlSessionFactory对象,有个这个对象,就能使用openSession(),创建SqlSession。

获取SqlSession

SqlSession简介
SqlSession是一个接口,DefaultSqlSession是它的一个实现类。SqlSession是MyBatis中用于和数据库交互的顶层类,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,并且在使用完毕后需要close。
其中有两个重要的属性:
在这里插入图片描述

  • configuration就是上面提到的Configuration类型的变量
  • Executor也是一个接口,和执行具体的SQL语句相关。它有三个常用的实现类BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statements),SimpleExecutor(普通的执行器,默认)。

继续调试,SqlSession session = factory.openSession();
源码:

@Override
public SqlSession openSession() {
  //getDefaultExecutorType()传递的是SimpleExecutor
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别(默认使用数据库的隔离级别),autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType,  	 TransactionIsolationLevel level, boolean autoCommit) {
	
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //根据参数创建指定类型的Executor
    final Executor executor = configuration.newExecutor(tx, execType);
    //返回的是DefaultSqlSession
    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();
  }
}

这一步的主要作用就是获得 SqlSession 的实例 (即DefaultSqlSession对象),打开DefaultSqlSession类的源码可以发现里面提供了很多selectOne、selectMap等方法来操作数据库。 也就是说,DefaultSqlSession对象完全包含了面向数据库执行 SQL 命令所需的所有方法

执行selectList方法

继续调式List<User> users = session.selectList("cn.ith.dao.IUserDao.findAll");
源码:

@Override
public <E> List<E> selectList(String statement) {
  return this.selectList(statement, 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 {
  	//根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
    MappedStatement ms = configuration.getMappedStatement(statement);
    //调用Executor中的方法处理
    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简介
MappedStatement与Mapper配置文件中的一个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中,主要用途是描述一条SQL语句。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。
在最开始的初始化配置信息讲解中,传入SqlMapConfig.xml配置文件,其中的:

<mappers>
    <mapper resource="cn/ith/dao/IUserDao.xml"/>
</mappers>

mappers标签用来引入mapper.xml文件或者配置mapper接口的目录。因此,IUserDao.xml也进行了解析,封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements属性中(也就是最开始截图那部分显示的mappedStatements属性信息)。mappedStatements 是一个HashMap,存储时key = 全限定类名 + 方法名,value = 对应的MappedStatement对象。

configuration.getMappedStatement(statement)这一句根据key来获取值:
在这里插入图片描述
继续调式,进入executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)的query中。

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
    @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, parameterObject, 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);
  }
    @Override
  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++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 如果缓存中没有本次查找的值,那么从数据库中查询
        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;
  }
    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 {
      // 查询的方法
      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;
  }
  //进入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 = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

在doQuery方法中,终于看到了Statement类型的变量,这就是我在原理分析前应具备的知识中提到的执行sql的对象。
现在,进入handler.<E>query(stmt, resultHandler);的query方法中:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.<E>query(statement, resultHandler);
  }
  
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

很开心,终于看到了execute方法,ps变量中包含了执行的sql语句等消息。
在这里插入图片描述
继续进入resultSetHandler.<E> 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++;
    }

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

    return collapseSingleResultList(multipleResults);
  }

听名字这个方法就是用于处理结果集的,结果集已经出来了:
在这里插入图片描述

用test1接口的方式分析

把断点打到这里:IUserDao userDao = session.getMapper(IUserDao.class);
先思考一个问题IUserDao 是一个接口类,为什么可以调用findAll()方法,答案是动态代理。下面就来分析这个过程,进入getMapper方法中:

  //此处跳过两个方法

  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);
    }
  }
  
  //进入mapperProxyFactory.newInstance(sqlSession);的newInstance方法中:
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

  //接下来调用这个newInstance
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
  
  //接下来调用这个newInstance
  //在这里可以看到通过Proxy.newProxyInstance的方式返回了一个反向代理
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

接下来调式List<User> users = userDao.findAll();进入findAll方法中,第一个执行的就是invoke方法(通过反射返回的代理对象调用方法时,首先进入的就是invoke方法去执行前置通知)

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //判断调用是是不是Object中定义的方法,toString,hashCode这类非。是的话直接放行。
      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);
    // 重点在这:MapperMethod最终调用了执行的方法
    return mapperMethod.execute(sqlSession, args);
  }

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断mapper中的方法类型,最终调用的还是SqlSession中的方法
    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);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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() + ").");
    }
    //result就是返回的结果集
    return result;
  }

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      //程序进入到这个语句,可以看到,虽然是通过动态代理的方式执行findAll()方法,程序最终还是调用的是SqlSession对象提供的操作数据库的接口。
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

在这里插入图片描述

注解的方式

上面提到的是mapper.xml配置文件的方式写sql信息,然后读取配置文件,封装成MappedStatement对象。如果是注解的方式,又是怎样获取到sql信息呢?其实xml配置的方式注解的方式对于封装得到的MappedStatement对象是一样的,只是如果我们在SqlMapConfig.xml中配置了<mapper class="cn.ith.dao.IUserDao"/>,那么解析器认定我们使用的是注解的方式,如果我们配置了<mapper resource="cn/ith/dao/IUserDao.xml"/>,那么解析器认定我们使用的是配置xml的方式,然后采取不同的方法去读取信息,封装MappedStatement对象。
接下来可以简单看下源码,可以在SqlSessionFactory factory = builder.build(in);这一句上打断点,然后进入build(),再进入build(),就能看到build(parser.parse());,这里就是开始MyBatis配置文件的解析,进入到parse()中:

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  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 {
      //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"));
      //从这里进入mappers映射配置信息的解析
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

  //这个方法就是判断我们使用的是注解还是xml配置的方式,并进行解析操作
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        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.");
          }
        }
      }
    }
  }

总结

以上面示例中的findAll方法总结MyBatis的执行流程。
(1)读取SqlMapConfig.xml配置信息中的environment部分,就能创建数据库连接的Connection对象(注册驱动,获取连接)。(用dom4j技术解析xml)
(2)读取SqlMapConfig.xml配置信息中的mappers部分,就能获取到映射配置文件或者是注解(如果是通过dao接口上写注解的方式),然后封装成MappedStatement对象(这其中就包含了sql语句,返回值类型等消息)。
以上两个文件的解析信息都放在了Configuration对象的属性中。
(3)得到SqlSession对象(实际上是它的实现类DefaultSqlSession对象,其中有个重要的属性:executor,执行器,用于调用查询方法,最终调用到原生的JDBC接口查询数据库)
(4)执行sql语句(如果是传统方式,则直接调用DefaultSqlSession对象提供的方法,如果是接口方式,则利用动态代理返回dao接口的代理对象)
(5)得到结果集
在这里插入图片描述

附录

参考:
https://blog.csdn.net/weixin_43184769/article/details/91126687
https://mybatis.org/mybatis-3/zh/getting-started.html

发布了243 篇原创文章 · 获赞 87 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/IT_10/article/details/103864644
今日推荐