jakartaee/data

[clarification]: Clarification and Restriction on Non-Built Repositories and Methods by Query

Closed this issue ยท 9 comments

Specification

https://github.com/jakartaee/data/blob/main/spec/src/main/asciidoc/method-query.asciidoc#query-by-method-name

I need clarification on ...

There seems to be an inconsistency or lack of clarity regarding the use of non-built repositories, especially when using query methods other than findBy. While the findBy action clearly identifies the entity type through its return type, this is not the case for other actions such as existsBy or countBy.

Examples:

For the findBy action, it's clear which entity is being queried:

@Repository
interface People {
    List<Person> findByName(String name);
}

In this case, the return type List<Person> clearly indicates that the entity is Person.

However, for other actions such as existsBy or countBy, the entity type is not as clear:

@Repository
interface People {
    boolean existsByName(String name);

    Long countByName(String name);
}

Here, we do not have any perspective on the entity in this query.

Proposed Solutions:

  1. Restrict these query methods to built-in repositories only:

    Example:

    @Repository
    interface People extends DataRepository<Person, Long> {
        boolean existsByName(String name);
    
        Long countByName(String name);
    }

Additional information

No response

In the preamble of ch4 we say:

Repositories perform operations on entities. For repository methods that are annotated with @Insert, @Update, @Save, or @Delete, the entity type is determined from the method parameter type. For find and delete methods where the return type is an entity, array of entity, or parameterized type such as List<MyEntity> or Page<MyEntity>, the entity type is determined from the method return type. For count, exists, and other delete methods that do not return the entity or accept the entity as a parameter, the entity type cannot be determined from the method signature and a primary entity type must be defined for the repository.

I agree that this passage could be improved and made much more explicit (and, in particular delete methods don't return entity types so that should be fixed). Also, this information should be added to the QbMN spec.

However, it is actually well-defined, I think: For count and exists queries, it's the primary entity type.

Note that there is additional language in section 5.5.1 for JDQL, and in 4.3 for parameter-based automatic query methods, and in the Javadoc for the @Find, @Delete, and @Query annotations.

If we agree, this test at TCK does not make sense:

assertEquals(4L, customRepo.countByIdIn(Set.of(2L, 15L, 37L, -5L, 60L)));
assertEquals(true, customRepo.existsByIdIn(Set.of(17L, 14L, -1L)));
assertEquals(false, customRepo.existsByIdIn(Set.of(-10L, -12L, -14L)));

I mean, related to those two methods here:

long countByIdIn(Set<Long> ids);
boolean existsByIdIn(Set<Long> ids);

How should I know the entities that those methods are talking about?

@otaviojava the entity type for those methods is the primary entity type of the repository, as defined in the preamble of ch4. (Last regular paragraph of page 19, just before the admonition.)

@otaviojava the entity type for those methods is the primary entity type of the repository, as defined in the preamble of ch4. (Last regular paragraph of page 19, just before the admonition.)

I got that, but once it is a custom repository it does not provide this.

A repository which extends a built-in supertype usually acts as a home for operations acting on a single entity type called the primary entity type of the repository. The primary entity type is determined by the argument to the first generic type variable of the generic supertype.

This code from TCK does not provide the primary entity of the repository.

@Repository
public interface CustomRepository {

    long countByIdIn(Set<Long> ids);

    boolean existsByIdIn(Set<Long> ids);
}

Sorry, maybe I am losing some details.

Did you read the paragraph I referenced?

That's not the whole code for CustomRepository. It also has two lifecycle methods, from which the primary entity type should be inferred.

That's not the whole code for CustomRepository. It also has two lifecycle methods, from which the primary entity type should be inferred.

I did not mention those methods because those are fine based on what we have in the documentation, as you said here:

#755 (comment)

Thus, those methods have the primary entity:

@Insert
void add(List<NaturalNumber> list);

@Delete
void remove(List<NaturalNumber> list);

But I am not talking about those, but the findBy, existsBy or countBy in the same context.

Otavio, I think you have the concept backwards. Life cycle methods, such as with @Insert and @Delete can choose any entity types they like, independent of the primary entity type, but also have the specified effect that if they all choose the same entity type and the repository does not otherwise declare a primary entity type, then it becomes the primary entity type of the repository. countByIdIn and existsByIdIn need to use the entity type of NaturalNumber here because it is the repository's primary entity type, as established by @Insert void add(List<NaturalNumber) and @Delete void remove(List<NaturalNumber>. This is a good test of how the primary entity type is selected. Note that the JavaDoc of the CustomRepository class of the test explicitly states that is why it is written this way:

  • Do not add methods or inheritance to this interface.
  • Its purpose is to test that without inheriting from a built-in repository,
  • the lifecycle methods with the same entity class are what identifies the
  • primary entity class to use for the count and exist methods.

It is what it is.
I will close this one.