Mybatis基本原理及框架设计流程

前言:本文主要阐述Mybatis中的核心原理,省略了其他跟核心原理相对来说没那么重要的部分。

先来一段mybatis的简单使用代码:

public class UserDaoTest {

    @Test
    public void findUserById() {
       SqlSessionFactory sessionFactory = null;
       String resource = "configuration.xml";
        try {
            sessionFactory = new SqlSessionFactoryBuilder().build(Resources
                    .getResourceAsReader(resource));
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSession sqlSession = sessionFactory.openSession();
        UserDao userMapper = sqlSession.getMapper(UserDao.class);
        User user = userMapper.findUserById(2);
        Assert.assertNotNull("没找到数据", user);
    }

}

上面是我在网上随便找的一段Mybatis的简单使用步骤,我们就根据上述代码来分析一下mybatis的实现过程。

 

第一步:首先框架肯定需要去获取用户配置的文件(SqlMapConfig.xml)并解析,这里先写两个必要的配置信息:

1、连接数据库所需要的参数    2、用户写的mapper文件(存放具体的sql语句)

采用面向对象的思想,mybatis会把得到的这些配置信息封装成两个对象MappedStatement以及Configuration,介绍下这两个类的作用;

MappedStatement:存放mapper里面的每一条sql标签的对象,例如<select>、<update>等,这些标签里面的用户自定义的sql语句,输入参数,返回类型等都会转换为MappedStatement对象的相应属性。简单点说,一个Mapper里面定义的增删查改的标签,就是一个MappedStatement对象

Configuration:存放数据库的基本信息,以及Map<String,MappedStatement>,这个Map就是用来定位到具体用的哪个MappedStatement对象,key是mapper xml里面的namespace+标签的id来保证唯一性。
 

了解完两个基础的对象之后,我们开始继续分析,下面就应该是解析用户配置文件:

Mybatis框架中,SqlSessionFactoryBuilder.build()方法会把用户配置文件通过demo4j进行解析,然后把解析出来的内容封装到MappedStatement和Configuration对象里面去,然后根据解析完成的Configuration对象生成SqlSessionFactory对象,部分关键的源码如下(只截取了类中部分代码):

public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        //1、这一步就是通过dom4j去解析,有兴趣可以跟进去看一下
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(Configuration config) {
    //2、这一步就是通过Configuration 对象去生成SqlSessionFactory对象
    return new DefaultSqlSessionFactory(config);
  }
}

第二步:获取到了SqlSessionFactory,我们就可以直接用SqlSessionFactory.openSeession来获取SqlSession对象。

这一步可以说是mybatis原理中比较重要的一步了,在前面的描述中,我们已经知道了在构建SqlSessionFactory的时候需要Configuration 对象,因此SqlSessionFactory就持有了Configuration对象,然后在调用openSession()的时候,Configuration对象也会被当做入参继续传递下去,关键代码如下图:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

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

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

接续跟进openSessionFromDataSource方法:

 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);
      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();
    }
  }

我们可以发现在openSessionFromDataSource方法中,Configuration对象会产生一个执行器Executor ,并且这个Executor 对象是作为的DefaultSqlSession的构造函数的入参,进入DefaultSqlSession,我们可以看到任意找一个增删查改的方法,底层都是调用的这个Executor对象来执行,

public class DefaultSqlSession implements SqlSession {

  //...只保留部分关键代码

    private final Configuration configuration;
  private final Executor executor;
  
  //构造函数
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  //查询方法
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      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();
    }
  }
}

继续跟进去executor.query()方法,找到Executor的一个实现类BaseExecutor,可以看到以下代码

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,     
    ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

ms.getBoundSql()的作用就是解析组装SQL,BoundSql对象就可以看做成一条已经可以执行的sql;

createCacheKey()的作用就是看这次查询的结果是否已经有过缓存,关于mybatis的一级缓存和二级缓存,篇幅问题就下次在写吧。。

继续跟BaseExecutor.query(ms, parameter, rowBounds, resultHandler, key, boundSql),找到SimpleExecutor,

 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.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

中间省略继续跟进的过程。。。总之也就是JDBC中过程,最后我们关注到如何把查询出来的结果集转换成我们的实体对象的,最后定位到DefaultResultSetHandler的handleResultSets方法。

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

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

未完待续。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是一个有理想的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值