Skip to content

Commit 4b80b0f

Browse files
authored
DataAccessUtils result accessors with Optional return value (#27735)
1 parent 793581e commit 4b80b0f

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed

Diff for: spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java

+87
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
package org.springframework.dao.support;
1818

1919
import java.util.Collection;
20+
import java.util.Iterator;
21+
import java.util.List;
22+
import java.util.Optional;
23+
import java.util.stream.Stream;
2024

2125
import org.springframework.dao.DataAccessException;
2226
import org.springframework.dao.EmptyResultDataAccessException;
@@ -56,6 +60,89 @@ public static <T> T singleResult(@Nullable Collection<T> results) throws Incorre
5660
return results.iterator().next();
5761
}
5862

63+
/**
64+
* Return a single result object from the given Stream.
65+
* <p>Returns {@code null} if 0 result objects found;
66+
* throws an exception if more than 1 element found.
67+
* @param results the result Stream (can be {@code null})
68+
* @return the single result object, or {@code null} if none
69+
* @throws IncorrectResultSizeDataAccessException if more than one
70+
* element has been found in the given Stream
71+
*/
72+
@Nullable
73+
public static <T> T singleResult(@Nullable Stream<T> results) throws IncorrectResultSizeDataAccessException {
74+
if (results == null) {
75+
return null;
76+
}
77+
try (results) {
78+
List<T> resultList = results.limit(2).toList();
79+
if (resultList.size() > 1) {
80+
throw new IncorrectResultSizeDataAccessException(1);
81+
}
82+
return CollectionUtils.isEmpty(resultList) ? null : resultList.get(0);
83+
}
84+
}
85+
86+
/**
87+
* Return a single result object from the given Iterator.
88+
* <p>Returns {@code null} if 0 result objects found;
89+
* throws an exception if more than 1 element found.
90+
* @param results the result Iterator (can be {@code null})
91+
* @return the single result object, or {@code null} if none
92+
* @throws IncorrectResultSizeDataAccessException if more than one
93+
* element has been found in the given Iterator
94+
*/
95+
@Nullable
96+
public static <T> T singleResult(@Nullable Iterator<T> results) throws IncorrectResultSizeDataAccessException {
97+
if (results == null) {
98+
return null;
99+
}
100+
T result = results.hasNext() ? results.next() : null;
101+
if (results.hasNext()) {
102+
throw new IncorrectResultSizeDataAccessException(1);
103+
}
104+
return result;
105+
}
106+
107+
/**
108+
* Return a single result object from the given Collection.
109+
* <p>Returns {@code Optional.empty()} if 0 result objects found;
110+
* throws an exception if more than 1 element found.
111+
* @param results the result Collection (can be {@code null})
112+
* @return the single optional result object, or {@code Optional.empty()} if none
113+
* @throws IncorrectResultSizeDataAccessException if more than one
114+
* element has been found in the given Collection
115+
*/
116+
public static <T> Optional<T> optionalResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
117+
return Optional.ofNullable(singleResult(results));
118+
}
119+
120+
/**
121+
* Return a single result object from the given Stream.
122+
* <p>Returns {@code Optional.empty()} if 0 result objects found;
123+
* throws an exception if more than 1 element found.
124+
* @param results the result Stream (can be {@code null})
125+
* @return the single optional result object, or {@code Optional.empty()} if none
126+
* @throws IncorrectResultSizeDataAccessException if more than one
127+
* element has been found in the given Stream
128+
*/
129+
public static <T> Optional<T> optionalResult(@Nullable Stream<T> results) throws IncorrectResultSizeDataAccessException {
130+
return Optional.ofNullable(singleResult(results));
131+
}
132+
133+
/**
134+
* Return a single result object from the given Iterator.
135+
* <p>Returns {@code Optional.empty()} if 0 result objects found;
136+
* throws an exception if more than 1 element found.
137+
* @param results the result Iterator (can be {@code null})
138+
* @return the single optional result object, or {@code Optional.empty()} if none
139+
* @throws IncorrectResultSizeDataAccessException if more than one
140+
* element has been found in the given Iterator
141+
*/
142+
public static <T> Optional<T> optionalResult(@Nullable Iterator<T> results) throws IncorrectResultSizeDataAccessException {
143+
return Optional.ofNullable(singleResult(results));
144+
}
145+
59146
/**
60147
* Return a single result object from the given Collection.
61148
* <p>Throws an exception if 0 or more than 1 element found.

Diff for: spring-tx/src/test/java/org/springframework/dao/support/DataAccessUtilsTests.java

+63
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.HashMap;
2424
import java.util.HashSet;
2525
import java.util.Map;
26+
import java.util.Optional;
2627
import java.util.function.Consumer;
2728

2829
import org.junit.jupiter.api.Test;
@@ -47,6 +48,13 @@ public void withEmptyCollection() {
4748

4849
assertThat(DataAccessUtils.uniqueResult(col)).isNull();
4950

51+
assertThat(DataAccessUtils.singleResult(col)).isNull();
52+
assertThat(DataAccessUtils.singleResult(col.stream())).isNull();
53+
assertThat(DataAccessUtils.singleResult(col.iterator())).isNull();
54+
assertThat(DataAccessUtils.optionalResult(col)).isEmpty();
55+
assertThat(DataAccessUtils.optionalResult(col.stream())).isEmpty();
56+
assertThat(DataAccessUtils.optionalResult(col.iterator())).isEmpty();
57+
5058
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
5159
DataAccessUtils.requiredUniqueResult(col))
5260
.satisfies(sizeRequirements(1, 0));
@@ -89,6 +97,30 @@ public void withTooLargeCollection() {
8997
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
9098
DataAccessUtils.longResult(col))
9199
.satisfies(sizeRequirements(1, 2));
100+
101+
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
102+
DataAccessUtils.singleResult(col))
103+
.satisfies(sizeRequirements(1, 2));
104+
105+
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
106+
DataAccessUtils.singleResult(col.stream()))
107+
.satisfies(sizeRequirements(1));
108+
109+
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
110+
DataAccessUtils.singleResult(col.iterator()))
111+
.satisfies(sizeRequirements(1));
112+
113+
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
114+
DataAccessUtils.optionalResult(col))
115+
.satisfies(sizeRequirements(1, 2));
116+
117+
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
118+
DataAccessUtils.optionalResult(col.stream()))
119+
.satisfies(sizeRequirements(1));
120+
121+
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
122+
DataAccessUtils.optionalResult(col.iterator()))
123+
.satisfies(sizeRequirements(1));
92124
}
93125

94126
@Test
@@ -102,6 +134,12 @@ public void withInteger() {
102134
assertThat(DataAccessUtils.objectResult(col, String.class)).isEqualTo("5");
103135
assertThat(DataAccessUtils.intResult(col)).isEqualTo(5);
104136
assertThat(DataAccessUtils.longResult(col)).isEqualTo(5);
137+
assertThat(DataAccessUtils.singleResult(col)).isEqualTo(5);
138+
assertThat(DataAccessUtils.singleResult(col.stream())).isEqualTo(5);
139+
assertThat(DataAccessUtils.singleResult(col.iterator())).isEqualTo(5);
140+
assertThat(DataAccessUtils.optionalResult(col)).isEqualTo(Optional.of(5));
141+
assertThat(DataAccessUtils.optionalResult(col.stream())).isEqualTo(Optional.of(5));
142+
assertThat(DataAccessUtils.optionalResult(col.iterator())).isEqualTo(Optional.of(5));
105143
}
106144

107145
@Test
@@ -139,6 +177,12 @@ public void withLong() {
139177
assertThat(DataAccessUtils.objectResult(col, String.class)).isEqualTo("5");
140178
assertThat(DataAccessUtils.intResult(col)).isEqualTo(5);
141179
assertThat(DataAccessUtils.longResult(col)).isEqualTo(5);
180+
assertThat(DataAccessUtils.singleResult(col)).isEqualTo(Long.valueOf(5L));
181+
assertThat(DataAccessUtils.singleResult(col.stream())).isEqualTo(Long.valueOf(5L));
182+
assertThat(DataAccessUtils.singleResult(col.iterator())).isEqualTo(Long.valueOf(5L));
183+
assertThat(DataAccessUtils.optionalResult(col)).isEqualTo(Optional.of(5L));
184+
assertThat(DataAccessUtils.optionalResult(col.stream())).isEqualTo(Optional.of(5L));
185+
assertThat(DataAccessUtils.optionalResult(col.iterator())).isEqualTo(Optional.of(5L));
142186
}
143187

144188
@Test
@@ -149,6 +193,12 @@ public void withString() {
149193
assertThat(DataAccessUtils.uniqueResult(col)).isEqualTo("test1");
150194
assertThat(DataAccessUtils.requiredUniqueResult(col)).isEqualTo("test1");
151195
assertThat(DataAccessUtils.objectResult(col, String.class)).isEqualTo("test1");
196+
assertThat(DataAccessUtils.singleResult(col)).isEqualTo("test1");
197+
assertThat(DataAccessUtils.singleResult(col.stream())).isEqualTo("test1");
198+
assertThat(DataAccessUtils.singleResult(col.iterator())).isEqualTo("test1");
199+
assertThat(DataAccessUtils.optionalResult(col)).isEqualTo(Optional.of("test1"));
200+
assertThat(DataAccessUtils.optionalResult(col.stream())).isEqualTo(Optional.of("test1"));
201+
assertThat(DataAccessUtils.optionalResult(col.iterator())).isEqualTo(Optional.of("test1"));
152202

153203
assertThatExceptionOfType(TypeMismatchDataAccessException.class).isThrownBy(() ->
154204
DataAccessUtils.intResult(col));
@@ -167,6 +217,12 @@ public void withDate() {
167217
assertThat(DataAccessUtils.requiredUniqueResult(col)).isEqualTo(date);
168218
assertThat(DataAccessUtils.objectResult(col, Date.class)).isEqualTo(date);
169219
assertThat(DataAccessUtils.objectResult(col, String.class)).isEqualTo(date.toString());
220+
assertThat(DataAccessUtils.singleResult(col)).isEqualTo(date);
221+
assertThat(DataAccessUtils.singleResult(col.stream())).isEqualTo(date);
222+
assertThat(DataAccessUtils.singleResult(col.iterator())).isEqualTo(date);
223+
assertThat(DataAccessUtils.optionalResult(col)).isEqualTo(Optional.of(date));
224+
assertThat(DataAccessUtils.optionalResult(col.stream())).isEqualTo(Optional.of(date));
225+
assertThat(DataAccessUtils.optionalResult(col.iterator())).isEqualTo(Optional.of(date));
170226

171227
assertThatExceptionOfType(TypeMismatchDataAccessException.class).isThrownBy(() ->
172228
DataAccessUtils.intResult(col));
@@ -199,6 +255,13 @@ private <E extends IncorrectResultSizeDataAccessException> Consumer<E> sizeRequi
199255
};
200256
}
201257

258+
private <E extends IncorrectResultSizeDataAccessException> Consumer<E> sizeRequirements(
259+
int expectedSize) {
260+
return ex -> {
261+
assertThat(ex.getExpectedSize()).as("expected size").isEqualTo(expectedSize);
262+
};
263+
}
264+
202265
public static class MapPersistenceExceptionTranslator implements PersistenceExceptionTranslator {
203266

204267
// in to out

0 commit comments

Comments
 (0)