Skip to content

Commit d85d844

Browse files
authored
#3695 Behaviour change: findSingleAttribute() to throw when multiple … (#3697)
* #3695 Behaviour change: findSingleAttribute() to throw when multiple rows returned by query On the plus, this makes the behaviour consistent with all the other findOne() methods (ORM, DTO, SqlQuery) and this very much feels like buggy behaviour [to just get the first result and ignore any subsequent results in the ResultSet] On the negative of merging this bug fix, any application code relying on the existing behaviour with this fix will break and throw a NonUniqueResultException at RUNTIME (not ideal). However, for these cases where the application now throws an exception, people might not be aware that they were relying on this behaviour and this exception could be useful to highlight that (a potential non-deterministic query result was being used and a potential source of bugs was being). * Change SqlQuery + Mapper + findOne() also to throw NonUniqueResultException
1 parent baf71cb commit d85d844

File tree

2 files changed

+38
-1
lines changed

2 files changed

+38
-1
lines changed

ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.ebeaninternal.server.core.RowReader;
1515
import io.ebeaninternal.server.persist.Binder;
1616

17+
import jakarta.persistence.NonUniqueResultException;
1718
import jakarta.persistence.PersistenceException;
1819

1920
import java.sql.SQLException;
@@ -115,9 +116,14 @@ public <T> T findOne(RelationalQueryRequest request, RowMapper<T> mapper) {
115116
try {
116117
request.executeSql(binder, SpiQuery.Type.BEAN);
117118
T value = request.mapOne(mapper);
119+
if (request.next()) {
120+
throw new NonUniqueResultException("Got more than 1 result for findOne");
121+
}
118122
request.logSummary();
119123
return value;
120124

125+
} catch (NonUniqueResultException e) {
126+
throw e;
121127
} catch (Exception e) {
122128
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
123129

@@ -160,13 +166,17 @@ public <T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls) {
160166
T value = null;
161167
if (dataReader.next()) {
162168
value = scalarType.read(dataReader);
169+
if (dataReader.next()) {
170+
throw new NonUniqueResultException("Got more than 1 result for findSingleAttribute");
171+
}
163172
}
164173
request.logSummary();
165174
return value;
166175

176+
} catch (NonUniqueResultException e) {
177+
throw e;
167178
} catch (Exception e) {
168179
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
169-
170180
} finally {
171181
request.close();
172182
}

ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.ebean.annotation.Platform;
77
import io.ebean.meta.MetaTimedMetric;
88
import io.ebean.test.LoggedSql;
9+
import jakarta.persistence.NonUniqueResultException;
910
import org.junit.jupiter.api.Test;
1011
import org.tests.model.basic.Order;
1112
import org.tests.model.basic.ResetBasicData;
@@ -21,6 +22,7 @@
2122
import java.util.concurrent.atomic.AtomicLong;
2223

2324
import static org.assertj.core.api.Assertions.assertThat;
25+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2426
import static org.junit.jupiter.api.Assertions.assertEquals;
2527

2628
class SqlQueryTests extends BaseTestCase {
@@ -85,6 +87,19 @@ void selectFunction() {
8587
assertThat(val2).isEqualTo(11);
8688
}
8789

90+
@Test
91+
void findSingleAttribute_whenMultipleRows_expect_NonUniqueResultException() {
92+
ResetBasicData.reset();
93+
String sql = "select id from o_order order by id desc";
94+
95+
assertThatThrownBy(() -> {
96+
DB.sqlQuery(sql)
97+
.mapToScalar(Long.class)
98+
.findOne();
99+
}).isInstanceOf(NonUniqueResultException.class)
100+
.hasMessageContaining("Got more than 1 result for findSingleAttribute");
101+
}
102+
88103
@Test
89104
void findSingleAttributeList_decimal() {
90105
ResetBasicData.reset();
@@ -373,6 +388,18 @@ void findOne_mapper() {
373388
assertThat(rob.name).isEqualTo("Rob");
374389
}
375390

391+
@Test
392+
void findOne_mapper_when_notUnique() {
393+
ResetBasicData.reset();
394+
395+
String sql = "select id, name, status from o_customer order by name desc";
396+
assertThatThrownBy(() -> DB.sqlQuery(sql)
397+
.mapTo(CUST_MAPPER)
398+
.findOne())
399+
.isInstanceOf(NonUniqueResultException.class)
400+
.hasMessageContaining("Got more than 1 result for findOne");
401+
}
402+
376403
@Test
377404
void findList_mapper() {
378405
ResetBasicData.reset();

0 commit comments

Comments
 (0)