MyBatis源码分析(三)SqlSession的执行主流程

news2024/9/30 19:30:33

文章目录

  • 一、熟悉主要接口
  • 二、SqlSession的获取
    • 1、通过数据源获取SqlSession
  • 三、Mapper的获取与代理
    • 1、从SqlSession获取Mapper
    • 2、执行Mapper方法前准备逻辑
    • 3、SqlCommand的创建
    • 4、构造MethodSignature
  • 四、执行Mapper的核心方法
    • 1、执行Mapper的方法逻辑
  • 五、简单SELECT处理过程
    • 1、对参数进行解析
    • 2、DefaultSqlSession的selectOne逻辑
    • 3、Executor的query
    • 4、查询数据库的逻辑
      • (1)prepareStatement
    • 5、StatementHandler的query方法
    • 6、处理结果集
      • (1)处理结果集
      • (2)创建映射结果对象
      • (3)根据columnName和type属性名映射赋值
      • (4)applyPropertyMappings
  • 写在后面

一、熟悉主要接口

DefaultSqlSession:SqlSession接口的默认实现类

Executor接口:
在这里插入图片描述
BaseExecutor:基础执行器,封装了子类的公共方法,包括一级缓存、延迟加载、回滚、关闭等功能;

SimpleExecutor:简单执行器,每执行一条 sql,都会打开一个 Statement,执行完成后关闭;

ReuseExecutor:重用执行器,相较于 SimpleExecutor 多了 Statement 的缓存功能,其内部维护一个 Map<String, Statement> ,每次编译完成的 Statement 都会进行缓存,不会关闭;

BatchExecutor:批量执行器,基于 JDBC 的 addBatch、executeBatch 功能,并且在当前 sql 和上一条 sql 完全一样的时候,重用Statement,在调用doFlushStatements 的时候,将数据刷新到数据库;

CachingExecutor:缓存执行器,装饰器模式,在开启缓存的时候。会在上面三种执行器的外面包上 CachingExecutor;

StatementHandler接口:
在这里插入图片描述
在这里插入图片描述

MyBatis实际使用的StatementHandler,就是RoutingStatementHandler,拦截器拦截的也是RoutingStatementHandler。它会根据Exector类型创建对应的StatementHandler,保存到属性delegate中,所以插件分离还要分离出具体的StatementHandler。

BaseStatementHandler:基础语句处理器(抽象类),它基本把语句处理器接口的核心部分都实现了,包括配置绑定、执行器绑定、映射器绑定、参数处理器构建、结果集处理器构建、语句超时设置、语句关闭等,并另外定义了新的方法 instantiateStatement 供不同子类实现以便获取不同类型的语句连接,子类可以普通执行 SQL 语句,也可以做预编译执行,还可以执行存储过程等。
SimpleStatementHandler:普通语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.Statement 对象的处理,处理普通的不带动态参数运行的 SQL,即执行简单拼接的字符串语句,同时由于 Statement 的特性,SimpleStatementHandler 每次执行都需要编译 SQL (注意:我们知道 SQL 的执行是需要编译和解析的)。
PreparedStatementHandler:预编译语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.PrepareStatement 对象的处理,相比上面的普通语句处理器,它支持可变参数 SQL 执行,由于 PrepareStatement 的特性,它会进行预编译,在缓存中一旦发现有预编译的命令,会直接解析执行,所以减少了再次编译环节,能够有效提高系统性能,并预防 SQL 注入攻击(所以是系统默认也是我们推荐的语句处理器)。
CallableStatementHandler:存储过程处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.CallableStatement 对象的处理,很明了,它是用来调用存储过程的,增加了存储过程的函数调用以及输出/输入参数的处理支持。
RoutingStatementHandler:路由语句处理器,直接实现了 StatementHandler 接口,作用如其名称,确确实实只是起到了路由功能,并把上面介绍到的三个语句处理器实例作为自身的委托对象而已,所以执行器在构建语句处理器时,都是直接 new 了 RoutingStatementHandler 实例。

RoutingStatementHandler:路由。Mybatis实际使用的类,拦截的StatementHandler实际就是它。它会根据Exector类型创建对应的StatementHandler,保存到属性delegate中;

ResultSetHandler接口:处理Statement执行后产生的结果集,生成结果列表;处理存储过程执行后的输出参数;

DefaultResultSetHandler:ResultSetHandler的 默认实现类。

二、SqlSession的获取

SqlSessionFactory接口的默认实现是DefaultSqlSessionFactory,其中实现了很多openSession的重载方法用来获取SqlSession,有几个关键的参数:

// DefaultSqlSessionFactory的重载方法
@Override
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
  return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
  return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
  return openSessionFromDataSource(execType, null, autoCommit);
}

① ExecutorType

public enum ExecutorType {
  SIMPLE, REUSE, BATCH // 默认是SIMPLE
}

② autoCommit

// 是否自动提交
boolean autoCommit

③ TransactionIsolationLevel事务等级

public enum TransactionIsolationLevel {
  NONE(Connection.TRANSACTION_NONE),
  READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
  READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
  REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
  SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);
}

1、通过数据源获取SqlSession

// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;// 包装数据库连接。处理连接生命周期,包括:连接的创建、准备、提交/回滚和关闭。
  try {
    final Environment environment = configuration.getEnvironment();
    // 在配置文件中或者API方式定义默认的JdbcTransactionFactory
    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); // 创建默认的SqlSession
  } 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();
  }
}

我们来看一下创建执行器的逻辑:

// org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 默认是SIMPLE
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    // 开启缓存的话,使用装饰者模式进行装饰
    executor = new CachingExecutor(executor);
  }
  // 责任链模式,使用JDK动态代理增强所有的拦截器,为后续的拦截器的使用做准备
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

三、Mapper的获取与代理

1、从SqlSession获取Mapper

官方文档中提供了一种使用SqlSession做查询的实例:

try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

但是推荐使用下面的方式进行处理:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

我们对DefaultSqlSession的getMapper方法进行分析:

// org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}

// org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

// org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

我们发现,就是根据接口的Class类型,从MapperRegistry中获取,在SqlSessionFactory接口中我们分析到,在创建SqlSessionFactory时,会在MapperRegistry存放一个MapperProxyFactory,此时我们会直接拿出来这个MapperProxyFactory。

最终调用mapperProxyFactory.newInstance方法:

// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

最终使用就JDK动态代理,生成了一个代理类,用于处理mapper的方法。

2、执行Mapper方法前准备逻辑

上面说到,从SqlSession中获取的Mapper是被JDK动态代理过的(JDK动态代理请自行学习),当执行Mapper接口的方法时,会调用其关键方法为MapperProxy的invoke方法:

// org.apache.ibatis.binding.MapperProxy#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {// Object的方法直接放行,不需要代理
      return method.invoke(this, args);
    } else { // 调用invoke方法
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

// org.apache.ibatis.binding.MapperProxy#cachedInvoker
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
  try {
    // 如果缓存没有的话,将方法放入缓存
    return MapUtil.computeIfAbsent(methodCache, method, m -> {
      if (m.isDefault()) {// 如果是default方法,用这种方式执行
        try {
          if (privateLookupInMethod == null) {
            return new DefaultMethodInvoker(getMethodHandleJava8(method));
          } else {
            return new DefaultMethodInvoker(getMethodHandleJava9(method));
          }
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException
            | NoSuchMethodException e) {
          throw new RuntimeException(e);
        }
      } else { // 正常的方法,会创建PlainMethodInvoker,装饰MapperMethod
        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    });
  } catch (RuntimeException re) {
    Throwable cause = re.getCause();
    throw cause == null ? re : cause;
  }
}

最终会new一个MapperMethod,MapperMethod构造方法中包含着两个关键对象的创建:

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  this.command = new SqlCommand(config, mapperInterface, method);
  this.method = new MethodSignature(config, mapperInterface, method);
}

3、SqlCommand的创建

我们看一下SqlCommand的构造方法

// org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  final String methodName = method.getName();
  final Class<?> declaringClass = method.getDeclaringClass();
  // 获取MappedStatement
  MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
      configuration);
  if (ms == null) {
    // Flush注解标注的接口
    if (method.getAnnotation(Flush.class) != null) {
      name = null;
      type = SqlCommandType.FLUSH;
    } else { // 没找到XML对应的statement
      throw new BindingException("Invalid bound statement (not found): "
          + mapperInterface.getName() + "." + methodName);
    }
  } else { // 查找到MappedStatement
    name = ms.getId();
    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}

在resolveMappedStatement方法中,有以下逻辑:

// org.apache.ibatis.binding.MapperMethod.SqlCommand#resolveMappedStatement
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
    Class<?> declaringClass, Configuration configuration) {
  // 接口全限定名 + 方法名,就是statementId
  String statementId = mapperInterface.getName() + "." + methodName;
  if (configuration.hasStatement(statementId)) { // 在addMapper时就会解析xml
    return configuration.getMappedStatement(statementId);
  } else if (mapperInterface.equals(declaringClass)) {
    return null;
  }
  // 查询父接口
  for (Class<?> superInterface : mapperInterface.getInterfaces()) {
    if (declaringClass.isAssignableFrom(superInterface)) {
      MappedStatement ms = resolveMappedStatement(superInterface, methodName,
          declaringClass, configuration);
      if (ms != null) {
        return ms;
      }
    }
  }
  return null;
}

此时我们获取了MappedStatement并且包装好了SqlCommand。

到这,我们应该了解了MyBatis的Statement,其实和JDBC的Statement的含义有些区别,MyBatis的Statement其实就是代表着XML中的每一个SELECT、UPDATE、DELETE等标签中的SQL信息。

SqlCommand中包含着Mapper.xml的id与SQL类型。

4、构造MethodSignature

// MethodSignature的构造方法
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
  Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
  // 设置返回值类型
  if (resolvedReturnType instanceof Class<?>) {
    this.returnType = (Class<?>) resolvedReturnType;
  } else if (resolvedReturnType instanceof ParameterizedType) {
    this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
  } else {
    this.returnType = method.getReturnType();
  }
  // 是否返回void
  this.returnsVoid = void.class.equals(this.returnType);
  // 是否返回多条
  this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
  // 是否返回游标
  this.returnsCursor = Cursor.class.equals(this.returnType);
  // 是否返回Optional
  this.returnsOptional = Optional.class.equals(this.returnType);
  this.mapKey = getMapKey(method);
  this.returnsMap = this.mapKey != null;
  this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  this.paramNameResolver = new ParamNameResolver(configuration, method);
}

处理一些方法签名相关信息。

四、执行Mapper的核心方法

上面我们分析到,使用PlainMethodInvoker包装了MapperMethod,PlainMethodInvoker的invoke方法(JDK动态代理的)最终会调用MapperMethod的execute方法:

private static class PlainMethodInvoker implements MapperMethodInvoker {
  private final MapperMethod mapperMethod;

  public PlainMethodInvoker(MapperMethod mapperMethod) {
    super();
    this.mapperMethod = mapperMethod;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
  }
}

1、执行Mapper的方法逻辑

正常的方法会调用MapperMethod的execute方法,该方法中包含着对sql的操作增删改查:

// org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      // 返回值为void
      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()) { // 返回游标
        result = executeForCursor(sqlSession, args);
      } else { // 默认
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH: // FLUSH
      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 (" + method.getReturnType() + ").");
  }
  return result;
}

我们此处按照官方文档,只分析简单的SELECT查询,其他同理。

五、简单SELECT处理过程

1、对参数进行解析

先执行convertArgsToSqlCommandParam方法对参数进行解析:

// org.apache.ibatis.binding.MapperMethod.MethodSignature#convertArgsToSqlCommandParam
public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}

// org.apache.ibatis.reflection.ParamNameResolver#getNamedParams
public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) { // mapper中的sql不需要参数
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) { // mapper中的sql需要1个参数
    Object value = args[names.firstKey()];
    return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
  } else { // mapper中的sql需要多个参数
    final Map<String, Object> param = new ParamMap<>();
    int i = 0;
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      param.put(entry.getValue(), args[entry.getKey()]);
      // add generic param names (param1, param2, ...)
      final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
      // ensure not to overwrite parameter named with @Param
      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}

// org.apache.ibatis.reflection.ParamNameResolver#wrapToMapIfCollection
// 只需要一个参数时,需要判断是否是集合、数组或者普通类型
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
  if (object instanceof Collection) { 
    ParamMap<Object> map = new ParamMap<>();
    map.put("collection", object);
    if (object instanceof List) {
      map.put("list", object);
    }
    Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
    return map;
  } else if (object != null && object.getClass().isArray()) {
    ParamMap<Object> map = new ParamMap<>();
    map.put("array", object);
    Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
    return map;
  }
  return object;
}

2、DefaultSqlSession的selectOne逻辑

解析完参数之后,然后执行DefaultSqlSession的selectOne逻辑:

// org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
@Override
public <T> T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  List<T> list = this.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;
  }
}

// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)
@Override
public <E> List<E> selectList(String statement, Object parameter) {
  // RowBounds:返回结果集最大值
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}

// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    // 获取到已经处理过的MappedStatement ,就是对应Mapper中的sql等信息
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 最终调用执行器的query
    // RowBounds是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页)
	// wrapCollection(parameter)是用来装饰集合或者数组参数
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

3、Executor的query

Executor接口中包含了增删改查以及提交回滚与对缓存的处理,是真正的sql执行器。

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

Configuration中cacheEnabled属性值默认为true,会执行CachingExecutor的query方法:

org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  // 获取SQL基本信息,sql参数会变成 ? 比如“SELECT * FROM user WHERE id = ? ”
  BoundSql boundSql = ms.getBoundSql(parameter);
  // 获取缓存key,通过算法确保key的唯一性
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    // 获取二级缓存
  Cache cache = ms.getCache();
  if (cache != null) {
  	// 当为select语句时,flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存
	// 当为insert、update、delete语句时,useCache默认为true,表示会将本条语句的结果进行二级缓存
	// 刷新二级缓存 (存在缓存且flushCache为true时)
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      // // 从二级缓存中查询数据
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      // 如果二级缓存中没有查询到数据,则查询数据库
      if (list == null) {
        // 委托给BaseExecutor执行
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  // 委托给BaseExecutor执行
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

// BaseExecutor执行query
// org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
@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.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    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 {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

4、查询数据库的逻辑

如果一级缓存没有数据,则从数据库查询数据。

// org.apache.ibatis.executor.BaseExecutor#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 { // 真正的执行数据库逻辑
    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;
}

在doQuery方法中,最终执行的是SimpleExecutor的doQuery方法,正式进行JDBC那一套了:

// org.apache.ibatis.executor.SimpleExecutor#doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null; // java.sql.Statement 
  try {
    // 获取Configuration对象
    Configuration configuration = ms.getConfiguration();
    // 创建RoutingStatementHandler,用来处理Statement
	// RoutingStatementHandler类中初始化delegate类(SimpleStatementHandler、PreparedStatementHandler)
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // 子流程1:设置参数
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    // Statement关闭
    closeStatement(stmt);
  }
}

(1)prepareStatement

我们看一下准备Statement的逻辑:

// org.apache.ibatis.executor.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);// 获取数据库连接
  // 创建Statement(PreparedStatement、Statement、CallableStatement)
  stmt = handler.prepare(connection, transaction.getTimeout());
  // 相关参数处理
  handler.parameterize(stmt);
  return stmt;
}

获取连接的逻辑:

// org.apache.ibatis.executor.BaseExecutor#getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection(); // 从事务管理器中拿到连接
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

// org.apache.ibatis.transaction.jdbc.JdbcTransaction#getConnection
@Override
public Connection getConnection() throws SQLException {
  if (connection == null) {
    openConnection();
  }
  return connection;
}

protected void openConnection() throws SQLException {
  if (log.isDebugEnabled()) {
    log.debug("Opening JDBC Connection");
  }
  connection = dataSource.getConnection();
  if (level != null) {
    connection.setTransactionIsolation(level.getLevel());
  }
  setDesiredAutoCommit(autoCommit);
}

我们发现,获取链接就是默认从DataSource中获取链接。

MyBatis中对Connection 的管理是交给Transaction接口管理的,Transaction接口包含了对Connection 的获取、提交回滚、关闭等操作。
在其实现类中,实际上最终的获取和关闭Connection ,是由DataSource来处理的,相当于使用Transaction接口做了一层包装。

拿到Connection 之后,就开始获取Statement了:

// org.apache.ibatis.executor.statement.BaseStatementHandler#prepare
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    // connection.createStatement() 创建Statement
    statement = instantiateStatement(connection);
    // 设置事务超时时间
    setStatementTimeout(statement, transactionTimeout);
    setFetchSize(statement);
    return statement;
  } catch (SQLException e) {
    closeStatement(statement);
    throw e;
  } catch (Exception e) {
    closeStatement(statement);
    throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
  }
}

获取完Statement,开始设置参数:

// org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize
@Override
public void parameterize(Statement statement) throws SQLException {
// 通过ParameterHandler处理参数
  parameterHandler.setParameters((PreparedStatement) statement);
}

// org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
@Override
public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    for (int i = 0; i < parameterMappings.size(); i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        try {
          // 调用typeHandler的setParameter方法
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException | SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}

到此时,Statement已经准备就绪了,也通过typeHandler将参数注入到Statement中了。

5、StatementHandler的query方法

// org.apache.ibatis.executor.statement.RoutingStatementHandler#query
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  return delegate.query(statement, resultHandler);
}

// org.apache.ibatis.executor.statement.PreparedStatementHandler#query
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute(); // 执行,如果是true就可以拿到ResultSet
  return resultSetHandler.handleResultSets(ps); // 提取结果集
}

6、处理结果集

execute执行完毕之后就是拿到结果集了:

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
  // <select>标签的resultMap属性,可以指定多个值,多个值之间用逗号(,)分割
  final List<Object> multipleResults = new ArrayList<>();

  int resultSetCount = 0;
  // 这里是获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // 这里是获取所有要映射的ResultMap(按照逗号分割出来的)
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  // 要映射的ResultMap的数量
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  // 循环处理每个ResultMap,从第一个开始处理
  while (rsw != null && resultMapCount > resultSetCount) { // 开始循环
    // 得到结果映射信息
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // 处理结果集
	// 从rsw结果集参数中获取查询结果,再根据resultMap映射信息,将查询结果映射到multipleResults中
    handleResultSet(rsw, resultMap, multipleResults, null);
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet(); // 清理结果集
    resultSetCount++;
  }

  // 对应<select>标签的resultSets属性,一般不使用该属性
  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);
}

(1)处理结果集

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    if (parentMapping != null) {
      // 处理行数据
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      if (resultHandler == null) {
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); // 处理行数据
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        // 处理行数据
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    // 关闭ResultSet
    // issue #228 (close resultsets)
    closeResultSet(rsw.getResultSet());
  }
}

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  // 是否有内置嵌套的结果映射
  if (resultMap.hasNestedResultMaps()) {
    ensureNoRowBounds();
    checkResultHandler();
    // 嵌套结果映射
    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  } else {
    // 处理简单的结果集
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  }
}

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  // 获取结果集信息
  ResultSet resultSet = rsw.getResultSet();
  // 使用rowBounds的分页信息,进行逻辑分页(也就是在内存中分页)
  skipRows(resultSet, rowBounds);
  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
    // 循环处理结果
    // 通过<resultMap>标签的子标签<discriminator>对结果映射进行鉴别
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
    // 将查询结果封装到POJO中
    Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
    // 处理对象嵌套的映射关系// 使用ResultHandler进行结果集映射
    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }
}

// 将查询结果封装到POJO中
// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.lang.String)
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  // 延迟加载的映射信息
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
   // 通过反射创建对象
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue); // 创建MetaObject
    boolean foundValues = this.useConstructorMappings;
    // 是否应用自动映射,也就是通过resultType进行映射
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      // 根据columnName和type属性名映射赋值(Mapper中的resultType映射)
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    }
    // 根据我们配置ResultMap的column和property映射赋值,也就是Mapper中的resultMap映射
	// 如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  return rowValue;
}

(2)创建映射结果对象

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, org.apache.ibatis.executor.loader.ResultLoaderMap, java.lang.String)
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  this.useConstructorMappings = false; // reset previous mapping result
  final List<Class<?>> constructorArgTypes = new ArrayList<>();
  final List<Object> constructorArgs = new ArrayList<>();
  // 创建结果映射的PO类对象
  Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
  if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    // 获取要映射的PO类的属性信息
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      // issue gcode #109 && issue #149
      // 延迟加载处理
      if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
        // 通过动态代理工厂,创建延迟加载的代理对象
        resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
        break;
      }
    }
  }
  this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
  return resultObject;
}

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.util.List<java.lang.Class<?>>, java.util.List<java.lang.Object>, java.lang.String)
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
    throws SQLException {
  final Class<?> resultType = resultMap.getType();
  final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
  final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
  if (hasTypeHandlerForResultObject(rsw, resultType)) {
    return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
  } else if (!constructorMappings.isEmpty()) {
    return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
  } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
    // 对象工厂创建对象
    return objectFactory.create(resultType);
  } else if (shouldApplyAutomaticMappings(resultMap, false)) {
    return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
  }
  throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

(3)根据columnName和type属性名映射赋值

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyAutomaticMappings
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  boolean foundValues = false;
  if (!autoMapping.isEmpty()) {
    for (UnMappedColumnAutoMapping mapping : autoMapping) {
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
        // gcode issue #377, call setter on nulls (value is not 'found')
        metaObject.setValue(mapping.property, value);
      }
    }
  }
  return foundValues;
}

(4)applyPropertyMappings

根据我们配置ResultMap的column和property映射赋值,如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyPropertyMappings
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  boolean foundValues = false;
  final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  for (ResultMapping propertyMapping : propertyMappings) {
    String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    if (propertyMapping.getNestedResultMapId() != null) {
      // the user added a column attribute to a nested result map, ignore it
      column = null;
    }
    if (propertyMapping.isCompositeResult()
        || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
        || propertyMapping.getResultSet() != null) {
      Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
      // issue #541 make property optional
      final String property = propertyMapping.getProperty();
      if (property == null) {
        continue;
      } else if (value == DEFERRED) {
        foundValues = true;
        continue;
      }
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
        // gcode issue #377, call setter on nulls (value is not 'found')
        metaObject.setValue(property, value);
      }
    }
  }
  return foundValues;
}

写在后面

如果本文对你有帮助,请点赞收藏关注一下吧 ~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/380887.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【蓝桥杯试题】 递归实现指数型枚举例题

&#x1f483;&#x1f3fc; 本人简介&#xff1a;男 &#x1f476;&#x1f3fc; 年龄&#xff1a;18 &#x1f91e; 作者&#xff1a;那就叫我亮亮叭 &#x1f4d5; 专栏&#xff1a;蓝桥杯试题 文章目录1. 题目描述2. 思路解释2.1 时间复杂度2.2 递归3. 代码展示最后&#x…

超级简单又功能强大还免费的电路仿真软件

设计电路的时候经常需要进行一些电路仿真。常用的仿真软件很多&#xff0c;由于大学里经常使用Multisim作为教学软件&#xff0c;所以基本上所有从事硬件开发的人都听过或者用过Multisim这个软件。这个软件最大的好处就是简单直观&#xff0c;可以在自己的PC上搭建电路并使用软…

gdb常用命令详解

gdb常用调试命令概览和说明 run命令 在默认情况下&#xff0c;gdbfilename只是attach到一个调试文件&#xff0c;并没有启动这个程序&#xff0c;我们需要输入run命令启动这个程序&#xff08;run命令被简写成r&#xff09;。如果程序已经启动&#xff0c;则再次输入 run 命令…

从面试官角度告诉你高级性能测试工程师面试必问的十大问题

目录 1、介绍下最近做过的项目&#xff0c;背景、预期指标、系统架构、场景设计及遇到的性能问题&#xff0c;定位分析及优化&#xff1b; 2、项目处于什么阶段适合性能测试介入&#xff0c;原因是什么&#xff1f; 3、性能测试场景设计要考虑哪些因素&#xff1f; 4、对于一…

SAP MM学习笔记4-在库类型都有哪些,在库类型有哪些控制点

SAP MM模块中的在库类型有3种&#xff1a; 1&#xff0c;利用可能在库 (非限制使用库存) 2&#xff0c;品质检查中在库 &#xff08;质检库存&#xff09; 3&#xff0c;保留在库&#xff08;已冻结库存&#xff09; 这3种在库标识该物料的状态&#xff0c;是否可用。 这3种…

bugku 安全加固1

js劫持 根据题目所给出的ip访问原本应该进入一个学院的二手交易网站 但是实际进入了一个博客 flag需要去除最后的斜杆 黑客首次webshell密码 利用所给的账户密码进行登录进入www目录并且进行备份 #我们对网站进行备份 cd /var/www && tar -czvf /tmp/html.tgz html …

Kubernetes之存储管理(上)

数据持久化的主要方式简介 pod是临时的&#xff0c;pod中的数据随着pod生命周期的结束也会被一起删除。 pod想实现数据持久化主要有以下几种方式&#xff1a; emptyDir&#xff1a;类似于docker run –v /xx&#xff0c;在物理机里随机产生一个目录(这个目录其实挂载的是物理…

墨天轮2022年度数据库获奖名单

2022年&#xff0c;国家相继从高位部署、省级试点布局、地市重点深入三个维度&#xff0c;颁布了多项中国数据库行业发展的利好政策。但是我们也能清晰地看到&#xff0c;中国数据库行业发展之路道阻且长&#xff0c;而道路上的“拦路虎”之一则是生态。中国数据库的发展需要多…

如何创建发布新品上市新闻稿

推出新产品对任何企业来说都是一个激动人心的时刻&#xff0c;但向潜在客户宣传并围绕您的新产品引起轰动也可能是一个挑战。最有效的方法之一就是通过发布新品上市新闻稿。精心制作的新闻稿可以帮助我们通过媒体报道、吸引并在目标受众中引起关注。下面&#xff0c;我们将讲述…

计算机组成原理4小时速成2:计算机运算方法,原码,反码,补码,移位,加法减法,乘除法

计算机组成原理4小时速成2&#xff1a;计算机运算方法&#xff0c;原码&#xff0c;反码&#xff0c;补码&#xff0c;移位&#xff0c;加法减法&#xff0c;乘除法 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多…

OpenCV入门(二)快速学会OpenCV1图像基本操作

OpenCV入门&#xff08;一&#xff09;快速学会OpenCV1图像基本操作 不讲大道理&#xff0c;直接上干货。操作起来。 众所周知&#xff0c;OpenCV 是一个跨平台的计算机视觉库, 支持多语言, 功能强大。今天就从读取图片&#xff0c;显示图片&#xff0c;输出图片信息和简单的…

记录自己遇到的关于Hashmap的面试题

一.麻烦讲述一下Hashmap的扩容原理 jdk1.8中的hashmap扩容原理 1.put流程图 首先贴一张图(图片来源于传送门&#xff09;&#xff0c;多谢大佬的美图&#xff0c;此图已经完美的描述了put的整个流程&#xff0c;我也就不想自己画了&#xff0c;嘿嘿: 2.hashmap中几个比较重…

hive临时目录清理

hive运行失败会导致临时目录无法自动清理&#xff0c;因此需要自己写脚本去进行清理 实际发现hive临时目录有两个&#xff1a; /tmp/hive/{user}/* /warehouse/tablespace//hive/**/.hive-staging_hive 分别由配置hive.exec.scratchdir和hive.exec.stagingdir决定: 要注意的…

requests---(4)发送post请求完成登录

前段时间写过一个通过cookies完成登录&#xff0c;今天我们写一篇通过post发送请求完成登录豆瓣网 模拟登录 1、首先找到豆瓣网的登录接口 打开豆瓣网站的登录接口&#xff0c;请求错误的账号密码&#xff0c;通过F12或者抓包工具找到登录接口 通过F12抓包获取到请求登录接口…

每日分享(微信社区小程序/h5/圈子论坛贴吧交友/博客/社交)

1.Java单元测试实战 高清PDF中文版 Java单元测试实战来自于作者多年来的单元测试实践&#xff0c;最初发表在阿里内网的ATA上&#xff0c;成为了很多阿里同学单元测试学习的必读文章。很多程序员认为单元测试会花费大量的时间&#xff0c;因此他们写单元测试的意愿比较低&…

【同步工具类:Semaphore】

同步工具类:Semaphore介绍源码分析构造函数acquire 获取信号量release 释放信号量业务场景代码测试结果总结介绍 官方说明: Semaphore用于限制可以访问某些资源&#xff08;物理或逻辑的&#xff09;的线程数目&#xff0c;他维护了一个许可证集合&#xff0c;有多少资源需要限…

vue2、vue3组件传值,引用类型,对象数组如何处理

vue2、vue3组件传值&#xff0c;引用类型&#xff0c;对象数组如何处理 Excerpt 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定&#xff1a;父级 prop 的更新会向下流动到子组件中&#xff0c;但是反过来则不行。这样会防止从子组件意外变更父… 下述组件传值指引…

【Go|第1期】Go遍历目录的三种方法

日期&#xff1a;2023年3月1日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xff…

web,h5海康视频接入监控视频流记录一

项目需求&#xff0c;web端实现海康监控视频对接接入&#xff0c;需实现实时预览&#xff0c;云台功能&#xff0c;回放功能。 web端要播放视频&#xff0c;有三种方式&#xff0c;一种是装浏览器装插件&#xff0c;一种是装客户端exe&#xff0c;还有就是无插件了。浏览器装插…

垃圾回收的概念与算法(第四章)

《实战Java虚拟机&#xff1a;JVM故障诊断与性能优化 (第2版)》 第4章 垃圾回收的概念与算法 目标&#xff1a; 了解什么是垃圾回收学习几种常用的垃圾回收算法掌握可触及性的概念理解 Stop-The-World&#xff08;STW&#xff09; 4.1. 认识垃圾回收 - 内存管理清洁工 垃圾…