Mybatis #foreach中相同的变量名导致值覆盖的问题解决

目录
  • 背景
  • 问题原因(简略版)
  • Mybatis流程源码解析(长文警告,按需自取)
    • 一、获取SqlSessionFactory
    • 二、获取SqlSession
    • 三、执行SQL

背景

使用Mybatis中执行如下查询:

单元测试

@Test
public void test1() {
    String resource = "mybatis-config.xml";
    InputStream inputStream = null;
    try {
        inputStream = Resources.getResourceAsStream(resource);
    } catch (IOException e) {
        e.printStackTrace();
    }
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        CommonMapper mapper = sqlSession.getMapper(CommonMapper.class);
        QueryCondition queryCondition = new QueryCondition();
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        queryCondition.setWidthList(list);
        System.out.println(mapper.findByCondition(queryCondition));
    }
}

XML

<select id="findByCondition" parameterType="cn.liupjie.pojo.QueryCondition" resultType="cn.liupjie.pojo.Test">
    select * from test
    <where>
        <if test="id != null">
            and id = #{id,jdbcType=INTEGER}
        </if>
        <if test="widthList != null and widthList.size > 0">
            <foreach collection="widthList" open="and width in (" close=")" item="width" separator=",">
                #{width,jdbcType=INTEGER}
            </foreach>
        </if>
        <if test="width != null">
            and width = #{width,jdbcType=INTEGER}
        </if>
    </where>
</select>

打印的SQL:
DEBUG [main] - ==>  Preparing: select * from test WHERE width in ( ? , ? , ? ) and width = ?
DEBUG [main] - ==> Parameters: 1(Integer), 2(Integer), 3(Integer), 3(Integer)

Mybatis版本

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.1</version>
</dependency>

这是公司的老项目,在迭代的过程中遇到了此问题,以此记录!
PS: 此bug在mybatis-3.4.5版本中已经解决。并且Mybatis维护者也建议不要在item/index中使用重复的变量名。

问题原因(简略版)

  • 在获取到DefaultSqlSession之后,会获取到Mapper接口的代理类,通过调用代理类的方法来执行查询
  • 真正执行数据库查询之前,需要将可执行的SQL拼接好,此操作在DynamicSqlSource#getBoundSql方法中执行
  • 当解析到foreach标签时,每次循环都会缓存一个item属性值与变量值之间的映射(如:width:1),当foreach标签解析完成后,缓存的参数映射关系中就保留了一个(width:3)
  • 当解析到最后一个if标签时,由于width变量有值,因此if判断为true,正常执行拼接,导致出错
  • 3.4.5版本中,在foreach标签解析完成后,增加了两行代码来解决这个问题。
 //foreach标签解析完成后,从bindings中移除item
  context.getBindings().remove(item);
  context.getBindings().remove(index);

Mybatis流程源码解析(长文警告,按需自取)

一、获取SqlSessionFactory

入口,跟着build方法走

//获取SqlSessionFactory, 解析完成后,将XML中的内容封装到一个Configuration对象中,
//使用此对象构造一个DefaultSqlSessionFactory对象,并返回
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

来到SqlSessionFactoryBuilder#build方法

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //获取XMLConfigBuilder,在XMLConfigBuilder的构造方法中,会创建XPathParser对象
    //在创建XPathParser对象时,会将mybatis-config.xml文件转换成Document对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //调用XMLConfigBuilder#parse方法开始解析Mybatis的配置文件
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

跟着parse方法走,来到XMLConfigBuilder#parseConfiguration方法

private void parseConfiguration(XNode root) {
  try {
    Properties settings = settingsAsPropertiess(root.evalNode("settings"));
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    loadCustomVfs(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    //这里解析mapper
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

来到mapperElement方法

//本次mappers配置:<mapper resource="xml/CommomMapper.xml"/>
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          //因此走这里,读取xml文件,并开始解析
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          //这里同上文创建XMLConfigBuilder对象一样,在内部构造时,也将xml文件转换为了一个Document对象
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          //解析
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

XMLMapperBuilder类,负责解析SQL语句所在XML中的内容

//parse方法
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    //解析mapper标签
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingChacheRefs();
  parsePendingStatements();
}

//configurationElement方法
private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    //解析各种类型的SQL语句:select|insert|update|delete
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
  }
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    //创建XMLStatementBuilder对象
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      //解析
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

XMLStatementBuilder负责解析单个select|insert|update|delete节点

public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");
  //判断databaseId是否匹配,将namespace+'.'+id拼接,判断是否已经存在此id
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }

  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  //获取参数类型
  String parameterType = context.getStringAttribute("parameterType");
  //获取参数类型的class对象
  Class<?> parameterTypeClass = resolveClass(parameterType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultType = context.getStringAttribute("resultType");
  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);
  //获取resultType的class对象
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultSetType = context.getStringAttribute("resultSetType");
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  //获取select|insert|update|delete类型
  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  //获取SqlSource对象,langDriver为默认的XMLLanguageDriver,在new Configuration时设置
  //若sql中包含元素节点或$,则返回DynamicSqlSource,否则返回RawSqlSource
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  String resultSets = context.getStringAttribute("resultSets");
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
  }

  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

二、获取SqlSession

由上文可知,此处的SqlSessionFactory使用的是DefaultSqlSessionFactory

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);
    //创建执行器,默认是SimpleExecutor
    //如果在配置文件中开启了缓存(默认开启),则是CachingExecutor
    final Executor executor = configuration.newExecutor(tx, execType);
    //返回DefaultSqlSession对象
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } 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();
  }
}

这里获取到了一个DefaultSqlSession对象

三、执行SQL

获取CommonMapper的对象,这里CommonMapper是一个接口,因此是一个代理对象,代理类是MapperProxy

org.apache.ibatis.binding.MapperProxy@72cde7cc

执行Query方法,来到MapperProxy的invoke方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if (Object.class.equals(method.getDeclaringClass())) {
    try {
      return method.invoke(this, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
  //缓存
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  //执行操作:select|insert|update|delete
  return mapperMethod.execute(sqlSession, args);
}

执行操作时,根据SELECT操作,以及返回值类型(反射方法获取)确定executeForMany方法

caseSELECT:
  if (method.returnsVoid() && method.hasResultHandler()) {
    executeWithResultHandler(sqlSession, args);
    result = null;
  } else if (method.returnsMany()) {
    result = executeForMany(sqlSession, args);
  } else if (method.returnsMap()) {
    result = executeForMap(sqlSession, args);
  } else if (method.returnsCursor()) {
    result = executeForCursor(sqlSession, args);
  } else {
    Object param = method.convertArgsToSqlCommandParam(args);
    result = sqlSession.selectOne(command.getName(), param);
  }
  break;

来到executeForMany方法中,就可以看到执行查询的操作,由于这里没有进行分页查询,因此走else

if (method.hasRowBounds()) {
  RowBounds rowBounds = method.extractRowBounds(args);
  result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
  result = sqlSession.<E>selectList(command.getName(), param);
}

来到DefaultSqlSession#selectList方法中

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //根据key(namespace+"."+id)来获取MappedStatement对象
    //MappedStatement对象中封装了解析好的SQL信息
    MappedStatement ms = configuration.getMappedStatement(statement);
    //通过CachingExecutor#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();
  }
}

CachingExecutor#query

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
 //解析SQL为可执行的SQL
 BoundSql boundSql = ms.getBoundSql(parameter);
 //获取缓存的key
 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
 //执行查询
 return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

MappedStatement#getBoundSql

public BoundSql getBoundSql(Object parameterObject) {
 //解析SQL
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings == null || parameterMappings.isEmpty()) {
    boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
  }

  //检查是否有嵌套的ResultMap
  // 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;
}

由上文,此次语句由于SQL中包含元素节点,因此是DynamicSqlSource。由此来到DynamicSqlSource#getBoundSql。
rootSqlNode.apply(context);这段代码便是在执行SQL解析。

@Override
public BoundSql getBoundSql(Object parameterObject) {
  DynamicContext context = new DynamicContext(configuration, parameterObject);
  //执行SQL解析
  rootSqlNode.apply(context);
  SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
  SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
    boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
  }
  return boundSql;
}

打上断点,跟着解析流程,来到解析foreach标签的代码,ForEachSqlNode#apply

@Override
public boolean apply(DynamicContext context) {
  Map<String, Object> bindings = context.getBindings();
  final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
  if (!iterable.iterator().hasNext()) {
    return true;
  }
  boolean first = true;
  //解析open属性
  applyOpen(context);
  int i = 0;
  for (Object o : iterable) {
    DynamicContext oldContext = context;
    if (first) {
      context = new PrefixedContext(context, "");
    } else if (separator != null) {
      context = new PrefixedContext(context, separator);
    } else {
        context = new PrefixedContext(context, "");
    }
    int uniqueNumber = context.getUniqueNumber();
    // Issue #709
    //集合中的元素是Integer,走else
    if (o instanceof Map.Entry) {
      @SuppressWarnings("unchecked")
      Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
      applyIndex(context, mapEntry.getKey(), uniqueNumber);
      applyItem(context, mapEntry.getValue(), uniqueNumber);
    } else {
      //使用index属性
      applyIndex(context, i, uniqueNumber);
      //使用item属性
      applyItem(context, o, uniqueNumber);
    }
    //当foreach中使用#号时,会将变量替换为占位符(类似__frch_width_0)(StaticTextSqlNode)
    //当使用$符号时,会将值直接拼接到SQL中(TextSqlNode)
    contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
    if (first) {
      first = !((PrefixedContext) context).isPrefixApplied();
    }
    context = oldContext;
    i++;
  }
  applyClose(context);
  return true;
}

private void applyItem(DynamicContext context, Object o, int i) {
    if (item != null) {
        //在参数映射中绑定item属性值与集合值的关系
        //第一次:(width:1)
        //第二次:(width:2)
        //第三次:(width:3)
        context.bind(item, o);
        //在参数映射中绑定处理后的item属性值与集合值的关系
        //第一次:(__frch_width_0:1)
        //第二次:(__frch_width_1:2)
        //第三次:(__frch_width_2:3)
        context.bind(itemizeItem(item, i), o);
    }
  }

到这里,结果就清晰了,在解析foreach标签时,每次循环都会将item属性值与参数集合中的值进行绑定,到最后就会保留(width:3)的映射关系,而在解析完foreach标签后,会解析最后一个if标签,此时在判断if标签是否成立时,答案是true,因此最终拼接出来一个错误的SQL。

在3.4.5版本中,代码中增加了context.getBindings().remove(item);在foreach标签解析完成后移除bindings中的参数映射。以下是源码:

@Override
public boolean apply(DynamicContext context) {
  Map<String, Object> bindings = context.getBindings();
  final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
  if (!iterable.iterator().hasNext()) {
    return true;
  }
  boolean first = true;
  applyOpen(context);
  int i = 0;
  for (Object o : iterable) {
    DynamicContext oldContext = context;
    if (first || separator == null) {
      context = new PrefixedContext(context, "");
    } else {
      context = new PrefixedContext(context, separator);
    }
    int uniqueNumber = context.getUniqueNumber();
    // Issue #709
    if (o instanceof Map.Entry) {
      @SuppressWarnings("unchecked")
      Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
      applyIndex(context, mapEntry.getKey(), uniqueNumber);
      applyItem(context, mapEntry.getValue(), uniqueNumber);
    } else {
      applyIndex(context, i, uniqueNumber);
      applyItem(context, o, uniqueNumber);
    }
    contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
    if (first) {
      first = !((PrefixedContext) context).isPrefixApplied();
    }
    context = oldContext;
    i++;
  }
  applyClose(context);
  //foreach标签解析完成后,从bindings中移除item
  context.getBindings().remove(item);
  context.getBindings().remove(index);
  return true;
}

到此这篇关于Mybatis #foreach中相同的变量名导致值覆盖的问题解决的文章就介绍到这了,更多相关Mybatis #foreach相同变量名覆盖内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Mybatis foreach标签使用不当导致异常的原因浅析

    异常产生场景及异常信息 上周,由于在Mybatis的Mapper接口方法中使用实现了Map.Entry接口的泛型类,同时此方法对应的sql语句也使用了foreach标签,导致出现了异常.如下为异常信息: org.apache.ibatis.exceptions.PersistenceException: ### Error updating database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no

  • 解决mybatis批量更新(update foreach)失败的问题

    如下所示: <!--批量更新报表 --> <update id="updateIssueByBatch" parameterType="java.util.List"> <foreach collection="issueList" item="item" index="index" separator=";"> update sys_issue &l

  • Mybatis中动态SQL,if,where,foreach的使用教程详解

    MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑. MyBatis中用于实现动态SQL的元素主要有: if choose(when,otherwise) trim where set foreach mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接.组装. 1.statement中直接定义使用动态SQL: 在statement中利用if 和 where 条件组合达到我们的需求,通过一个例子来说明: 原SQL语句

  • MyBatis传入数组集合类并使用foreach遍历

    这篇文章主要介绍了MyBatis传入数组集合类并使用foreach遍历,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在mapper中传入数组或集合类,使用foreach标签遍历出其中的值与SQL语句拼接 JAVA dao层接口 public interface UserDao { public List<User> getUsersByCollection(Collection collection); } mapper文件 <sel

  • mybatis3.4.6 批量更新 foreach 遍历map 的正确姿势详解

    好久没编码了!最近开始编码遇到一个问题 !一个批量修改的问题,就是mybatis foreach 的使用. 当时使用的场景 ,前端 传逗号拼接的字符串id, 修改id对应数据的数据顺序 ,顺序 就是id 的顺序. 就是一个条件(单个id值) 修改一个值(传入的id的顺序) , 1. 把条件作为Map 的key 修改值是value,用map入参 2.用List<Object> 或者数组 ,把条件和值封装成对象放进list集合或者array数组 3.代码使用for循环调用mapper方法 穿两个参

  • mybatis-plus  mapper中foreach循环操作代码详解(新增或修改)

    .循环添加 接口处: 分别是 void 无返回类型 :有的话是(resultType)返回类型,参数类型(parameterType) list , 如: 在mapper文件中分别对应ID,参数类型和返回类型. 循环处理,如下: <insert id="insertPack" parameterType="java.util.List"> insert into t_ev_bu_pack ( PACK_CODE, BIN, PACK_PROD_TIME,

  • mybatis foreach标签的使用详解

    mybatis的foreach标签经常用于遍历集合,构建in条件语句或者批量操作语句. 下面是foreach标签的各个属性 属性 描述 collection 表示迭代集合的名称,可以使用@Param注解指定,如下图所示 该参数为必选 item 表示本次迭代获取的元素,若collection为List.Set或者数组,则表示其中的元素:若collection为map,则代表key-value的value,该参数为必选 open 表示该语句以什么开始,最常用的是左括弧'(',注意:mybatis会将

  • MyBatis的foreach语句详解

    foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合.foreach元素的属性主要有 item,index,collection,open,separator,close.item表示集合中每一个元素进行迭代时的别名,index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置,open表示该语句以什么开始,separator表示在每次进行迭代之间以什么符号作为分隔 符,close表示以什么结束,在使用foreach的时候最关键的也是最容易出错的就是collectio

  • Mybatis动态SQL之if、choose、where、set、trim、foreach标记实例详解

    动态SQL就是动态的生成SQL. if标记 假设有这样一种需求:查询用户,当用户名不等于"admin"的时候,我们还需要密码为123456. 数据库中的数据为: MyBatisConfig.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

  • Mybatis #foreach中相同的变量名导致值覆盖的问题解决

    目录 背景 问题原因(简略版) Mybatis流程源码解析(长文警告,按需自取) 一.获取SqlSessionFactory 二.获取SqlSession 三.执行SQL 背景 使用Mybatis中执行如下查询: 单元测试 @Test public void test1() { String resource = "mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.get

  • 利用反射获取Java类中的静态变量名及变量值的简单实例

    JAVA可以通过反射获取成员变量和静态变量的名称,局部变量就不太可能拿到了. public class Test { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub //获取所有变量的值 Class clazz = Class.forName("com.qianmingxs.ScoreTable"); Field[] fields = clazz.g

  • Tensorflow 如何从checkpoint文件中加载变量名和变量值

    假设你已经经过上千次的迭代,并且得到了以下模型: 则从这些checkpoint文件中加载变量名和变量值代码如下: model_dir = './ckpt-182802' import tensorflow as tf from tensorflow.python import pywrap_tensorflow reader = pywrap_tensorflow.NewCheckpointReader(model_dir) var_to_shape_map = reader.get_varia

  • Android编程中TextView宽度过大导致Drawable无法居中问题解决方法

    本文实例讲述了Android编程中TextView宽度过大导致Drawable无法居中问题解决方法.分享给大家供大家参考,具体如下: 在做项目的时候,很多时候我们都要用到文字和图片一起显示,一般设置TextView的DrawableLeft.DrawableRight.DrawableTop.DrawableBottom就行了.但是有一种情况是当TextView的熟悉是fill_parent或者使用权重的时候并且设置了起Gravity的ceter的时候,Drawable图片是无法一起居中的,为了

  • Python自动打印被调用函数变量名及对应值 

    目录 1.软件环境 2.问题描述 3.解决方法 4.结果预览 1.软件环境 Windows10 教育版64位Python 3.6.3 2.问题描述 我们在定义一个函数或者是调用一个函数的时候,总是希望能够知道传入该被调用函数的具体值是多少?是否符合我们的预期?因此我们往往会将我们关心的值给打印出来(当然debug也可以,但不能每次都debug吧?),如下,我们创建了一个initial_printer示例函数: def initial_printer(variable_a, variable_b,

  • logback 实现给变量指定默认值

    目录 logback 实现给变量指定默认值 格式是 ${变量名:-默认值} logback变量 定义变量 在 logback.xml 中定义变量 在命令行定义变量 引入properties文件 变量的作用域 变量的默认值 变量使用 logback 实现给变量指定默认值 格式是 ${变量名:-默认值} **光有冒号还不够,再加条短线后面才是默认值** <appender name="info" class="ch.qos.logback.core.rolling.Roll

  • PHP中获取变量的变量名的一段代码的bug分析

    复制代码 代码如下: /** * 获取变量名 * * @param $string * @return $string * * $test = "helo"; * $test2 = "helo"; * getVarName($test2); */ function getVarName(&$src){ //存储当前变量值 $save = $src; //存储所有变量值 $allvar = $GLOBALS; //在函数中不要直拉遍历$GLOBALS,会出现堆

  • MyBatis中foreach标签的collection属性的取值方式

    目录 foreach标签的collection属性的取值 传的是List列表 传的是Array数组 传的是Map collection属性总结 MyBatis使用foreach标签报错 原因 解决方案 foreach标签的collection属性的取值 传的是List列表 接口代码 List<Emp> findEmpByDeptnos(List<Integer> deptnos); xml配置代码 <select id="findEmpByDeptnos"

  • 浅谈js中的变量名和函数名重名

    今天骚凯问了一道变量名冲突的题目,感觉很有意思,顺便也复习一下预解析的一些知识,有不对的地方忘前辈大神指正,题目是这样的: var a=100; function a(){ console.log(a); } a(); 这个串代码执行完会报错 : a is not a function 问题来了,为什么会报这个错误呢? 这里涉及到函数和变量的预解析: 1)函数声明会置顶 2)变量声明也会置顶 3)函数声明比变量声明更置顶:(函数在变量上面) 4)变量和赋值语句一起书写,在js引擎解析时,会将其拆

随机推荐