mybatis/mybatis-3 · error · SQLException
Statement returned {} results where exactly one (1) was expe
Error message
Statement returned {} results where exactly one (1) was expected. What it means
SqlRunner.selectOne(String sql, Object... args) executes the query via selectAll() and throws SQLException unless exactly one row comes back. Both zero rows and multiple rows are errors: it is a strict 'expect exactly one' API, unlike selectOne in the mapper layer which returns null for zero rows.
Source
Thrown at src/main/java/org/apache/ibatis/jdbc/SqlRunner.java:72
}
/**
* Executes a SELECT statement that returns one row.
*
* @param sql
* The SQL
* @param args
* The arguments to be set on the statement.
*
* @return The row expected.
*
* @throws SQLException
* If less or more than one row is returned
*/
public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {
List<Map<String, Object>> results = selectAll(sql, args);
if (results.size() != 1) {
throw new SQLException("Statement returned " + results.size() + " results where exactly one (1) was expected.");
}
return results.get(0);
}
/**
* Executes a SELECT statement that returns multiple rows.
*
* @param sql
* The SQL
* @param args
* The arguments to be set on the statement.
*
* @return The list of rows expected.
*
* @throws SQLException
* If statement preparation or execution fails
*/
public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {View on GitHub (pinned to 008069adb1)
Solutions
- Use selectAll(sql, args) and inspect the list size when 0..N rows are acceptable.
- Tighten the WHERE clause / add LIMIT 1 semantics or query by the true primary key so exactly one row matches.
- If zero rows is a valid outcome, branch on selectAll().isEmpty() before treating the row as present.
- Verify the row was actually committed before querying from another connection.
Example fix
// before
Map<String,Object> row = sqlRunner.selectOne("select * from users where name = ?", name);
// after
List<Map<String,Object>> rows = sqlRunner.selectAll("select * from users where name = ?", name);
if (rows.size() != 1) {
throw new IllegalStateException("Expected 1 user named " + name + " but got " + rows.size());
}
Map<String,Object> row = rows.get(0); Defensive patterns
Strategy: validation
Validate before calling
// When 0..N rows are possible, check before selecting one
List<Map<String, Object>> rows = runner.selectAll("select * from users where id = ?", id);
if (rows.size() != 1) {
throw new NoSuchElementException("user " + id + " not unique: " + rows.size() + " rows");
}
Map<String, Object> row = rows.get(0); Prevention
- Prefer selectAll + explicit size check over selectOne whenever absence is a normal outcome.
- Query by primary key or add uniqueness constraints so 'exactly one' is guaranteed by schema.
- In tests, assert row counts with selectAll so failures carry the actual count in the message.
When it happens
Trigger: Calling selectOne with a query that matches 0 rows (lookup by absent id) or >1 rows (non-unique filter, bad WHERE clause). Typical in test utilities that fetch generated keys or verify a single row: new SqlRunner(conn).selectOne("select * from users where id=?", id).
Common situations: Verification queries in tests after inserts; querying by a natural key that turns out not unique; a WHERE clause accidentally omitted or too broad; data not yet committed by another transaction so zero rows are visible.
Related errors
- Constructor auto-mapping of ''{0}'' failed. The constructor
- Could not commit transaction. Cause: {}
- Line missing end-of-line terminator ({}) => {}
- SqlRunner requires an instance of Null to represent typed nu
- SqlRunner could not find a TypeHandler instance for {}
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/07d1e6c393efa916.
Report an issue: GitHub.