Skip to content

Commit 79f71ee

Browse files
committed
Support for self-joins
1 parent 28a4201 commit 79f71ee

File tree

14 files changed

+304
-160
lines changed

14 files changed

+304
-160
lines changed

doma-core/src/main/java/org/seasar/doma/jdbc/aggregate/AggregateCommand.java

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
*/
1616
package org.seasar.doma.jdbc.aggregate;
1717

18-
import java.util.LinkedHashMap;
18+
import java.util.HashMap;
19+
import java.util.LinkedHashSet;
1920
import java.util.List;
2021
import java.util.Map;
2122
import java.util.Objects;
23+
import java.util.Set;
2224
import java.util.function.BiFunction;
2325
import java.util.stream.Stream;
2426
import org.seasar.doma.internal.util.Combinations;
@@ -55,28 +57,36 @@ public AggregateCommand(
5557

5658
@Override
5759
public RESULT execute() {
58-
Map<LinkableEntityKey, Object> cache = new LinkedHashMap<>();
59-
Combinations<LinkableEntityKey> combinations = new Combinations<>();
60+
Set<EntityCacheKey> rootEntityKeys = new LinkedHashSet<>();
61+
Map<EntityCacheKey, Object> entityCache = new HashMap<>();
6062
SelectCommand<List<LinkableEntityPool>> command =
6163
new SelectCommand<>(
6264
query,
6365
new LinkableEntityPoolIterationHandler(
64-
entityType, aggregateStrategyType, query.isResultMappingEnsured(), cache));
66+
entityType,
67+
aggregateStrategyType,
68+
query.isResultMappingEnsured(),
69+
rootEntityKeys,
70+
entityCache));
6571
List<LinkableEntityPool> entityPools = command.execute();
72+
73+
Combinations<LinkableEntityKey> combinations = new Combinations<>();
6674
for (LinkableEntityPool entityPool : entityPools) {
67-
Map<String, LinkableEntity> associationCandidate = new LinkedHashMap<>();
68-
for (LinkableEntity entry : entityPool) {
69-
associationCandidate.put(entry.key().propertyPath(), entry);
75+
Map<PathKey, LinkableEntityPoolEntry> associationCandidate = new HashMap<>();
76+
for (LinkableEntityPoolEntry entry : entityPool) {
77+
associationCandidate.put(entry.entityKey().pathKey(), entry);
7078
}
71-
associate(cache, combinations, associationCandidate);
79+
associate(entityCache, combinations, associationCandidate);
7280
}
81+
7382
@SuppressWarnings("unchecked")
7483
Stream<ENTITY> stream =
7584
(Stream<ENTITY>)
76-
cache.entrySet().stream()
77-
.filter(e -> e.getKey().isRootEntityKey())
78-
.map(Map.Entry::getValue)
85+
rootEntityKeys.stream()
86+
.map(entityCache::get)
87+
.filter(Objects::nonNull)
7988
.filter(entityType.getEntityClass()::isInstance);
89+
8090
return streamReducer.reduce(stream);
8191
}
8292

@@ -85,30 +95,35 @@ public RESULT execute() {
8595
* of linkage rules and updates the cache with newly associated entities.
8696
*/
8797
private void associate(
88-
Map<LinkableEntityKey, Object> cache,
98+
Map<EntityCacheKey, Object> entityCache,
8999
Combinations<LinkableEntityKey> combinations,
90-
Map<String, LinkableEntity> associationCandidate) {
100+
Map<PathKey, LinkableEntityPoolEntry> associationCandidate) {
101+
91102
for (AssociationLinkerType<?, ?> linkerType :
92103
aggregateStrategyType.getAssociationLinkerTypes()) {
93-
LinkableEntity source = associationCandidate.get(linkerType.getAncestorPath());
94-
LinkableEntity target = associationCandidate.get(linkerType.getPropertyPath());
104+
LinkableEntityPoolEntry source = associationCandidate.get(linkerType.getSourcePathKey());
105+
LinkableEntityPoolEntry target = associationCandidate.get(linkerType.getTargetPathKey());
95106
if (source == null || target == null) {
96107
continue;
97108
}
98-
Pair<LinkableEntityKey, LinkableEntityKey> keyPair = new Pair<>(source.key(), target.key());
109+
110+
Pair<LinkableEntityKey, LinkableEntityKey> keyPair =
111+
new Pair<>(source.entityKey(), target.entityKey());
99112
if (combinations.contains(keyPair)) {
100113
continue;
101114
}
115+
combinations.add(keyPair);
116+
102117
@SuppressWarnings("unchecked")
103118
BiFunction<Object, Object, Object> linker =
104119
(BiFunction<Object, Object, Object>) linkerType.getLinker();
105-
Object newEntity = linker.apply(source.entity(), target.entity());
106-
if (newEntity != null) {
107-
cache.replace(source.key(), newEntity);
120+
Object entity = linker.apply(source.entity(), target.entity());
121+
if (entity != null) {
122+
EntityCacheKey cacheKey = EntityCacheKey.of(source.entityKey());
123+
entityCache.replace(cacheKey, entity);
108124
associationCandidate.replace(
109-
linkerType.getAncestorPath(), new LinkableEntity(source.key(), newEntity));
125+
source.pathKey(), new LinkableEntityPoolEntry(source.entityKey(), entity));
110126
}
111-
combinations.add(keyPair);
112127
}
113128
}
114129

doma-core/src/main/java/org/seasar/doma/jdbc/aggregate/AssociationLinkerType.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ public BiFunction<S, T, S> getLinker() {
8080
return linker;
8181
}
8282

83+
public PathKey getSourcePathKey() {
84+
return new PathKey(ancestorPath, source);
85+
}
86+
87+
public PathKey getTargetPathKey() {
88+
return new PathKey(propertyPath, target);
89+
}
90+
8391
public static <S, T> AssociationLinkerType<S, T> of(
8492
String ancestorPath,
8593
String propertyPath,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright Doma 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+
package org.seasar.doma.jdbc.aggregate;
17+
18+
import java.util.List;
19+
import org.seasar.doma.jdbc.entity.EntityType;
20+
21+
public record EntityCacheKey(EntityType<?> entityType, List<?> items) {
22+
public static EntityCacheKey of(LinkableEntityKey entityKey) {
23+
return new EntityCacheKey(entityKey.entityType(), entityKey.items());
24+
}
25+
}

doma-core/src/main/java/org/seasar/doma/jdbc/aggregate/LinkableEntity.java

Lines changed: 0 additions & 41 deletions
This file was deleted.

doma-core/src/main/java/org/seasar/doma/jdbc/aggregate/LinkableEntityKey.java

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,17 @@
1919
import java.util.Objects;
2020
import org.seasar.doma.jdbc.entity.EntityType;
2121

22-
/**
23-
* Represents a key for an entity that can be linked or associated within a domain model. This key
24-
* is characterized by its association identifier and a list of associated items.
25-
*
26-
* @param associationIdentifier the identifier for the association, must not be {@code null}
27-
* @param items the items involved in the association, must not be {@code null}
28-
*/
29-
public record LinkableEntityKey(AssociationIdentifier associationIdentifier, List<?> items) {
22+
public record LinkableEntityKey(PathKey pathKey, List<?> items) {
3023
public LinkableEntityKey {
31-
Objects.requireNonNull(associationIdentifier);
24+
Objects.requireNonNull(pathKey);
3225
Objects.requireNonNull(items);
3326
}
3427

3528
public String propertyPath() {
36-
return associationIdentifier.propertyPath();
29+
return pathKey.propertyPath();
3730
}
3831

3932
public EntityType<?> entityType() {
40-
return associationIdentifier.entityType();
41-
}
42-
43-
public boolean isRootEntityKey() {
44-
return associationIdentifier.propertyPath().isEmpty();
33+
return pathKey.entityType();
4534
}
4635
}

doma-core/src/main/java/org/seasar/doma/jdbc/aggregate/LinkableEntityPool.java

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,17 @@
2020
import java.util.List;
2121
import java.util.Objects;
2222

23-
/**
24-
* Represents a pool of {@link LinkableEntity} objects, providing functionality for adding entities
25-
* and iterating over the collection.
26-
*
27-
* <p>This class is designed to manage a collection of {@code LinkableEntity} instances and
28-
* guarantees that only non-null entities can be added, ensuring the integrity of the pool. It
29-
* implements {@link Iterable}, allowing the underlying collection to be used in enhanced for-loops
30-
* or with other iterable utilities.
31-
*/
32-
public class LinkableEntityPool implements Iterable<LinkableEntity> {
33-
private final List<LinkableEntity> entities = new ArrayList<>();
23+
public class LinkableEntityPool implements Iterable<LinkableEntityPoolEntry> {
24+
private final List<LinkableEntityPoolEntry> entities = new ArrayList<>();
3425

35-
public void add(LinkableEntity entity) {
36-
Objects.requireNonNull(entity);
37-
entities.add(entity);
26+
public void add(LinkableEntityPoolEntry entry) {
27+
Objects.requireNonNull(entry);
28+
entities.add(entry);
3829
}
3930

4031
@SuppressWarnings("NullableProblems")
4132
@Override
42-
public Iterator<LinkableEntity> iterator() {
33+
public Iterator<LinkableEntityPoolEntry> iterator() {
4334
return entities.iterator();
4435
}
4536
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright Doma 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+
package org.seasar.doma.jdbc.aggregate;
17+
18+
import java.util.Objects;
19+
20+
public record LinkableEntityPoolEntry(LinkableEntityKey entityKey, Object entity) {
21+
public LinkableEntityPoolEntry {
22+
Objects.requireNonNull(entityKey);
23+
Objects.requireNonNull(entity);
24+
}
25+
26+
public PathKey pathKey() {
27+
return entityKey.pathKey();
28+
}
29+
}

doma-core/src/main/java/org/seasar/doma/jdbc/aggregate/LinkableEntityPoolIterationHandler.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.List;
1919
import java.util.Map;
2020
import java.util.Objects;
21+
import java.util.Set;
2122
import org.seasar.doma.internal.jdbc.command.AbstractIterationHandler;
2223
import org.seasar.doma.internal.jdbc.command.ResultListCallback;
2324
import org.seasar.doma.jdbc.ObjectProvider;
@@ -34,23 +35,31 @@ public class LinkableEntityPoolIterationHandler
3435
private final EntityType<?> entityType;
3536
private final AggregateStrategyType aggregateStrategyType;
3637
private final boolean resultMappingEnsured;
37-
private final Map<LinkableEntityKey, Object> cache;
38+
private final Set<EntityCacheKey> rootEntityKeys;
39+
private final Map<EntityCacheKey, Object> entityCache;
3840

3941
public LinkableEntityPoolIterationHandler(
4042
EntityType<?> entityType,
4143
AggregateStrategyType aggregateStrategyType,
4244
boolean resultMappingEnsured,
43-
Map<LinkableEntityKey, Object> cache) {
45+
Set<EntityCacheKey> rootEntityKeys,
46+
Map<EntityCacheKey, Object> entityCache) {
4447
super(new ResultListCallback<>());
4548
this.entityType = Objects.requireNonNull(entityType);
4649
this.aggregateStrategyType = Objects.requireNonNull(aggregateStrategyType);
4750
this.resultMappingEnsured = resultMappingEnsured;
48-
this.cache = Objects.requireNonNull(cache);
51+
this.rootEntityKeys = Objects.requireNonNull(rootEntityKeys);
52+
this.entityCache = Objects.requireNonNull(entityCache);
4953
}
5054

5155
@Override
5256
protected ObjectProvider<LinkableEntityPool> createObjectProvider(SelectQuery query) {
5357
return new LinkableEntityPoolProvider(
54-
entityType, aggregateStrategyType, query, resultMappingEnsured, cache);
58+
entityType,
59+
aggregateStrategyType,
60+
query,
61+
resultMappingEnsured,
62+
rootEntityKeys,
63+
entityCache);
5564
}
5665
}

0 commit comments

Comments
 (0)