Skip to content

Commit 5f2a218

Browse files
committed
Loading trivial aggregates with Single Query Loading.
Since trivial aggregates consist only of a single table this doesn't make a real difference, but it demonstrates that the general infrastructure works. See #1446
1 parent f1f43af commit 5f2a218

File tree

22 files changed

+1935
-5
lines changed

22 files changed

+1935
-5
lines changed

spring-data-jdbc/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@
191191

192192
<!-- Testcontainers -->
193193

194+
<dependency>
195+
<groupId>com.github.jsqlparser</groupId>
196+
<artifactId>jsqlparser</artifactId>
197+
<version>4.5</version>
198+
</dependency>
199+
194200
<dependency>
195201
<groupId>org.testcontainers</groupId>
196202
<artifactId>mysql</artifactId>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.jdbc.core.convert;
18+
19+
import java.util.ArrayList;
20+
import java.util.Iterator;
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
import org.springframework.dao.IncorrectResultSizeDataAccessException;
25+
import org.springframework.data.jdbc.core.convert.sqlgeneration.AliasFactory;
26+
import org.springframework.data.jdbc.core.convert.sqlgeneration.SingleQuerySqlGenerator;
27+
import org.springframework.data.mapping.PersistentPropertyPath;
28+
import org.springframework.data.relational.core.dialect.Dialect;
29+
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
30+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
31+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
32+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
33+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
34+
import org.springframework.util.Assert;
35+
36+
public class AggregateReader<T> {
37+
38+
private final RelationalMappingContext mappingContext;
39+
private final RelationalPersistentEntity<T> aggregate;
40+
private final AliasFactory aliasFactory = new AliasFactory();
41+
private final SingleQuerySqlGenerator sqlGenerator;
42+
private final JdbcConverter converter;
43+
private final NamedParameterJdbcOperations jdbcTemplate;
44+
45+
AggregateReader(RelationalMappingContext mappingContext, Dialect dialect, JdbcConverter converter,
46+
NamedParameterJdbcOperations jdbcTemplate, RelationalPersistentEntity<T> aggregate) {
47+
48+
this.mappingContext = mappingContext;
49+
50+
this.aggregate = aggregate;
51+
this.converter = converter;
52+
this.jdbcTemplate = jdbcTemplate;
53+
54+
this.sqlGenerator = new SingleQuerySqlGenerator(mappingContext, dialect, aggregate);
55+
}
56+
57+
public List<T> findAll() {
58+
59+
String sql = sqlGenerator.findAll();
60+
61+
PathToColumnMapping pathToColumn = createPathToColumnMapping(aliasFactory);
62+
AggregateResultSetExtractor<T> extractor = new AggregateResultSetExtractor<>(mappingContext, aggregate, converter,
63+
pathToColumn);
64+
65+
Iterable<T> result = jdbcTemplate.query(sql, extractor);
66+
67+
return (List<T>) result;
68+
}
69+
70+
public T findById(Object id) {
71+
72+
PathToColumnMapping pathToColumn = createPathToColumnMapping(aliasFactory);
73+
AggregateResultSetExtractor<T> extractor = new AggregateResultSetExtractor<>(mappingContext, aggregate, converter,
74+
pathToColumn);
75+
76+
String sql = sqlGenerator.findById();
77+
78+
id = converter.writeValue(id, aggregate.getIdProperty().getTypeInformation());
79+
80+
Iterator<T> result = jdbcTemplate.query(sql, Map.of("id", id), extractor).iterator();
81+
82+
T returnValue = result.hasNext() ? result.next() : null;
83+
84+
if (result.hasNext()) {
85+
throw new IncorrectResultSizeDataAccessException(1);
86+
}
87+
88+
return returnValue;
89+
}
90+
91+
public Iterable<T> findAllById(Iterable<?> ids) {
92+
93+
PathToColumnMapping pathToColumn = createPathToColumnMapping(aliasFactory);
94+
AggregateResultSetExtractor<T> extractor = new AggregateResultSetExtractor<>(mappingContext, aggregate, converter,
95+
pathToColumn);
96+
97+
String sql = sqlGenerator.findAllById();
98+
99+
List<Object> convertedIds = new ArrayList<>();
100+
for (Object id : ids) {
101+
convertedIds.add(converter.writeValue(id, aggregate.getIdProperty().getTypeInformation()));
102+
}
103+
104+
return jdbcTemplate.query(sql, Map.of("ids", convertedIds), extractor);
105+
}
106+
107+
private PathToColumnMapping createPathToColumnMapping(AliasFactory aliasFactory) {
108+
return new PathToColumnMapping() {
109+
@Override
110+
public String column(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
111+
112+
PersistentPropertyPathExtension aliasLookUpKey = new PersistentPropertyPathExtension(mappingContext, propertyPath);
113+
114+
String alias = aliasFactory.getAlias(aliasLookUpKey);
115+
Assert.notNull(alias, () -> "alias for >" + aliasLookUpKey + "<must not be null");
116+
return alias;
117+
}
118+
119+
@Override
120+
public String keyColumn(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
121+
return aliasFactory.getKeyAlias(propertyPath);
122+
}
123+
};
124+
}
125+
126+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.jdbc.core.convert;
18+
19+
import java.util.HashMap;
20+
21+
import org.springframework.data.relational.core.dialect.Dialect;
22+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
23+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
24+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
25+
26+
public class AggregateReaderFactory {
27+
28+
private final RelationalMappingContext mappingContext;
29+
private final Dialect dialect;
30+
private final JdbcConverter converter;
31+
private final NamedParameterJdbcOperations jdbcTemplate;
32+
33+
private final HashMap<RelationalPersistentEntity<?>, AggregateReader> cash = new HashMap<>();
34+
35+
public AggregateReaderFactory(RelationalMappingContext mappingContext, Dialect dialect, JdbcConverter converter,
36+
NamedParameterJdbcOperations jdbcTemplate) {
37+
this.mappingContext = mappingContext;
38+
this.dialect = dialect;
39+
this.converter = converter;
40+
this.jdbcTemplate = jdbcTemplate;
41+
}
42+
43+
<T> AggregateReader<T> createAggregateReaderFor(RelationalPersistentEntity<T> entity) {
44+
return cash.computeIfAbsent(entity, e -> new AggregateReader<>(mappingContext, dialect, converter, jdbcTemplate, e));
45+
}
46+
}

0 commit comments

Comments
 (0)