baomidou/mybatis-plus · error · TooManyResultsException

Expected one result (or null) to be returned by selectOne(),

Error message

Expected one result (or null) to be returned by selectOne(), but found: %s

What it means

BaseMapper.selectOne(wrapper, throwEx=true) executed the query, received more than one row, and threw TooManyResultsException because strict mode was requested. With throwEx=false it would silently return the first row; the boolean overload exists to make one-row expectations explicit.

Source

Thrown at mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/mapper/BaseMapper.java:341

    default T selectOne(Wrapper<T> queryWrapper) {
        return this.selectOne(queryWrapper, true);
    }

    /**
     * 根据 entity 条件,查询一条记录,现在会根据{@code throwEx}参数判断是否抛出异常,如果为false就直接返回一条数据
     * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     * @param throwEx      boolean 参数,为true如果存在多个结果直接抛出异常
     */
    default T selectOne(Wrapper<T> queryWrapper, boolean throwEx) {
        List<T> list = this.selectList(queryWrapper);
        int size = list.size();
        if (size == 1) {
            return list.get(0);
        } else if (size > 1) {
            if (throwEx) {
                throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + size);
            }
            return list.get(0);
        }
        return null;
    }

    /**
     * 根据 Wrapper 条件,游标方式查询全部记录
     * <p>注意!需要在事务中 @Transactional 注解方法,或者手动管理 sqlSession 避免会话中断</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Cursor<T> selectWithCursor(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,判断是否存在记录
     *
     * @param queryWrapper 实体对象封装操作类

View on GitHub (pinned to bf67d90747)

Solutions

  1. Decide intent: if duplicates are legitimate, pass throwEx=false or add qw.last("LIMIT 1")
  2. If one row is the contract, fix the data (add a unique constraint) and the query predicate so it truly selects one row
  3. Inspect the rows returned by the same wrapper via selectList to understand the duplication

Example fix

// before
User u = userMapper.selectOne(qw, true); // throws if duplicates exist
// after (dupes acceptable, want first)
User u = userMapper.selectOne(qw.last("LIMIT 1"));
// after (dupes are a bug)
// dedupe data + ALTER TABLE user ADD UNIQUE KEY uk_email (email);
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check when duplicates are possible
Long cnt = userMapper.selectCount(qw);
if (cnt != null && cnt > 1) {
    // decide: first row, or fail with context
    log.warn("selectOne matched {} rows", cnt);
}
User u = cnt != null && cnt == 1 ? userMapper.selectOne(qw) : userMapper.selectOne(qw.last("LIMIT 1"));

Try / catch

try { return userMapper.selectOne(qw, true); } catch (TooManyResultsException e) { log.warn("Duplicate rows for filter, returning first"); return userMapper.selectOne(qw.last("LIMIT 1")); } // only when duplicates are acceptable

Prevention

When it happens

Trigger: Calling selectOne(queryWrapper, true) where the query matches 2+ rows — e.g. a unique-ish filter that is not actually unique, or missing a limit while duplicate data exists.

Common situations: Assuming a field is unique when the table contains duplicates; race between check and insert creating duplicates; upgrading code from selectOne(wrapper) (which also throws by default in MyBatis) and hitting dirty data.

Related errors


AI-assisted analysis of baomidou/mybatis-plus@bf67d90747 (2026-08-14). Data as JSON: /api/errors/5d89810be6b941d1. Report an issue: GitHub.