zoukankan      html  css  js  c++  java
  • mybatis源码解读(五)——sql语句的执行流程

      还是以第一篇博客中给出的例子,根据代码实例来入手分析。

     1     static {
     2         InputStream inputStream = MybatisTest.class.getClassLoader().getResourceAsStream("mybatis-configuration.xml");
     3         sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
     4     }
     5 
     6     /**
     7      * 查询单个记录
     8      */
     9     @Test
    10     public void testSelectOne() {
    11         SqlSession session = sqlSessionFactory.openSession();
    12         User user = session.selectOne(NAME_SPACE + ".selectUserById", 1);
    13         System.out.println(user);
    14         session.close();
    15     }

      如何加载配置文件前面也已经介绍了,通过配置文件产生SqlSessionFactory,追溯源码可以发现其实现是 DefaultSqlSessionFactory。

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

      得到 SqlSessionFactory 之后,就可以通过 SqlSessionFactory 去获取 SqlSession 对象。源码如下:

     1     @Override
     2     public SqlSession openSession() {
     3         return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
     4     }
     5 
     6 
     7     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
     8         Transaction tx = null;
     9         try {
    10             //Environment对象封装了配置文件中对于数据源和事务的配置
    11             final Environment environment = configuration.getEnvironment();
    12             final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    13             tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    14             //获取Executor对象,用来执行sql语句
    15             final Executor executor = configuration.newExecutor(tx, execType);
    16             return new DefaultSqlSession(configuration, executor, autoCommit);
    17         } catch (Exception e) {
    18             closeTransaction(tx); // may have fetched a connection so lets call close()
    19             throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    20         } finally {
    21             ErrorContext.instance().reset();
    22         }
    23     }

      这里我们重点看一下第 15 行代码:

     1   public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
     2     executorType = executorType == null ? defaultExecutorType : executorType;
     3     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
     4     Executor executor;
     5     if (ExecutorType.BATCH == executorType) {
     6       executor = new BatchExecutor(this, transaction);
     7     } else if (ExecutorType.REUSE == executorType) {
     8       executor = new ReuseExecutor(this, transaction);
     9     } else {
    10       executor = new SimpleExecutor(this, transaction);
    11     }
    12     if (cacheEnabled) {
    13       executor = new CachingExecutor(executor);
    14     }
    15     executor = (Executor) interceptorChain.pluginAll(executor);
    16     return executor;
    17   }

      根据执行器类型这里有多种不同的执行器Executor。

      注意第 12 行代码,如果我们开启了缓存,即 cacheEnabled = true(这里是一级缓存,默认是开启的),第13行代码使用了装饰器模式,在原有的 Executor 上装饰了缓存功能。

      第 15 行用于设置插件。

      这时候已经得到SqlSession对象了,实际类型是 DefaultSqlSession。接下来我们就可以通过该对象来执行sql语句了。

     1、insert 操作

     1     /**
     2      * 插入一条记录
     3      */
     4     @Test
     5     public void testInsert() {
     6         SqlSession session = sqlSessionFactory.openSession();
     7         User user = new User(2, "zhangsan", 22);
     8         session.insert(NAME_SPACE + ".insertUser", user);
     9         session.commit();
    10         session.close();
    11     }

      通过第8行代码,我们进入到 insert 方法:

    1   @Override
    2   public int insert(String statement, Object parameter) {
    3     return update(statement, parameter);
    4   }

      注意:这里通过 insert 方法,调用的是 update 方法。

     1   public int update(String statement, Object parameter) {
     2     try {
     3       dirty = true;
     4       MappedStatement ms = configuration.getMappedStatement(statement);
     5       return executor.update(ms, wrapCollection(parameter));
     6     } catch (Exception e) {
     7       throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
     8     } finally {
     9       ErrorContext.instance().reset();
    10     }
    11   }

      第4行根据给的statement参数,获取配置的所有如下信息,并将其封装到 MappedStatement 对象中,关于这个对象后面会详细介绍。

    1     <!-- 向 user 表插入一条数据 -->
    2     <insert id="insertUser" parameterType="com.ys.po.User" >
    3         insert into
    4         user(<include refid="Base_Column_List" />)
    5         value(#{id,jdbcType=INTEGER},#{name,jdbcType=VARCHAR},#{age,jdbcType=INTEGER})
    6     </insert>

      ①、接着我们看第 5 行代码,首先看 wrapCollection(parameter) 方法:

     1     private Object wrapCollection(final Object object) {
     2         if (object instanceof Collection) {
     3             DefaultSqlSession.StrictMap<Object> map = new DefaultSqlSession.StrictMap<Object>();
     4             map.put("collection", object);
     5             if (object instanceof List) {
     6                 map.put("list", object);
     7             }
     8             return map;
     9         } else if (object != null && object.getClass().isArray()) {
    10             DefaultSqlSession.StrictMap<Object> map = new DefaultSqlSession.StrictMap<Object>();
    11             map.put("array", object);
    12             return map;
    13         }
    14         return object;
    15     }

      通过这段代码的if-else if 语句主要做了如下两个操作:

      1、如果传入的参数是集合 Collection,在 map 集合中放入一个key为"collection"、value为参数的键值对,接着判断该集合是不是 List 类型,如果是,那么在 map 集合中在放入一个key为"list"、value为参数的键值对。

      2、如果传入的参数是数组类型,那么在 map 中放入一个key为"array"、value为参数的键值对。

      注意:这里的 StrictMap ,其实就是一个 HashMap。

     1   public static class StrictMap<V> extends HashMap<String, V> {
     2 
     3     private static final long serialVersionUID = -5741767162221585340L;
     4 
     5     @Override
     6     public V get(Object key) {
     7       if (!super.containsKey(key)) {
     8         throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
     9       }
    10       return super.get(key);
    11     }
    12 
    13   }
    14 
    15 }
    View Code

      ②、wrapCollection(parameter) 方法介绍完了。接着我们看 executor.update()方法:

      这里需要说明的是 Executor 对象上面我们已经介绍了,由于默认是开启一级缓存的,这时候我们进入 CachingExecutor 类的 update() 方法:

    1   public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    2     flushCacheIfRequired(ms);
    3     return delegate.update(ms, parameterObject);
    4   }

      首先我们看这里的第 2 行代码:

      private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        if (cache != null && ms.isFlushCacheRequired()) {      
          tcm.clear(cache);
        }
      }

      这里表示的意思是是否清除缓存。看我们是否在配置文件中配置了 <cache> 标签,以及我们是否在 <insert /> 标签中是否增加了 flushCache="true"属性。如果有其中任何一个,此次操作都会清除缓存。

      接着我们再看第3行代码,这里的delegate 是 Executor,但是这是一个接口,其真实类型是SimpleExecutor,经过装饰器模式,调用 CachingExecutor 的 update 方法,经过处理后,最后最后调用 SimpleExecutor的update方法:

      具体调用:

      首先调用 BaseExecutor 的 update 方法

      public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        clearLocalCache();
        return doUpdate(ms, parameter);
      }

      然后调用 doUpdate 方法,由于 SimpleExecutor 继承 BaseExecutor 类,并重写了 doUpdate 方法,我们看 SimpleExecutor 类的 doUpdate 方法:

     1   public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
     2     Statement stmt = null;
     3     try {
     4       Configuration configuration = ms.getConfiguration();
     5       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
     6       stmt = prepareStatement(handler, ms.getStatementLog());
     7       return handler.update(stmt);
     8     } finally {
     9       closeStatement(stmt);
    10     }
    11   }

      看到这里,Statement 对象,看到我们熟悉的 JDBC 操作数据库的对象了吧。我们直接看第 6 行代码:

    1   private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    2     Statement stmt;
    3     Connection connection = getConnection(statementLog);
    4     stmt = handler.prepare(connection, transaction.getTimeout());
    5     handler.parameterize(stmt);
    6     return stmt;
    7   }

      第 3 行代码获取数据库连接,是根据前面配置的数据源来获取。接着我们看 handler.update(stemt) 方法:

     1   public int update(Statement statement) throws SQLException {
     2     String sql = boundSql.getSql();
     3     Object parameterObject = boundSql.getParameterObject();
     4     KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
     5     int rows;
     6     if (keyGenerator instanceof Jdbc3KeyGenerator) {
     7       statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
     8       rows = statement.getUpdateCount();
     9       keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    10     } else if (keyGenerator instanceof SelectKeyGenerator) {
    11       statement.execute(sql);
    12       rows = statement.getUpdateCount();
    13       keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    14     } else {
    15       statement.execute(sql);
    16       rows = statement.getUpdateCount();
    17     }
    18     return rows;
    19   }

      这里就都是我们熟悉的 JDBC 操作了。

    2、update 和 delete 操作

     1     /**
     2      * 更新一条记录
     3      */
     4     @Test
     5     public void testUpdate() {
     6         SqlSession session = sqlSessionFactory.openSession();
     7         User user = new User(2, "lisi", 22);
     8         session.update(NAME_SPACE + ".updateUserById", user);
     9         session.commit();
    10         session.close();
    11     }
    12 
    13     /**
    14      * 删除一条记录
    15      */
    16     @Test
    17     public void testDelete() {
    18         SqlSession session = sqlSessionFactory.openSession();
    19         session.delete(NAME_SPACE + ".deleteUserById", 2);
    20         session.commit();
    21         session.close();
    22     }

      进入到上述第 8 行和 第 19 行代码,我们发现都是进入到和 上面 insert 操作一样的代码:

      第 8 行:

      public int update(String statement, Object parameter) {
        try {
          dirty = true;
          MappedStatement ms = configuration.getMappedStatement(statement);
          return executor.update(ms, wrapCollection(parameter));
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }

      第 19 行:

      @Override
      public int delete(String statement, Object parameter) {
        return update(statement, parameter);
      }

      之后的 update 也是上面的代码。这也和我们理解的应该保持一致。

      结论:

    insert、update、delete都是属于对数据库的行进行更新操作

      所以这三种语句的执行都是采用的同种逻辑处理。最终都可以调用 executeUpdate() 方法来处理。唯一不同的是 select 操作,必须要调用 executeQuery() 来执行。

    3、select 操作

     1     /**
     2      * 查询单个记录
     3      */
     4     @Test
     5     public void testSelectOne() {
     6         SqlSession session = sqlSessionFactory.openSession();
     7         User user = session.selectOne(NAME_SPACE + ".selectUserById", 1);
     8         System.out.println(user);
     9         session.close();
    10     }
    11 
    12     /**
    13      * 查询多个记录
    14      */
    15     @Test
    16     public void testSelectList() {
    17         SqlSession session = sqlSessionFactory.openSession();
    18         List<User> listUser = session.selectList(NAME_SPACE + ".selectUserAll");
    19         if (listUser != null) {
    20             System.out.println(listUser.size());
    21         }
    22         session.close();
    23     }

      首先看第 7 行代码:

     1   public <T> T selectOne(String statement, Object parameter) {
     2     // Popular vote was to return null on 0 results and throw exception on too many.
     3     List<T> list = this.<T>selectList(statement, parameter);
     4     if (list.size() == 1) {
     5       return list.get(0);
     6     } else if (list.size() > 1) {
     7       throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
     8     } else {
     9       return null;
    10     }
    11   }

      看到上面的第 3 行代码,我们可能马上就明白了,其实selectOne() 和 selectList() 也都是调用的 selectList() 方法,只不过 selectOne() 是获取集合的第一个元素而已。

      接着看 selectList() 源码:

     1   @Override
     2   public <E> List<E> selectList(String statement, Object parameter) {
     3     return this.selectList(statement, parameter, RowBounds.DEFAULT);
     4   }
     5 
     6   @Override
     7   public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
     8     try {
     9       MappedStatement ms = configuration.getMappedStatement(statement);
    10       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    11     } catch (Exception e) {
    12       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    13     } finally {
    14       ErrorContext.instance().reset();
    15     }
    16   }

      看第10的 query 方法:

    1   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    2     BoundSql boundSql = ms.getBoundSql(parameterObject);
    3     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    4     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    5   }
     1   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
     2       throws SQLException {
     3     Cache cache = ms.getCache();
     4     if (cache != null) {
     5       flushCacheIfRequired(ms);
     6       if (ms.isUseCache() && resultHandler == null) {
     7         ensureNoOutParams(ms, parameterObject, boundSql);
     8         @SuppressWarnings("unchecked")
     9         List<E> list = (List<E>) tcm.getObject(cache, key);
    10         if (list == null) {
    11           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    12           tcm.putObject(cache, key, list); // issue #578 and #116
    13         }
    14         return list;
    15       }
    16     }
    17     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    18   }

      最后我们来到doQuery() 方法:

      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);
        }
      }
    1   @Override
    2   public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    3     PreparedStatement ps = (PreparedStatement) statement;
    4     ps.execute();
    5     return resultSetHandler.<E> handleResultSets(ps);
    6   }

      至此,select 操作也执行完毕了。

  • 相关阅读:
    sqlmap参数设置
    SQL注入
    SQL注入基本原理
    信息收集(shodan的使用)
    CDN
    2020软件工程个人作业06——软件工程实践总结作业
    问题清单
    2020软件工程作业05
    2020软件工程作业00——问题清单
    软件工程第三次作业
  • 原文地址:https://www.cnblogs.com/ysocean/p/9017793.html
Copyright © 2011-2022 走看看