Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for Criteria Feature in Jakarta Data[Vote] #460

Open
2 of 4 tasks
otaviojava opened this issue Feb 20, 2024 · 8 comments
Open
2 of 4 tasks

Proposal for Criteria Feature in Jakarta Data[Vote] #460

otaviojava opened this issue Feb 20, 2024 · 8 comments
Labels
enhancement New feature or request vote Something to vote on

Comments

@otaviojava
Copy link
Contributor

As a ...

  • Application user/user of the configuration itself
  • API user (application developer)
  • SPI user (container or runtime developer)
  • Specification implementer

I need to be able to ...

Expanding upon the capabilities of the Jakarta Data Query Language (#458), this proposal introduces the Criteria feature to enable programmatically constructing queries within the Jakarta Data framework. The Criteria API provides developers with an object-oriented approach to building query criteria, offering greater flexibility and control over data retrieval operations.

Which enables me to ...

The primary goal of this proposal is to initiate a discussion and a vote regarding the inclusion of the Criteria feature in the Jakarta Data project.

Key Features:

  1. Programmatic Query Construction: The Criteria API allows developers to programmatically build query criteria using a fluent and object-oriented interface, eliminating the need for raw queries.

  2. Filtering and Logical Conditions: Developers can apply various filtration rules and logical conditions to the criteria query object, enabling precise data retrieval based on specific criteria.

  3. Integration with Jakarta Data Query Language: The Criteria feature seamlessly integrates with the Jakarta Data Query Language, providing an additional method for constructing queries within the framework.

  4. Integration with Metadata: Criteria queries can be combined with metadata-driven queries, offering developers multiple options for constructing queries based on their requirements.

  5. Integration with Repository: Introducing a new interface, CriteriaRepository, that extends the existing PageableRepository, and enables seamless integration of criteria-based queries with repository pattern implementations.

Additional information

Example Usage:

// Combined with Metadata
List<Car> cars = template
    .select(Car.class)
    .where(_Car.city.eq("Rome").and(_Car.active.isTrue()))
    .orderBy(_Car.name.asc())
    .result();

// Combined with Repository
@Repository
public interface CardEntityRepository extends PageableRepository<Car, String>, CriteriaRepository<Car> {

    default Page<Car> findAll(String city, Pageable pageable) {
        Specification<Car> criteria = (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.equal(root.get("city"), city));
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
        return findAll(criteria, pageable);
    }
}

// Pure Implementation
List<Cat> cats = template.createCriteria(Cat.class)
    .add(Predicate.like("name", "Iz%"))
    .addOrder(Order.asc("age"))
    .result();

Benefits:

  1. Simplified Query Construction: The Criteria API simplifies query construction by providing a fluent and object-oriented interface, reducing the complexity of query writing.

  2. Enhanced Flexibility: Developers have greater flexibility in defining query criteria programmatically, allowing for dynamic and adaptable data retrieval operations.

  3. Integration with Metadata and Query Language: The Criteria feature seamlessly integrates with metadata-driven queries and the Jakarta Data Query Language, providing a comprehensive solution for data access within the Jakarta Data framework.

  4. Repository Integration: The introduction of CriteriaRepository enables developers to leverage criteria-based queries within the repository pattern, promoting code organization and reusability.

@otaviojava otaviojava added the enhancement New feature or request label Feb 20, 2024
@graemerocher
Copy link
Contributor

seems specific to JPA criteria

@njr-11
Copy link
Contributor

njr-11 commented Feb 20, 2024

A pattern like this is something to consider post v1.0 when more is added to the static metamodel to better enable it.

@gavinking
Copy link
Contributor

A pattern like this is something to consider post v1.0 when more is added to the static metamodel to better enable it.

Agreed. This isn't something I would bite off for 1.0. I think really it's tricky to get right. (Speaking from hard experience here.)

@otaviojava otaviojava added this to the Jakarta Data Future milestone Feb 21, 2024
@otaviojava otaviojava added the vote Something to vote on label Feb 22, 2024
@gavinking
Copy link
Contributor

gavinking commented Feb 25, 2024

By the way, since this issue is open, I just want to record for the future that @njr-11 has proposed something that I think is really nice, in some ways quite a lot nicer than a "full" criteria query API. The idea is to use the static metamodel to express "restrictions" which can be passed to a repository methods.

Conceptually, something like this:

repo.books(_Book.title.like("Jakarta Data%"), 
           _Book.publicationDate.between(pastDate, LocalDate.now()))

Or, if you need to combine this with ordering:

repo.books(where(_Book.title.like("Jakarta Data%"), 
                 _Book.publicationDate.between(pastDate, LocalDate.now())),
           by(_Book.title.ascIgnoreCase(),_Book.isbn.asc())))

[Don't focus on the details here, I know @njr-11 has thought about this longer than I have and surely has more-developed ideas about it.]

Of course, this is much less expressive than a real criteria API. But:

  • it's much, much easier to design
  • it's much, much easier to implement
  • in terms of actual practical usability it's absolute 🔥🔥🔥

Now, on the other hand, it's also completely compatible with a full criteria API, it's not really in competition at all. The objects returned by like() and between() could be criteria expressions.

Anyway, I would love to have this, and I think users would stampede for something like it.

@otaviojava
Copy link
Contributor Author

That is nice!
IIMHO: it would be faster than the query language.

@pipinet
Copy link

pipinet commented Oct 30, 2024

I wonder if it might be helpful to look at how go-jet approaches this?

@pipinet
Copy link

pipinet commented Nov 1, 2024

I have a very, very simple real-life scenario here.

select * from Post
where 
id in (select postId from PostTag where tagId in :tagIds)     #if tagIds is empty, return empty result
and status in (:statuses).   #if statuses is empty, ignore this filter

1、Is there any way to achieve repository more easily?
2、Is there any way to achieve PostQuery more semantic?

@Entity
@Table(name = "post")
public class PostEntity {
    @Id
    @UuidGenerator(style = TIME)
    private String id;
    private String status;
    private OffsetDateTime createdAt;

    ...getter/setter
}

@Entity
@Table(name = "post_tag")
public class PostTag {
    @Id
    @UuidGenerator(style = TIME)
    private String id;
    private String postId;
    private String tagId;

    ...getter/setter
}


public class PostQuery {
    private final Set<String> tagIds;
    private final Set<String> statuses;
      ...getter/setter
}


@Repository
public interface PostRepository extends CrudRepository<Post, String> {
    @Nonnull
    StatelessSession session();

    default Page<Post> pageBy(PostQuery query, PageRequest page) {
        if (query.getTagIds() == null || query.getTagIds().isEmpty()) {
            return new PageRecord<>(request, Collections.emptyList(), 0L);
        }
        CriteriaDefinition<Post> cd = new CriteriaDefinition<>(session().getFactory(), Post.class, 
            """
            select * from Post
            where id in (select postId from PostTag where tagId in :tagIds)
            """) {{
            var post = from(Post.class);
            if(query.getStatuses() != null && !query.getStatuses().isEmpty()){
                restrict(in(post.get(Post_.status), query.getStatuses()));
            }
            orderBy(desc(post.get(Post_.CREATED_AT)));
        }};
        int totalResults = session().createSelectionQuery(cd)
            .setParameter("tagIds", query.getTagIds())
            .getResultCount();
        List<Post> results = session().createSelectionQuery(cd)
            .setParameter("tagIds", query.getTagIds())
            .setFirstResult((int) (page.page() - 1) * page.size())
            .setMaxResults(page.size())
            .getResultList();
        return new PageRecord<>(page, results, totalResults);
    }
}

@gavinking
Copy link
Contributor

@pipinet I believe you're looking for #829

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request vote Something to vote on
Projects
None yet
Development

No branches or pull requests

5 participants