mybatis源码追踪

参考:mybatis源码中文注释

https://github.com/tuguangquan/mybatis

mybatis运行流程图:

    先看一段mybatis的连接数据库操作的代码:下面跟着源码走一遍运行流程

 public static void main( String[] args ) throws IOException {
        String resource="SqlMapConfig.xml";   //1.数据源,别名等信息的配置文件
        InputStream inputStream= Resources.getResourceAsStream(resource);  //2.调用类加载器,加载配置文件中的类,并以二进制形式赋给inputStream
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);//3.根据inputStream得到SqlsessionFactory,过程描述如下@SqlsessionFactory
        SqlSession sqlSession=sqlSessionFactory.openSession(); //4.通过SqlsessionFactory产生Sqlsession实例(里面阐述的生成事务【进行事务的提交】,执行器【执行sql】)
        Book book=sqlSession.selectOne("test.findBookById",1000);   //5.调用sqlsession的crud方法
        // System.out.println(book);
        System.out.println(String.valueOf(book));
        sqlSession.close();
    }
}

  以下是mybatis的第三步得实现 过程:

new SqlSessionFactoryBuilder().build(inputStream);--------------------------------------》@SqlsessionFactory第一步,加载配置文件并转成二进制InputStream流

 

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);-----@SqlsessionFactory第二步:解析xml配置文件,将结果封装进Configuration
(SqlSessionFactory) return build(parser.parse());------------------------------------------》@SqlsessionFactory第三步 解析xml的每个节点,/configuration,/environments,/dataSource..........
return new DefaultSqlSessionFactory(Configuration);  -------@SqlsessionFactory第四步,将解析得到的configuration赋给SqlSessionFactory接口的实现类DefaultSqlSessionFactory的成员变量configuration对象,并且返回该实现类DefaultSqlSessionFactory,再向上转型就得到SqlsessionFactory

parser.parse()得到的是一个 Configuration类        parse()方法解析的是SqlMapConfig.xml文件,依次从根节点

configuration【

parseConfiguration(parser.evalNode("/configuration"));


】,到子节点 environments 【

environmentsElement(root.evalNode("environments"));


】【dataSource【

DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));

】】 ,mappers【

mapperElement(root.evalNode("mappers"));

-----------------------------------------------------------------------------------------

//至此SqlMapConfig.xml解析完成,解析得到的内容全在Configuration里面
return new DefaultSqlSessionFactory(Configuration);


//注意:
public DefaultSqlSessionFactory(Configuration configuration) {
  this.configuration = configuration;
}


 

小插曲:

费尽千辛万苦,只为拿到Configuration【xml配置文件---------到---------configuration配置对象的过程】,后续要从里面拿到

final Environment environment = configuration.getEnvironment(); 
final Executor executor = configuration.newExecutor(tx, execType);         ------13号技师(executor 执行器)粉墨登场

 

Environment里面主要是:三个东西
//环境id
private final String id;
//事务工厂
private final TransactionFactory transactionFactory;
//数据源
private final DataSource dataSource;

 

下面是第四步的描述:

//最终都会调用2种方法:openSessionFromDataSource,openSessionFromConnection
//以下6个方法都会调用openSessionFromDataSource
public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }


/**
*openSessionFromDataSource主要是通过configuration里的信息生成事务对象,
*生成Executor执行器对象(执行sql),进行事务的自动的提交
*/
 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);
      //然后产生一个DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);   //----------返回sqlsession对象,程序员可以通过该接口对象调用相应的方法进行对数据库的CRUD
    } 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的crud(增删改查)方法的实现:(以selectOne()的方法举例说明)

public interface SqlSession extends Closeable {
    <T> T selectOne(String statement);
}

 DefaultSqlSession实现了Sqlsession接口:(里面还描述了selectMap,selectList,insertupdate,delete,commit,rollback,close)

 

 @Override
  public <T> T selectOne(String statement) {
    return this.<T>selectOne(statement, null);
  }

具体实现过程:(实际它调用了selectList)

 //核心selectOne
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    //转而去调用selectList,很简单的,如果得到0条则返回null,得到1条则返回1条,得到多条报TooManyResultsException错
    // 特别需要主要的是当没有查询到结果的时候就会返回null。因此一般建议在mapper中编写resultType的时候使用包装类型
    //而不是基本类型,比如推荐使用Integer而不是int。这样就可以避免NPE
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

selectList的实现:

 //核心selectList
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据statement id找到对应的MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement); //---------这里是根据方法的id查询要调用mapper.xml里面的哪一个sql语句。
      //@executor下文描述:转而用执行器来查询结果,注意这里传入的ResultHandler是null
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);// wrapCollection(parameter)完成输入参数集的封装和对占位符值的设置,不如传的id=1,则where id=?  经过这条语句后就变成了where id=1,然后将sql语句发给数据库执行,再回调函数中处理数据库返回来的结果集
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

 

下面详细说明一下@executor执行器的实现:

//查询,带分页
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  BaseExecutor implements Executor实现Executor执行器:

 //SqlSession.selectList会调用此方法
public abstract class BaseExecutor implements Executor {
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //得到绑定sql
    BoundSql boundSql = ms.getBoundSql(parameter);
    //创建缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }
}

追踪了这么久,终极boss终于被我们找出来了:

public abstract class BaseExecutor implements Executor {
@SuppressWarnings("unchecked")
  @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.");
    }
    //先清局部缓存,再查询.但仅查询堆栈为0,才清。为了处理递归调用
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      //加一,这样递归调用到上面的时候就不会再清局部缓存了
      queryStack++;
      //先根据cachekey从localCache去查
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //若查到localCache缓存,处理localOutputParameterCache
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //从数据库查--------------@queryFromDatabase下文描述
        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
    	//如果是STATEMENT,清本地缓存
        clearLocalCache();
      }
    }
    return list;
  }
}

接着我们看看mybatis是整么从数据库查数据的   @queryFromDatabase过程:

//从数据库查
  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 {
      /**
      *以下几步是mybatis实现jdbc的规范的操作
      *Connection connection = getConnection(ms.getStatementLog()); Connenct con=DriverManage
      stmt = handler.prepare(connection);
      handler.parameterize(stmt);
      */
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //最后删除占位符
      localCache.removeObject(key);
    }
    //加入缓存
    localCache.putObject(key, list);
    //如果是存储过程,OUT参数也加入缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

5.1:重点来看BatchExecutor(实现Executor接口)的doQuery(.........)方法:

 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      flushStatements();
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
/*
Connection con=DriverManager.getConnection("localhost:3306/test","root","root");
String sql="select id from user where id=?";
PrepareStatement prepareStat=con.prepareStatment(sql);
prepareStat.setInt(1,666);
ResultSet rs=prepareStat.executeQuery();
while(rs.next()){
    System.out.println(rs.getInt(1));
    //System.out.println(rs.getString('column_name'))
    // Date birthday=rs.getDate(8);
    //System.out.println(rs.getString(2));
    //int a=rs.getInt('column_name'));
}
prepareStat.close();
con.close();
*/
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection);
      handler.parameterize(stmt);
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

5.1.1 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);------------------------parameterObject是客户端发过来的输入集参数,newStatementHandler方法完成了输入参数的封装,下面我们再来看看myatis是整么实现输入参数的 封装的:,进入到这个方法去看看。它是位于Configuration.java里面:

 //创建语句处理器
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //创建路由选择语句处理器--------------------进去看一看
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    //插件在这里插入
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

Configuration.java:

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    //根据语句类型,委派到不同的语句处理器(STATEMENT|PREPARED|CALLABLE)
    switch (ms.getStatementType()) {
      case STATEMENT:
         //----------------------------进去看一看,是个啥
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
SimpleStatementHandler.java:  -----------走到它的父类去看看------------->BaseStatementHandler.java
/**
 * @author Clinton Begin
public class SimpleStatementHandler extends BaseStatementHandler {}
 */
/**
 * 语句处理器的基类
 * 
 */
public abstract class BaseStatementHandler implements StatementHandler {
  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
//------------------------------>该句完成了封装,我们进去看看是个啥@mappedStatement.getBoundSql(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    //生成parameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    //生成resultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
}

@ mappedStatement.getBoundSql(parameterObject);重点来了:   -----------------------MappedStatement.java

/**
 * @author Clinton Begin
 */
/**
 * 映射的语句
 *
 */
public final class MappedStatement {
  public BoundSql getBoundSql(Object parameterObject) {
	//其实就是调用sqlSource.getBoundSql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    //剩下的可以暂时忽略
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }
}

5.1.2 其中完成结果集的封装---------------------------------->    return handler.<E>query(stmt, resultHandler);    进入这个方法我们看看

public class CallableStatementHandler extends BaseStatementHandler {
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    CallableStatement cs = (CallableStatement) statement;
    cs.execute();
    List<E> resultList = resultSetHandler.<E>handleResultSets(cs);
    resultSetHandler.handleOutputParameters(cs);
    return resultList;
  }
}

关注 List<E> resultList = resultSetHandler.<E>handleResultSets(cs);     我们进去看看(DefaultResultSetHandler 里面还实现了)------------------------输出结果的映射与封装

public class DefaultResultSetHandler implements ResultSetHandler {
 @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();
    //一般resultMaps里只有一个元素
    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.getResulSets();
    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);
  }










//重点关注以下:

 //核心,取得一行的值
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    //实例化ResultLoaderMap(延迟加载器)
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //调用自己的createResultObject,内部就是new一个对象(如果是简单类型,new完也把值赋进去)
    Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
    if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
      //一般不是简单类型不会有typehandler,这个if会进来
      final MetaObject metaObject = configuration.newMetaObject(resultObject);
      boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
      if (shouldApplyAutomaticMappings(resultMap, false)) {        
        //自动映射咯
        //这里把每个列的值都赋到相应的字段里去了
    	foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      resultObject = foundValues ? resultObject : null;
      return resultObject;
    }
    return resultObject;
  }

}

 

至此,解析SqlMapConfig.xml--------------解析Mapper.xml---------------------->调用指定了id的mapper.xml里面的方法------->parameterType输入参数集的封装------------------>resultType输出结果集的封装,代码追踪完毕。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【目的】如果您希望弄懂框架的原理,但是苦于不知道如何读框架源码?希望该课程能够通过带领大家阅读源码的方式,了解源码是如何阅读的。该课程选择了一个比较小,比较简单的框架入门,相信大家应该也用过该工具——mybatis generator【内容】该课程内容如下,列出了具体时间,便于复习:第一课:mybatis-generator源码分析-解析配置这节课分为如下几段:【00:00-05:00】:介绍源码阅读的方法【05:00-08:00】:简介mybatis-generator的使用 【08:00-27:30】:mybatis-generator包一览 【27:30-结束】:解析配置源码解读 【总结】所在的位置:1.第一次总结【34:15】。2.第二次总结【52:40】 涉及的【设计思路】和【设计模式】:1.模板模式【15:30】。2.xml解析的对象设计+组合模式:【37:00】。3.策略模式:【45:40】 第二课:mybatis-generator源码分析-生成文件 这节课分为如下几段:        1. 【00:00-10:20】:上节课内容回顾       2. 【10:20-42:20】:如何从数据库中获取元数据信息       3. 【42:20-结束】:生成文件 【总结】所在的位置:1.第一次总结【37:45】。2.第二次总结【56:25】 涉及的【设计思路】和【设计模式】:1、简单工厂方法模式【35:20】。2、聚合的设计思想【44:00】。 第三课:mybatis-generator源码分析-总结 这节课分为如下几段:        1. 【00:00-01:00】: 设计思路总结         2. 【01:00-02:50】:配置解析总结         3. 【02:50-03:55】: 从数据库获取信息总结         4. 【03:55-结束】: 文件的生成总结         

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值