Skip to content

Commit 8561918

Browse files
authored
Fix for runtime criteria containing multi level joins (#3305)
* Fix for runtime criteria containing multi level joins * Fix verification, mysql had unsorted elements * Sonar related changes
1 parent 3fd0dd3 commit 8561918

File tree

5 files changed

+96
-16
lines changed

5 files changed

+96
-16
lines changed

data-model/src/main/java/io/micronaut/data/model/jpa/criteria/PersistentEntityCommonAbstractCriteria.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public interface PersistentEntityCommonAbstractCriteria extends CommonAbstractCr
3333
* @param type The type
3434
* @param <U> The subquery type
3535
* @return A new subquery
36-
* @see 4.10
36+
* @since 4.10
3737
*/
3838
<U> PersistentEntitySubquery<U> subquery(ExpressionType<U> type);
3939

data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityJoinSupport.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@ private <X, Y> PersistentAssociationPath<X, Y> getJoin(String attributeName, io.
8484

8585
private <X, Y> PersistentAssociationPath<X, Y> getJoin(String attributeName, io.micronaut.data.annotation.Join.Type type, String alias) {
8686
PersistentProperty persistentProperty = getPersistentEntity().getPropertyByName(attributeName);
87+
88+
if (persistentProperty == null && attributeName.contains(".")) {
89+
int periodIndex = attributeName.indexOf(".");
90+
String owner = attributeName.substring(0, periodIndex);
91+
PersistentAssociationPath<E, ?> persistentAssociationPath;
92+
if (joins.containsKey(owner)) {
93+
persistentAssociationPath = joins.get(owner);
94+
} else {
95+
persistentAssociationPath = (PersistentAssociationPath<E, ?>) join(owner, type);
96+
}
97+
String remainingJoinPath = attributeName.substring(periodIndex + 1);
98+
return alias == null ? (PersistentAssociationPath<X, Y>) persistentAssociationPath.join(remainingJoinPath, type)
99+
: (PersistentAssociationPath<X, Y>) persistentAssociationPath.join(remainingJoinPath, type, alias);
100+
}
101+
87102
if (!(persistentProperty instanceof Association association)) {
88103
throw new IllegalStateException("Expected an association for attribute name: " + attributeName);
89104
}

data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
import jakarta.persistence.criteria.Selection;
6363

6464
import java.util.ArrayList;
65+
import java.util.Collection;
66+
import java.util.Comparator;
6567
import java.util.List;
6668
import java.util.Map;
6769
import java.util.Optional;
@@ -343,7 +345,7 @@ protected final <K> CriteriaQueryBuilder<K> getCriteriaQueryBuilder(MethodInvoca
343345
}
344346
}
345347
if (CollectionUtils.isNotEmpty(joinPaths)) {
346-
for (JoinPath joinPath : joinPaths) {
348+
for (JoinPath joinPath : sortJoinPaths(joinPaths)) {
347349
join(root, joinPath);
348350
}
349351
}
@@ -384,7 +386,7 @@ private <K> CriteriaQuery<Tuple> createSelectIdsCriteriaQuery(MethodInvocationCo
384386
}
385387
}
386388
if (CollectionUtils.isNotEmpty(joinPaths)) {
387-
for (JoinPath joinPath : joinPaths) {
389+
for (JoinPath joinPath : sortJoinPaths(joinPaths)) {
388390
join(root, joinPath);
389391
}
390392
}
@@ -588,4 +590,9 @@ protected enum Type {
588590
COUNT, FIND_ONE, FIND_PAGE, FIND_ALL, DELETE_ALL, UPDATE_ALL, EXISTS
589591
}
590592

593+
private List<JoinPath> sortJoinPaths(Collection<JoinPath> joinPaths) {
594+
List<JoinPath> sortedJoinPaths = new ArrayList<>(joinPaths);
595+
sortedJoinPaths.sort((o1, o2) -> Comparator.comparingInt(String::length).thenComparing(String::compareTo).compare(o1.getPath(), o2.getPath()));
596+
return sortedJoinPaths;
597+
}
591598
}

data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractRepositorySpec.groovy

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ import java.time.ZoneId
8282
import java.util.concurrent.CountDownLatch
8383
import java.util.concurrent.TimeUnit
8484

85+
import static io.micronaut.data.tck.repositories.AuthorRepository.Specifications.authorIdEquals
86+
import static io.micronaut.data.tck.repositories.AuthorRepository.Specifications.authorNameEquals
8587
import static io.micronaut.data.tck.repositories.BookSpecifications.hasChapter
8688
import static io.micronaut.data.tck.repositories.BookSpecifications.titleAndTotalPagesEquals
8789
import static io.micronaut.data.tck.repositories.BookSpecifications.titleAndTotalPagesEqualsUsingConjunction
@@ -1946,20 +1948,27 @@ abstract class AbstractRepositorySpec extends Specification {
19461948
author.getBooks()[0].preRemove == 0
19471949
author.getBooks()[0].postRemove == 0
19481950

1949-
def result1 = author.getBooks().find {book -> book.title == "Book1" }
1950-
result1.pages.size() == 1
1951-
result1.pages.find {page -> page.num = 1}
1951+
verifyAuthorBooksAndPages(author)
19521952

1953-
def result2 = author.getBooks().find {book -> book.title == "Book2" }
1954-
result2.pages.size() == 2
1955-
result2.pages.find {page -> page.num = 21}
1956-
result2.pages.find {page -> page.num = 22}
1953+
when:"Retrieve author using findOne predicate specification"
1954+
def foundAuthor = authorRepository.findOne(authorNameEquals(author.name)).orElse(null)
1955+
then:"All joined relations are loaded"
1956+
foundAuthor
1957+
foundAuthor.name == author.name
1958+
verifyAuthorBooksAndPages(foundAuthor)
19571959

1958-
def result3 = author.getBooks().find {book -> book.title == "Book3" }
1959-
result3.pages.size() == 3
1960-
result3.pages.find {page -> page.num = 31}
1961-
result3.pages.find {page -> page.num = 32}
1962-
result3.pages.find {page -> page.num = 33}
1960+
when:"Retrieve author using findOne query specification"
1961+
def otherFoundAuthor = authorRepository.findOne(authorIdEquals(author.id)).orElse(null)
1962+
then:"All joined relations are loaded"
1963+
otherFoundAuthor
1964+
otherFoundAuthor.name == author.name
1965+
verifyAuthorBooksAndPages(otherFoundAuthor)
1966+
1967+
when:"Retrieve author using findAll predicate specification"
1968+
def foundAuthors = authorRepository.findAll(authorNameEquals(author.name))
1969+
then:"All joined relations are loaded using findAll"
1970+
foundAuthors.size() == 1
1971+
verifyAuthorBooksAndPages(foundAuthors[0])
19631972

19641973
when:
19651974
def newBook = new Book()
@@ -1991,6 +2000,25 @@ abstract class AbstractRepositorySpec extends Specification {
19912000
// author.getBooks()[0].postRemove == 1
19922001
}
19932002

2003+
def verifyAuthorBooksAndPages(Author author) {
2004+
def book1 = author.getBooks().find { book -> book.title == "Book1" }
2005+
def book2 = author.getBooks().find { book -> book.title == "Book2" }
2006+
def book3 = author.getBooks().find { book -> book.title == "Book3" }
2007+
def book1pages = book1.pages.sort {it -> it.num}
2008+
def book2pages = book2.pages.sort {it -> it.num}
2009+
def book3pages = book3.pages.sort {it -> it.num}
2010+
author.books.size() == 3 &&
2011+
book1.pages.size() == 1 &&
2012+
book1pages[0].num == 1 &&
2013+
book2pages.size() == 2 &&
2014+
book2pages[0].num == 21 &&
2015+
book2pages[1].num == 22 &&
2016+
book3pages.size() == 3 &&
2017+
book3pages[0].num == 31 &&
2018+
book3pages[1].num == 32 &&
2019+
book3pages[2].num == 33
2020+
}
2021+
19942022
void "test one-to-one mappedBy"() {
19952023
when:"when a one-to-one mapped by is saved"
19962024
def face = faceRepository.save(new Face("Bob"))

data-tck/src/main/java/io/micronaut/data/tck/repositories/AuthorRepository.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import io.micronaut.data.model.Page;
2727
import io.micronaut.data.model.Pageable;
2828
import io.micronaut.data.repository.CrudRepository;
29+
import io.micronaut.data.repository.jpa.JpaSpecificationExecutor;
30+
import io.micronaut.data.repository.jpa.criteria.PredicateSpecification;
31+
import io.micronaut.data.repository.jpa.criteria.QuerySpecification;
2932
import io.micronaut.data.tck.entities.Author;
3033

3134
import io.micronaut.core.annotation.Nullable;
@@ -37,7 +40,7 @@
3740
import java.util.Optional;
3841
import java.util.stream.Stream;
3942

40-
public interface AuthorRepository extends CrudRepository<Author, Long> {
43+
public interface AuthorRepository extends CrudRepository<Author, Long>, JpaSpecificationExecutor<Author> {
4144

4245
@Join(value = "books", type = Join.Type.LEFT_FETCH)
4346
Author queryByName(String name);
@@ -48,6 +51,19 @@ public interface AuthorRepository extends CrudRepository<Author, Long> {
4851
@Join(value = "books.pages", alias = "bp", type = Join.Type.LEFT_FETCH)
4952
Optional<Author> findById(@NonNull @NotNull Long aLong);
5053

54+
@Override
55+
@Join(value = "books.pages", alias = "bp", type = Join.Type.LEFT_FETCH)
56+
@Join(value = "books", alias = "b", type = Join.Type.LEFT_FETCH)
57+
Optional<Author> findOne(PredicateSpecification<Author> specification);
58+
59+
@Override
60+
@Join(value = "books.pages", alias = "bp", type = Join.Type.LEFT_FETCH)
61+
List<Author> findAll(PredicateSpecification<Author> specification);
62+
63+
@Override
64+
@Join(value = "books.pages", type = Join.Type.LEFT_FETCH)
65+
Optional<Author> findOne(QuerySpecification<Author> specification);
66+
5167
Author findByName(String name);
5268

5369
Author findByBooksTitle(String title);
@@ -138,4 +154,18 @@ public interface AuthorRepository extends CrudRepository<Author, Long> {
138154
WHERE author_.name = :name
139155
""")
140156
List<Author> findAllByNameCustom(String name);
157+
158+
final class Specifications {
159+
160+
private Specifications() {
161+
}
162+
163+
static PredicateSpecification<Author> authorNameEquals(String name) {
164+
return (root, criteriaBuilder) -> criteriaBuilder.equal(root.get("name"), name);
165+
}
166+
167+
static QuerySpecification<Author> authorIdEquals(Long id) {
168+
return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("id"), id);
169+
}
170+
}
141171
}

0 commit comments

Comments
 (0)