Mybatis#foreach中相同的变量名导致值覆盖

背景

使用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中使用重复的变量名。

1.png

2.png

问题原因(简略版)

  • 在获取到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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值