mybatis/mybatis-3 · error · ExecutorException

SelectKey returned more than one value.

Error message

SelectKey returned more than one value.

What it means

SelectKeyGenerator requires the <selectKey> statement to produce exactly one row, because a single key value is assigned to one keyProperty set. If the query returns two or more rows, MyBatis cannot decide which value is the key and throws this ExecutorException.

Source

Thrown at src/main/java/org/apache/ibatis/executor/keygen/SelectKeyGenerator.java:72

      processGeneratedKeys(executor, ms, parameter);
    }
  }

  private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
    try {
      if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
        String[] keyProperties = keyStatement.getKeyProperties();
        final Configuration configuration = ms.getConfiguration();
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        // Do not close keyExecutor.
        // The transaction will be closed by parent executor.
        Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
        List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
        if (values.isEmpty()) {
          throw new ExecutorException("SelectKey returned no data.");
        }
        if (values.size() > 1) {
          throw new ExecutorException("SelectKey returned more than one value.");
        } else {
          MetaObject metaResult = configuration.newMetaObject(values.get(0));
          if (keyProperties.length == 1) {
            if (metaResult.hasGetter(keyProperties[0])) {
              setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
            } else {
              // no getter for the property - maybe just a single value object
              // so try that
              setValue(metaParam, keyProperties[0], values.get(0));
            }
          } else {
            handleMultipleProperties(keyProperties, metaParam, metaResult);
          }
        }
      }
    } catch (ExecutorException e) {
      throw e;
    } catch (Exception e) {

View on GitHub (pinned to 008069adb1)

Solutions

  1. Constrain the selectKey SQL to one row: use aggregates (MAX), LIMIT 1 / FETCH FIRST 1 ROWS ONLY, or a proper sequence
  2. Add uniqueness guarantees (unique index) for lookup-based key strategies
  3. Test the selectKey SQL in a DB client against production-like data

Example fix

<!-- before -->
<selectKey keyProperty="id" resultType="long" order="BEFORE">
  SELECT id FROM codes WHERE name = #{name}
</selectKey>

<!-- after -->
<selectKey keyProperty="id" resultType="long" order="BEFORE">
  SELECT id FROM codes WHERE name = #{name} LIMIT 1
</selectKey>
Defensive patterns

Strategy: validation

Validate before calling

// For lookup-based selectKeys, assert uniqueness first
Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM codes WHERE name=?", Integer.class, name);
if (count != null && count > 1) throw new IllegalStateException("selectKey lookup not unique");

Try / catch

try { mapper.insert(entity); } catch (PersistenceException e) { if (String.valueOf(e.getMessage()).contains("more than one value")) { /* constrain the selectKey query */ } throw e; }

Prevention

When it happens

Trigger: A <selectKey> like SELECT id FROM t WHERE name = #{name} matching duplicates; a sequence select accidentally returning multiple rows (bad FROM clause, join fan-out); MAX(id) queries without aggregation over a filtered join.

Common situations: Non-unique lookup columns inside selectKey; forgetting FROM DUAL on Oracle scalar selects; test data introducing duplicates.

Related errors


AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14). Data as JSON: /api/errors/fc1eb9efe31f831e. Report an issue: GitHub.