本文主要从ibatis框架的基本代码骨架进行切入,理解ibatis框架的整体设计思路,各组件的实现细节将在后文进行分析。 

背景 

介绍ibatis实现之前,先来看一段jdbc代码: 
  1. Class.forName("com.mysql.jdbc.Driver");
  2. String url = "jdbc:mysql://localhost:3306/learnworld";
  3. Connection con = DriverManager.getConnection(url, "root","learnworld");
  4. String sql = "select * from test";
  5. PreparedStatement ps = con.prepareStatement(sql);
  6. ResultSet rs = ps.executeQuery();
  7. while(rs.next()){
  8. System.out.println("id=" + rs.getInt(1)+". score=" + rs.getInt(2));
  9. }

上面这段代码大家比较熟悉,这是一个典型的jdbc方式处理流程: 建立连接->传递参数->sql执行->处理结果->关闭连接。 

问题 

上面的代码中包含了很多不稳定的因素,可能会经常发生修改: 

1. 数据源和事务管理等 

2. sql语句 

3. 入参和结果处理 

如果这些发生变化,我们需要直接修改代码,重新编译、打包、发布等。 

DIY 
下面从我们自己DIY的视角来考虑如何设计框架,应对这些问题。框架的核心思想是抽取共性,封装可能出现的变化。 
如何处理数据源变化? 

将数据源连接等过程固定,将数据源中易变的信息封装在一起(如driverClassName, url等),放在配置文件中。 

如何处理sql语句的变化? 

将sql语句统一放在配置文件中,为每条sql语句设置标识,在代码中使用标识进行调用。 

如何应对入参和结果处理的变化? 

将参数传递,结果映射到java bean等统一封装在配置文件中。 

结论: 将不变的流程固化到代码中,将变化的信息封装在配置文件中。 

核心接口 

ibatis抽取了以下几个重要接口: 

1. SqlMapExecutor 

该接口是对SQL操作行为的抽象,提供了SQL单条执行和批处理涉及的所有操作方法。 
 

2. SqlMapTransactionManager 

该接口是对事务行为的抽象,提供了事务执行过程中的涉及的所有方法。 
 

3. SqlMapClient 

该接口定位是SQL执行客户端,是线程安全的,用于处理多个线程的sql执行。它继承了上面两个接口,这意味着该接口具有SQL执行、批处理和事务处理的能力,如果你拥有该接口的实现类,意味着执行任何SQL语句对你来说是小菜一碟。该接口的核心实现类是SqlMapClientImpl。 

4. SqlMapSession 

该接口在继承关系上和SqlMapClient一致,但它的定位是保存单线程sql执行过程的session信息。该接口的核心实现类是SqlMapSessionImpl。 

5. MappedStatement 

该接口定位是单条SQL执行时的上下文环境信息,如SQL标识、SQL、参数信息、返回结果、操作行为等。 

6. ParameterMap/ResultMap 

该接口用于在SQL执行的前后提供参数准备和执行结果集的处理。 

以上接口的类图关系如下(部分): 
 

这里必须要强调SqlMapExecutorDelegate这个类,他是一个执行代理类,在ibatis框架中地位非常重要,因为他耦合了用户端的操作行为和执行环境,他持有执行操作的所需要的所有数据,同时管理着执行操作依赖的环境。 

初始化过程 

1. 读取和解析sqlmap配置文件。 

2. 注册Statement对象。 

3. 创建SqlMapClientImpl对象。 

下面是一个Spring中sqlMapClient的bean配置: 

  1. <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  2. <property name="dataSource" ref="dataSource" />
  3. <property name="configLocation" value="classpath/sqlmap/sqlmap-ibatis.xml" />
  4. </bean>

下面看一下SqlMapClientFactoryBean的初始化方法afterPropertiesSet(),用于构建sqlMapClient对象: 

  1. public void afterPropertiesSet() throws Exception {
  2. ...
  3. this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);   //初始化核心方法,构建sqlMapClient对象
  4. ...
  5. }

看一下buildSqlMapClient()的实现: 

  1. protected SqlMapClient buildSqlMapClient(
  2. Resource[] configLocations, Resource[] mappingLocations, Properties properties)
  3. throws IOException {
  4. ...
  5. SqlMapClient client = null;
  6. SqlMapConfigParser configParser = new SqlMapConfigParser();
  7. for (int i = 0; i < configLocations.length; i++) {
  8. InputStream is = configLocations[i].getInputStream();
  9. try {
  10. client = configParser.parse(is, properties); //通过SqlMapConfigParser解析配置文件,生成SQLMapClientImpl对象
  11. }
  12. catch (RuntimeException ex) {
  13. throw new NestedIOException("Failed to parse config resource: " + configLocations[i], ex.getCause());
  14. }
  15. }
  16. ...
  17. return client;
  18. }

SQL执行过程 

下面以一个select语句的执行过程,分析一下以上各ibatis核心接口相互如何配合。 

1. dao调用SqlMapClientTemplate的query()方法: 

  1. public Object queryForObject(final String statementName, final Object parameterObject)
  2. throws DataAccessException {
  3. return execute(new SqlMapClientCallback() {
  4. public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
  5. return executor.queryForObject(statementName, parameterObject);
  6. }
  7. });
  8. }

2. execute()方法中展示了执行过程中的核心逻辑:获取session -> 获取connection -> 执行sql ->释放connection -> 关闭session。 

部分源码如下: 

  1. public Object execute(SqlMapClientCallback action) throws DataAccessException {
  2. ...
  3. SqlMapSession session = this.sqlMapClient.openSession(); //获取session信息
  4. Connection ibatisCon = null;     //获取Connection
  5. try {
  6. Connection springCon = null;
  7. DataSource dataSource = getDataSource();
  8. boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);
  9. try {
  10. ibatisCon = session.getCurrentConnection();
  11. if (ibatisCon == null) {
  12. springCon = (transactionAware ?
  13. dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
  14. session.setUserConnection(springCon);
  15. ...
  16. }
  17. }
  18. ...
  19. // Execute given callback...
  20. try {
  21. return action.doInSqlMapClient(session); //执行SQL
  22. }
  23. ...
  24. finally {
  25. // 关闭Connection
  26. try {
  27. if (springCon != null) {
  28. if (transactionAware) {
  29. springCon.close();
  30. }
  31. else {
  32. DataSourceUtils.doReleaseConnection(springCon, dataSource);
  33. }
  34. }
  35. }
  36. if (ibatisCon == null) {
  37. session.close(); //关闭session
  38. }
  39. }

3. action.doInSqlMapClient(session)调用SqlMapSessionImpl().queryForObject()方法。 

注意: 这里调用对象主体为SqlMapSessionImpl,表示在线程session中执行sql(session是ThreadLocal的),而不在负责整体协调的SqlMapClientImpl中执行sql语句。 

源码如下: 

  1. public Object queryForObject(final String statementName, final Object parameterObject)
  2. throws DataAccessException {
  3. return execute(new SqlMapClientCallback() {
  4. //这里的SqlMapExecutor对象类型为SqlMapSessionImpl
  5. public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
  6. return executor.queryForObject(statementName, parameterObject);
  7. }
  8. });
  9. }

4. SqlMapSessionImpl().queryForObject()的方法很简单,直接交给代理对象SqlMapExecutorDelegate处理: 

  1. public Object queryForObject(String id, Object paramObject) throws SQLException {
  2. return delegate.queryForObject(session, id, paramObject);
  3. }

5. SqlMapExecutorDelegate的queryForObject()方法代码如下: 

  1. public Object queryForObject(SessionScope session, String id, Object paramObject, Object resultObject) throws SQLException {
  2. Object object = null;
  3. MappedStatement ms = getMappedStatement(id); //MappedStatement对象集是上文中提及的初始化方法SqlMapClientFactoryBean.afterPropertiesSet()中,由配置文件构建而成
  4. Transaction trans = getTransaction(session); // 用于事务执行
  5. boolean autoStart = trans == null;
  6. try {
  7. trans = autoStartTransaction(session, autoStart, trans);
  8. RequestScope request = popRequest(session, ms);  // 从RequestScope池中获取该次sql执行中的上下文环境RequestScope
  9. try {
  10. object = ms.executeQueryForObject(request, trans, paramObject, resultObject);   // 执行sql
  11. } finally {
  12. pushRequest(request);  //归还RequestScope
  13. }
  14. autoCommitTransaction(session, autoStart);
  15. } finally {
  16. autoEndTransaction(session, autoStart);
  17. }
  18. return object;
  19. }

6. MappedStatement携带了SQL语句和执行过程中的相关信息,MappedStatement.executeQueryForObject()方法部分源码如下: 

  1. public Object executeQueryForObject(RequestScope request, Transaction trans, Object parameterObject, Object resultObject)
  2. throws SQLException {
  3. try {
  4. Object object = null;
  5. DefaultRowHandler rowHandler = new DefaultRowHandler();
  6. executeQueryWithCallback(request, trans.getConnection(), parameterObject, resultObject, rowHandler, SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS); //执行sql语句
  7. //结果处理,返回结果
  8. List list = rowHandler.getList();
  9. if (list.size() > 1) {
  10. throw new SQLException("Error: executeQueryForObject returned too many results.");
  11. } else if (list.size() > 0) {
  12. object = list.get(0);
  13. }
  14. return object;
  15. }
  16. ....
  17. }

7. MappedStatement.executeQueryWithCallback()方法包含了参数值映射、sql准备和sql执行等关键过程,部分源码如下: 

  1. protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler, int skipResults, int maxResults)
  2. throws SQLException {
  3. ...
  4. try {
  5. parameterObject = validateParameter(parameterObject);  //验证入参
  6. Sql sql = getSql();  //获取SQL对象
  7. errorContext.setMoreInfo("Check the parameter map.");
  8. ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);// 入参映射
  9. errorContext.setMoreInfo("Check the result map.");
  10. ResultMap resultMap = sql.getResultMap(request, parameterObject); //结果映射
  11. request.setResultMap(resultMap);
  12. request.setParameterMap(parameterMap);
  13. errorContext.setMoreInfo("Check the parameter map.");
  14. Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject);  //获取参数值
  15. errorContext.setMoreInfo("Check the SQL statement.");
  16. String sqlString = sql.getSql(request, parameterObject);  //获取拼装后的sql语句
  17. errorContext.setActivity("executing mapped statement");
  18. errorContext.setMoreInfo("Check the SQL statement or the result map.");
  19. RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler);
  20. sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback);  //sql执行
  21. errorContext.setMoreInfo("Check the output parameters.");
  22. if (parameterObject != null) {
  23. postProcessParameterObject(request, parameterObject, parameters);
  24. }
  25. errorContext.reset();
  26. sql.cleanup(request);
  27. notifyListeners();
  28. ....
  29. }

8. 到了执行中最核心的一步,也是最后一步: MappedStatement.sqlExecuteQuery()方法,它负责sql的最后执行,内部调用了SqlExecutor.executeQuery()方法,部分源码如下: 

  1. public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {
  2. ...
  3. PreparedStatement ps = null;
  4. ResultSet rs = null;
  5. setupResultObjectFactory(request);
  6. try {
  7. errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
  8. Integer rsType = request.getStatement().getResultSetType();
  9. //初始化PreparedStatement,设置sql、参数值等
  10. if (rsType != null) {
  11. ps = prepareStatement(request.getSession(), conn, sql, rsType);
  12. } else {
  13. ps = prepareStatement(request.getSession(), conn, sql);
  14. }
  15. setStatementTimeout(request.getStatement(), ps);
  16. Integer fetchSize = request.getStatement().getFetchSize();
  17. if (fetchSize != null) {
  18. ps.setFetchSize(fetchSize.intValue());
  19. }
  20. errorContext.setMoreInfo("Check the parameters (set parameters failed).");
  21. request.getParameterMap().setParameters(request, ps, parameters);
  22. errorContext.setMoreInfo("Check the statement (query failed).");
  23. ps.execute(); //执行
  24. errorContext.setMoreInfo("Check the results (failed to retrieve results).");
  25. // ResultSet处理
  26. rs = handleMultipleResults(ps, request, skipResults, maxResults, callback);
  27. } finally {
  28. try {
  29. closeResultSet(rs);
  30. } finally {
  31. closeStatement(request.getSession(), ps);
  32. }
  33. }
  34. }

上面这段代码大家会非常熟悉,和本文开始处的代码很相似,ibatis归根到底,是对JDBC操作一定程度上的封装而已。 

下面在总体上概括sql的一般执行过程: 

SqlMapClientImpl接到请求后,创建SqlMapSessionImpl对象(ThreadLocal,保证线程安全),SqlMapSessionImpl交由内部的代理类SqlMapExecutorDelegate执行,代理类获取相应的MappedStatement,交由MappedStatement对象执行,MappedStatement交由SqlExecutor执行,最终使用JDBC方式执行sql。 

小结 

ibatis源码规模较小,整体设计思路清晰,阅读ibatis源码可以按以下思路进行: 

1. 了解ibatis框架的整体目标,用于解决哪些问题。 

2. ibatis如何解决这些问题,带着问题去学习。 

3. 了解ibatis框架的核心接口和整体设计思路。 

4. 抓住ibatis核心流程: 初始化和请求处理流程。 

5. 详细ibatis框架的关键细节实现,如ibatis中的配置文件解析,参数和结果映射等。

ibatis源码学习(一)整体设计和核心流程的更多相关文章

  1. ibatis源码学习1_整体设计和核心流程

    背景介绍ibatis实现之前,先来看一段jdbc代码: Class.forName("com.mysql.jdbc.Driver"); String url = "jdb ...

  2. ibatis源码学习4_参数和结果的映射原理

    问题在详细介绍ibatis参数和结果映射原理之前,让我们先来思考几个问题.1. 为什么需要参数和结果的映射?相对于全自动的orm,ibatis一个重要目标是,通过维护POJO与SQL之间的映射关系,让 ...

  3. spring源码学习——spring整体架构和设计理念

    Spring是在Rod Johnson的<Expert One-On-One J2EE Development and Design >的基础上衍生而来的.主要目的是通过使用基本的java ...

  4. Mybatis源码学习之整体架构(一)

    简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...

  5. 【iScroll源码学习04】分离IScroll核心

    前言 最近几天我们前前后后基本将iScroll源码学的七七八八了,文章中未涉及的各位就要自己去看了 1. [iScroll源码学习03]iScroll事件机制与滚动条的实现 2. [iScroll源码 ...

  6. RecyclerView源码分析(一)--整体设计

    RecyclerView这个控件出来已经有一段时间了,如果看这篇文章的你,还没有使用过这个控件.那请先去学习怎样使用.不然看也白看.这里奉上一些关于介绍RecyclerView使用方法的优秀博客: 鸿 ...

  7. ibatis源码学习2_初始化和配置文件解析

    问题在详细介绍ibatis初始化过程之前,让我们先来思考几个问题. 1. ibatis初始化的目标是什么?上文中提到过,ibatis初始化的核心目标是构造SqlMapClientImpl对象,主要是其 ...

  8. Hadoop源码学习笔记之NameNode启动场景流程五:磁盘空间检查及安全模式检查

    本篇内容关注NameNode启动之前,active状态和standby状态的一些后台服务及准备工作,即源码里的CommonServices.主要包括磁盘空间检查. 可用资源检查.安全模式等.依然分为三 ...

  9. Hadoop源码学习笔记之NameNode启动场景流程四:rpc server初始化及启动

    老规矩,还是分三步走,分别为源码调用分析.伪代码核心梳理.调用关系图解. 一.源码调用分析 根据上篇的梳理,直接从initialize()方法着手.源码如下,部分代码的功能以及说明,已经在注释阐述了. ...

  10. Hadoop源码学习笔记之NameNode启动场景流程三:FSNamesystem初始化源码剖析

    上篇内容分析了http server的启动代码,这篇文章继续从initialize()方法中按执行顺序进行分析.内容还是分为三大块: 一.源码调用关系分析 二.伪代码执行流程 三.代码图解 一.源码调 ...

随机推荐

  1. psutil 检测exe是否已经运行

    安装依赖 pip install psutil 代码 import psutil def check_if_process_running(process_name): ''' Check if th ...

  2. Html 使用scss爆红

      使用     <style  lang="less" scoped> </style>   即可      

  3. 【Azure Policy】使用deployIfNotExists 把 Azure Activity logs 导出保存在Storage Account

    问题描述 使用Azure Policy,对订阅下的全部Activity Log配置Diagnostic Setting,要求: 在Subscription或Management Group级别,针对未 ...

  4. 【笔记】Tapable源码解析图以及webpack怎样实现一个插件plugin

    Tapable源码解析图,如图所示: 一个webpack plugin由一下几个步骤组成: 一个JavaScript类函数. 在函数原型 (prototype)中定义一个注入compiler对象的ap ...

  5. ASP.NET Core – Web API 冷知识

    Under/Over Posting 参考: .NET Core WebApi Action is executed even with missing properties in the reque ...

  6. ASP.NET Core 单元测试

    前言 单元测试是好, 但是也很花时间. 有些功能封装好了以后也不怎么会再打开, 所以通常就是徒手测试一下, 过了就过了. 但是往往就是那么神奇, 就是会有需求漏掉. 后来要加, 又由于不想潜水, 对自 ...

  7. Redis 入门 - 收官

    <Redis 入门>系列文章总算完成了,希望这个系列文章可以想入门或刚入门的同学提供帮助,希望能让你形成学习Redis系统性概念. 当时为什么要写这个系列文章,是因为我自己就是迷迷糊糊一路 ...

  8. 神经网络之卷积篇:详解为什么使用卷积?(Why convolutions?)

    详解为什么使用卷积? 来分析一下卷积在神经网络中如此受用的原因,然后对如何整合这些卷积,如何通过一个标注过的训练集训练卷积神经网络做个简单概括.和只用全连接层相比,卷积层的两个主要优势在于参数共享和稀 ...

  9. std::vector::reserve

    std::vector::reserve 函数在 C++ 中用于预分配内存,避免在元素增加时多次重新分配内存,从而提高性能. 它最常用于需要频繁向 vector 中添加元素,并且可以预估容器的最终大小 ...

  10. 高通pmic voter机制

    前不久在高通 SDM450 平台接触了 voter 机制(投票机制).最近终于得空,结合一个问题简单研究了一下.现将研究流程简单记录一下,由于时间有限,所以是实用为目的,没有做详细的分析,不过结合着这 ...