Skip to content

Commit

Permalink
Fix for runtime criteria containing multi level joins (#3305)
Browse files Browse the repository at this point in the history
* Fix for runtime criteria containing multi level joins

* Fix verification, mysql had unsorted elements

* Sonar related changes
  • Loading branch information
radovanradic authored Jan 29, 2025
1 parent 3fd0dd3 commit 8561918
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public interface PersistentEntityCommonAbstractCriteria extends CommonAbstractCr
* @param type The type
* @param <U> The subquery type
* @return A new subquery
* @see 4.10
* @since 4.10
*/
<U> PersistentEntitySubquery<U> subquery(ExpressionType<U> type);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ private <X, Y> PersistentAssociationPath<X, Y> getJoin(String attributeName, io.

private <X, Y> PersistentAssociationPath<X, Y> getJoin(String attributeName, io.micronaut.data.annotation.Join.Type type, String alias) {
PersistentProperty persistentProperty = getPersistentEntity().getPropertyByName(attributeName);

if (persistentProperty == null && attributeName.contains(".")) {
int periodIndex = attributeName.indexOf(".");
String owner = attributeName.substring(0, periodIndex);
PersistentAssociationPath<E, ?> persistentAssociationPath;
if (joins.containsKey(owner)) {
persistentAssociationPath = joins.get(owner);
} else {
persistentAssociationPath = (PersistentAssociationPath<E, ?>) join(owner, type);
}
String remainingJoinPath = attributeName.substring(periodIndex + 1);
return alias == null ? (PersistentAssociationPath<X, Y>) persistentAssociationPath.join(remainingJoinPath, type)
: (PersistentAssociationPath<X, Y>) persistentAssociationPath.join(remainingJoinPath, type, alias);
}

if (!(persistentProperty instanceof Association association)) {
throw new IllegalStateException("Expected an association for attribute name: " + attributeName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
import jakarta.persistence.criteria.Selection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -343,7 +345,7 @@ protected final <K> CriteriaQueryBuilder<K> getCriteriaQueryBuilder(MethodInvoca
}
}
if (CollectionUtils.isNotEmpty(joinPaths)) {
for (JoinPath joinPath : joinPaths) {
for (JoinPath joinPath : sortJoinPaths(joinPaths)) {
join(root, joinPath);
}
}
Expand Down Expand Up @@ -384,7 +386,7 @@ private <K> CriteriaQuery<Tuple> createSelectIdsCriteriaQuery(MethodInvocationCo
}
}
if (CollectionUtils.isNotEmpty(joinPaths)) {
for (JoinPath joinPath : joinPaths) {
for (JoinPath joinPath : sortJoinPaths(joinPaths)) {
join(root, joinPath);
}
}
Expand Down Expand Up @@ -588,4 +590,9 @@ protected enum Type {
COUNT, FIND_ONE, FIND_PAGE, FIND_ALL, DELETE_ALL, UPDATE_ALL, EXISTS
}

private List<JoinPath> sortJoinPaths(Collection<JoinPath> joinPaths) {
List<JoinPath> sortedJoinPaths = new ArrayList<>(joinPaths);
sortedJoinPaths.sort((o1, o2) -> Comparator.comparingInt(String::length).thenComparing(String::compareTo).compare(o1.getPath(), o2.getPath()));
return sortedJoinPaths;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ import java.time.ZoneId
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

import static io.micronaut.data.tck.repositories.AuthorRepository.Specifications.authorIdEquals
import static io.micronaut.data.tck.repositories.AuthorRepository.Specifications.authorNameEquals
import static io.micronaut.data.tck.repositories.BookSpecifications.hasChapter
import static io.micronaut.data.tck.repositories.BookSpecifications.titleAndTotalPagesEquals
import static io.micronaut.data.tck.repositories.BookSpecifications.titleAndTotalPagesEqualsUsingConjunction
Expand Down Expand Up @@ -1946,20 +1948,27 @@ abstract class AbstractRepositorySpec extends Specification {
author.getBooks()[0].preRemove == 0
author.getBooks()[0].postRemove == 0

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

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

def result3 = author.getBooks().find {book -> book.title == "Book3" }
result3.pages.size() == 3
result3.pages.find {page -> page.num = 31}
result3.pages.find {page -> page.num = 32}
result3.pages.find {page -> page.num = 33}
when:"Retrieve author using findOne query specification"
def otherFoundAuthor = authorRepository.findOne(authorIdEquals(author.id)).orElse(null)
then:"All joined relations are loaded"
otherFoundAuthor
otherFoundAuthor.name == author.name
verifyAuthorBooksAndPages(otherFoundAuthor)

when:"Retrieve author using findAll predicate specification"
def foundAuthors = authorRepository.findAll(authorNameEquals(author.name))
then:"All joined relations are loaded using findAll"
foundAuthors.size() == 1
verifyAuthorBooksAndPages(foundAuthors[0])

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

def verifyAuthorBooksAndPages(Author author) {
def book1 = author.getBooks().find { book -> book.title == "Book1" }
def book2 = author.getBooks().find { book -> book.title == "Book2" }
def book3 = author.getBooks().find { book -> book.title == "Book3" }
def book1pages = book1.pages.sort {it -> it.num}
def book2pages = book2.pages.sort {it -> it.num}
def book3pages = book3.pages.sort {it -> it.num}
author.books.size() == 3 &&
book1.pages.size() == 1 &&
book1pages[0].num == 1 &&
book2pages.size() == 2 &&
book2pages[0].num == 21 &&
book2pages[1].num == 22 &&
book3pages.size() == 3 &&
book3pages[0].num == 31 &&
book3pages[1].num == 32 &&
book3pages[2].num == 33
}

void "test one-to-one mappedBy"() {
when:"when a one-to-one mapped by is saved"
def face = faceRepository.save(new Face("Bob"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.repository.CrudRepository;
import io.micronaut.data.repository.jpa.JpaSpecificationExecutor;
import io.micronaut.data.repository.jpa.criteria.PredicateSpecification;
import io.micronaut.data.repository.jpa.criteria.QuerySpecification;
import io.micronaut.data.tck.entities.Author;

import io.micronaut.core.annotation.Nullable;
Expand All @@ -37,7 +40,7 @@
import java.util.Optional;
import java.util.stream.Stream;

public interface AuthorRepository extends CrudRepository<Author, Long> {
public interface AuthorRepository extends CrudRepository<Author, Long>, JpaSpecificationExecutor<Author> {

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

@Override
@Join(value = "books.pages", alias = "bp", type = Join.Type.LEFT_FETCH)
@Join(value = "books", alias = "b", type = Join.Type.LEFT_FETCH)
Optional<Author> findOne(PredicateSpecification<Author> specification);

@Override
@Join(value = "books.pages", alias = "bp", type = Join.Type.LEFT_FETCH)
List<Author> findAll(PredicateSpecification<Author> specification);

@Override
@Join(value = "books.pages", type = Join.Type.LEFT_FETCH)
Optional<Author> findOne(QuerySpecification<Author> specification);

Author findByName(String name);

Author findByBooksTitle(String title);
Expand Down Expand Up @@ -138,4 +154,18 @@ public interface AuthorRepository extends CrudRepository<Author, Long> {
WHERE author_.name = :name
""")
List<Author> findAllByNameCustom(String name);

final class Specifications {

private Specifications() {
}

static PredicateSpecification<Author> authorNameEquals(String name) {
return (root, criteriaBuilder) -> criteriaBuilder.equal(root.get("name"), name);
}

static QuerySpecification<Author> authorIdEquals(Long id) {
return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("id"), id);
}
}
}

0 comments on commit 8561918

Please sign in to comment.