前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis源码学习(三)executor

Mybatis源码学习(三)executor

作者头像
虞大大
发布2020-09-01 11:17:46
5520
发布2020-09-01 11:17:46
举报
文章被收录于专栏:码云大作战

一、executor执行前参数处理

代码语言:javascript
复制
Blog blog = mapper.selectBlog(101);

以上述为示例,开始执行select方法,源码如下:

代码语言:javascript
复制
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
  //根据mapper解析的基础信息,确定sql执行类型  switch (command.getType()) {
case INSERT: {    //插入sql类型      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
case UPDATE: {      //修改sql类型
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
case DELETE: {        //删除sql类型      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
case SELECT:        //查询sql类型      if (method.returnsVoid() && method.hasResultHandler()) {        //如果方法没有返回值并存在返回解析器        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {        //方法返回值是集合        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {        //方法返回值是map类型        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {        //方法返回值是cursor,查了下好像是awt相关的 我就忽略了        result = executeForCursor(sqlSession, args);
      } else {        //如果都不是,则执行查询单条sql        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
      break;
    case FLUSH:      //预处理sql类型      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
  }
  return result;
}

(1)selectOne

首先执行method.convertArgsToSqlCommandParam获取传参。

代码语言:javascript
复制
public Object getNamedParams(Object[] args) {
  //...  //根据上述例子,参数只有一个 因此,直接返回的是传参  else if (!hasParamAnnotation && paramCount == 1) {    return args[names.firstKey()];
  //...
}

开始进行selectOne操作。

代码语言:javascript
复制
result = sqlSession.selectOne(command.getName(), param);
代码语言:javascript
复制
@Override
public <T> T selectOne(String statement, Object parameter) {
  //底层调用的是selectList方法
  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;
  }
}

在selectOne方法中,这里的statement参数其实为sql标识即mapper路径名+方法名,因此在mapper方法中是不允许重载的,param参数则为传参101。另外selectOne的方法的执行实际上底层是调用了selectList方法。

(2)selectList

代码语言:javascript
复制
List<Blog> list = mapper.selectList(101,"name");

上述执行的含义为查询type为101,name为name的多条数据。在mapper接口方法中需要使用@Param指定参数进行解析。

代码语言:javascript
复制
List<Blog> selectList(@Param("type") Integer type, @Param("name") String name);

开始执行selectList,首先会根据加载后的returnManys判断返回结果是集合类型,则指向executeForMany。然后执行method.convertArgsToSqlCommandParam获取传参,这里观察下多参数的情况与单个参数参数结果有何不同。

代码语言:javascript
复制
public Object getNamedParams(Object[] args) {
    //...
    final Map<String, Object> param = new ParamMap<Object>();
    int i = 0;    //对sortedMap遍历    for (Map.Entry<Integer, String> entry : names.entrySet()) {      //放入到map中 key-参数名 如type,value-值,如101      param.put(entry.getValue(), args[entry.getKey()]);
      final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
      //将自定义生成的paramName放入到map中 key-param+遍历次数 如params1,      //value-值,如101      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
}

selectList的方法参数首先已经在加载时被解析成了sortedMap结构{0-type,1-name}。然后会对该sortedMap进行遍历,将参数名作为key,值作为value放到map中,并且也会往map中放入自定义生成的参数名作为key-即param+遍历次数,value为值。

将请求参数解析成map后返回,最后返回的结构为{type-101,param1-101,name-name,param2-name}。

开始执行selectList方法。

代码语言:javascript
复制
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {    //根据sql标识,去取mapper映射信息    MappedStatement ms = configuration.getMappedStatement(statement);
    //执行query方法    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方法之前,会从configuration的mappedStatementsMap中根据sql标识即mapper路径名+方法取出mapper映射信息,MappedStatement中主要存放的是mapper.xml的全路径信息、jdbc资源信息、resultMap信息、sql执行类型(SELECT)、statementType(PREPARED)等基础信息。

(3)insert

首先也会执行method.convertArgsToSqlCommandParam获取传参。然后开始执行insert,底层实际上是调用的update方法。

代码语言:javascript
复制
public int insert(String statement, Object parameter) {
  return update(statement, parameter);
}

(4)delete

和insert一样,首先也会执行method.convertArgsToSqlCommandParam获取传参。然后开始执行delete,底层实际上是调用的也是update方法。

代码语言:javascript
复制
public int delete(String statement, Object parameter) {
  return update(statement, parameter);
}

(5)update

代码语言:javascript
复制
@Override
public int update(String statement, Object parameter) {
  try {
dirty = true;    //根据sql标识从map中获取Mapped映射    MappedStatement ms = configuration.getMappedStatement(statement);     //通过executor执行。    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

二、executor.query

代码语言:javascript
复制
@Override
 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   //获取boundSql   BoundSql boundSql = ms.getBoundSql(parameter);
   //获取一级缓存的key    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   //执行query   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

第一步,从mapped映射器中获取boundSql信息,里面存放的是被替换为占位符的sql语句、参数映射、参数map等与sql相关的基础信息。这里的sql由#{type}、#{name}被替换为?,也是发生在mybatis加载的时候(org.apache.ibatis.builder.SqlSourceBuilder#parse)。

第二步,获取一级缓存的key = sql标识id+sql语句+参数类型+参数值+额外参数。一级缓存是默认开启的。

第三步,开始执行query。

代码语言:javascript
复制
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {//mapper映射中是否存在缓存
  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);
}

先查询mapper映射中是否存在缓存,如果存在缓存,则直接取缓存中的数据,否则查询数据库。

代码语言:javascript
复制
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  //...
  List<E> list;
  try {    //在一个事物中或一个sqlSession中,sql执行次数+1    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 {    //sql执行完毕,sql执行次数-1
    queryStack--;
  }  //事物中或者sqlSession的sql全部执行完毕需要清除一级缓存
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      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;  //先根据key存放一级缓存占位符  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;
}

一级缓存默认开启,并且生命周期只存在于一个sqlSession中,sqlSession执行完毕,一级缓存也就被清空。

· doQuery核心方法

代码语言:javascript
复制
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {    //获取configuration    Configuration configuration = ms.getConfiguration();
    //封装Handler执行器    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //准备statement执行器    stmt = prepareStatement(handler, ms.getStatementLog());
    //执行query方法    return handler.<E>query(stmt, resultHandler);
  } finally {    //关闭资源    closeStatement(stmt);
  }
}

这一步主要是在建立数据连接、准备PreparedStatement,然后进行数据库操作。最后关闭连接资源。

三、executor.update

代码语言:javascript
复制
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//清除缓存  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}
代码语言:javascript
复制
public int update(MappedStatement ms, Object parameter) throws SQLException {
  //清除一级缓存
  clearLocalCache();
  return doUpdate(ms, parameter);
}
代码语言:javascript
复制
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    stmt = prepareStatement(handler, ms.getStatementLog());
    //执行update方法    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
}

过程大致与query的相同,不过在操作之前会先清空当前sqlSession所有的一级缓存,防止出现脏读情况。然后开始建立连接、准备PreparedStatement,进行数据库操作。最后关闭资源。

四、总结

execute中只存在query和update,并且我们定义的selectOne和selectLsit都是调用execute的query,而insert、update、delete都是调用的update。即query代表对数据库的查询操作,而insert、update、delete代表对数据库的修改操作,因此都调用了update。

在源码中我们也看到了一级缓存的应用,一级缓存只会在相同sqlSession作用域情况才会被使用到,并且key为sql标识id+sql语句+参数类型+参数值+额外参数,一级缓存在我们的mybaits中也是默认开启的。为了不出现脏读的情况,在相同sqlSession作用域中如果出现了update操作则会清空一级缓存。

问题一:一级缓存、二级缓存的理解和区别。

首先一级缓存的作用域是一个SqlSession范围内的。只有当这个sqlSesion执行两次相同的sql时才会命中缓存。从缓存中获取数据,不再去数据库查询,提高查询效率。并且当sqlSession执行完毕或者出现了update操作,则会清空一级缓存避免出现脏读现象。一级缓存默认开启。

二级缓存的作用域比一级缓存大,是mapper级别的(即nameSpace),因此不同的sqlSession执行相同的sql语句时可以命中缓存,不再去数据库查询。二级缓存需要手动开启。但是二级缓存在多表查询情况下不推荐使用,虽然二级缓存在单表进行update操作时会清空二级缓存,但是在连表的情况下,如:A.xml,b.xml,A.xml中存在连b表的查询,当b中进行了update操作时,只会把b.xml中nameSpace关联的二级缓存清空,其他nameSpace并不会清空,在这样的情况下,会导致其他表的数据读取充满着风险。在最新的mybatis也已经取消掉了二级缓存。

问题二:PreparedStatement和Statement的区别

PreparedStatement继承自Statement。但是PreparedStatement会在启动时会将占位符#{}替换为?,采用预编译的方式来处理sql和替换参数,比Statement拥有着更好的性能。并且有了预编译机制,sql编写的扩展性也提高了很多,可以写很多动态性的sql。

而Statement不推荐使用的原因在于,没有预编译机制可能会导致sql注入攻击,比如select * from user where name = +(),这个sql如果传入了“test” or 1=1 ======>(select * from user where name = ‘test’ or ‘1’ = ‘1’),导致sql注入。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码云大作战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档