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

A way to shorten/alias method name derived queries. [DATACMNS-1213] #1652

Closed
spring-projects-issues opened this issue Nov 15, 2017 · 11 comments
Assignees
Labels
status: declined A suggestion or change that we don't feel we should currently apply

Comments

@spring-projects-issues
Copy link

justin mccune opened DATACMNS-1213 and commented

Many think that it's great to use the name of the method to infer the SQL for a query on a repository. However, often the use of descriptive field names and one or two concerns (an AND operation with a SORT ordering) leads to incredibly long (or "ugly" names). For example one properly named long field and a simple boolean & sort order lead to the repository method name being 96 characters long. This destroys many style guides line-lengths when used.

While it is an alternative to use @Query make a shorter method name and write the actual query by hand, or to use java's "default" on interface to alias a shorter form that uses the full / ugly query. They both are not as elegant as the simplicity of the named query where preferably I alias it.

Often the alias / shorter name is just as meaningful and clear in meaning even if omitting implicit/assumed context-- see examples below.

This has come up several times with coworkers (in multiple companies) & a quick search has shown that others have the same concern.

https://stackoverflow.com/questions/30019408/how-to-shorten-names-of-query-methods-in-spring-data-jpa-repositories

When I read the above, I thought perhaps I'd found the solution-- but it was a suggestion that I couldn't find in the Spring Data feature request list, so I thought I'd add it.

So what I'd like would be something like the below-- where the annotation name is used instead of the actual method name to build the query.

@QueryByMethodName("findOneByDeletedIsFalseAndEmail")
User findOneByEmail(String email);

@QueryByMethodName("findOneByReallyLongFieldNameAndActiveStatusIsTrue")
Entity findOneByRLFN(ReallyLongFieldNameType value)

This improves both simplicity and readability. And I'm not particular about the annotation name.


16 votes, 8 watchers

@gbraleigh
Copy link

Yes, please! This seems like a relatively simple feature that would be very valuable, improving our ability to read and easily understand repository generated method names.

@kadirtn
Copy link

kadirtn commented Jan 26, 2022

Method name gets too crowd just for a simple query, proposed approach seems convenient.

@laguiar
Copy link

laguiar commented Jun 15, 2022

any news on this proposal?

@grzechup
Copy link

I would love to see this feature. For example, I have the following method query in my repository.
Optional<CallManagerMonitoredUser> findByServiceProviderBwIdAndGroupBwIdAndUserBwIdAndMonitoredContactContactNameAndMonitoredContactContactNumber(String serviceProviderId, String groupId, String userBwId, String contactName, String contactNumber);
That will add much readability to my code.

@bektun
Copy link

bektun commented Dec 6, 2022

if you extend your repository with JpaRepository then you can use a default method which calls your origin repository method:
public interface UserRepository extens JpaRepositry<String, User> {
List<User> findUserByCreatedDateAndHairColorAndFavoriteTeamAndBLaBla(param1, param2...);

default findBlaBlaUser(param1, param2...) {

return findUserByCreatedDateAndHairColorAndFavoriteTeamAndBLaBla(param1, param2...);
}
}

in your service you can now call myUserRepo.findBlaBlaUser(param1, param2...)

@grzechup
Copy link

grzechup commented Dec 6, 2022

if you extend your repository with JpaRepository then you can use a default method which calls your origin repository method: public interface UserRepository extens JpaRepositry<String, User> { List<User> findUserByCreatedDateAndHairColorAndFavoriteTeamAndBLaBla(param1, param2...);

default findBlaBlaUser(param1, param2...) {

return findUserByCreatedDateAndHairColorAndFavoriteTeamAndBLaBla(param1, param2...); } }

in your service you can now call myUserRepo.findBlaBlaUser(param1, param2...)

Yeah you can use this trick but still, it isn't pleasant when you enter your repository and see your long basic methods.

@mp911de
Copy link
Member

mp911de commented Feb 23, 2023

Spring Data is not in charge of defining yet another query language. We've built query derivation to allow users conveniently declare simple queries without having to dive into the store-specific query language to implement common use cases.
If a method becomes too long, then it no longer fits the intended usage pattern, and it falls out of this category.

Putting the potential String into an annotation moves the problem from the method declaration to an annotation. Still, it doesn't cure the underlying problem of fixing a query that is too complex. If it is complex, then it needs to be expressed that way or be addressed using the store's query language; We recommend either using @Query or a custom implementation.

Furthermore, we are strictly against putting code into annotations that actually belong somewhere else. Such a change leads to removing information from the call site. The method name becomes less expressive at its best. In the worst case, it contradicts what was specified in an annotation.

@mp911de mp911de closed this as not planned Won't fix, can't repro, duplicate, stale Feb 23, 2023
@mp911de mp911de added status: declined A suggestion or change that we don't feel we should currently apply and removed type: enhancement A general enhancement has: votes-jira labels Feb 23, 2023
@jxblum
Copy link
Contributor

jxblum commented Feb 23, 2023

I speak here in the general case, and not specifically with respect to SD JPA.

Perhaps a less-known feature of Spring Data, and unfortunately, it is not a feature that is supported across all Spring Data modules, but a nice alternative none-the-less is, some Spring Data modules support a QueryLookupStrategy that pulls the actual store-specific query from a Java Properties file and uses the entity's simple type name + the query method's name as a key to lookup the query to use for that Repository query method when invoked.

TIP: Query Lookup Strategies are loosely described in the Spring Data Commons reference documentation, here. The key statements in this documentation are 1) "Some strategies may not be supported for particular datastores." and 2) under USE_DECLARED_QUERY option, "The query can be defined by an annotation somewhere or declared by other means. See the documentation of the specific store to find available options for that store."

Building on my statement above with a concrete example, suppose you have a Repository with the following query method:

interface CustomerRepository extends CrudRepository<Customer, Long> {

  Optional<Customer> findVipCustomers(String firstName, String lastName);

}

Then, the actual store-specific query could be defined in a Java Properties file (pseudo syntax used):

// Properties defining store-specific queries used by all application declared Repositories
Customer.findVipCustomers=SELECT DISTINCT c.first_name, c.last_name, c.birth_date \
  FROM Customers c \
  WHERE c.first_name = ? \
  AND c.last_name = ? \
  AND c.vip = true \
  ORDER BY c.last_name ASC, c.birth_date DESC

Typically, the Java Properties file location/name is in META-INF/<data-store-name>-named-queries.properties, where <data-store-name> could be "cassandra". Of course, this location/name can be changed, but is again store specific.

To see how this works in Spring Data Repository infrastructure, there are few things you have to know.

Internally, Spring Data (Commons) represents these externally sourced (e.g. Java Properties) and named queries with the NamedQueries type. 1 such implementation is the PropertiesBasedNamedQueries type.

The "name" of the query gets resolved from the QueryMethod type representing any query method declared in your application Repository interface, such as the findVipCustomers(..) query method declared in our CustomerRepository interface above, whether the query is derived or not (e.g. when annotated with @Query). Specifically, Spring Data's Repository infrastructure calls the QueryMethod.getNamedQueryName() method to resolve the "name" given to the query (or key in the Java Properties file). The default implementation combines the entity simple type name with the Java Method name for the [derived] query method declared by the Repository. AFAIK, most Spring Data modules do not override the default implementation.

The "location" of this Java Properties file defining the queries can be changed with Repository enabling annotation for the specific data store in use. For example, when using Spring Data Cassandra, you would use the @EnableCassandraRepositories annotation, namedQueriesLocation attribute.

Then, internally, and by example, Spring Data Cassandra's QueryLookupStrategy will use the above mentioned infrastructure objects to lookup the query from an external source, such as a Java Properties file (default).

It is pretty eloquent.

It seems to me, Spring Data JPA's QueryLookupStrategy supports named query resolution quite nicely. See here in particular.

With all the mucking with [derived] query method names (and overly used @Query annotations for rather complex queries), I am not sure why users don't use this Spring Data feature more. There are also other advantages to externalizing a query like this.

Although, I can empathize somewhat since our documentation does not explicitly describe this feature, and store-specific documentation in many cases is a just a blanket copy of Spring Data Commons' reference documentation, particularly for Spring Data Repository support.

@jxblum
Copy link
Contributor

jxblum commented Feb 23, 2023

To summarize the different approaches for declaring queries with Spring Data's Repository infrastructure, the following common strategies can be used:

  1. Queries derived from carefully crafted and named Repository query methods.

  2. Defining queries with the @Query annotation declared on Repository query methods.

  3. Custom Repository method implementations.

  4. Externally sourced queries as I described above.

Other non-common approaches and forms of querying that some Spring Data modules may support include, but are not limited to: Query by Example or Querydsl, JOOQ, HQL, etc.

Spring Data does not prevent or limit your choice; the limitation only occurs due to the underlying data store.

@Mooninaut
Copy link

Mooninaut commented Jan 8, 2024

Spring Data is not in charge of defining yet another query language. We've built query derivation to allow users conveniently declare simple queries without having to dive into the store-specific query language to implement common use cases. If a method becomes too long, then it no longer fits the intended usage pattern, and it falls out of this category.

Except query derivation is a query DSL. I can't see how to reconcile these two statements.

Putting the potential String into an annotation moves the problem from the method declaration to an annotation. Still, it doesn't cure the underlying problem of fixing a query that is too complex. If it is complex, then it needs to be expressed that way or be addressed using the store's query language; We recommend either using @Query or a custom implementation.

Instead of moving the problem to an annotation (@QueryByMethodName), move the problem to a more complicated and harder to read annotation (@Query)?

Furthermore, we are strictly against putting code into annotations that actually belong somewhere else. Such a change leads to removing information from the call site. The method name becomes less expressive at its best. In the worst case, it contradicts what was specified in an annotation.

I don't understand rejecting @QueryByMethodName with a rationale that applies even more strongly to @Query.

I write this because I have observed in practice, that moving logic from query derivation to @Query dramatically increases the chance that the resulting query logic will accidentally differ from the intended logic. The derivation DSL, like it or not, is a valuable tool to express simple queries simply. The issue is not that the resulting queries are too complex but that their method names are too long when the queries have multiple parameters.

@odrotbohm
Copy link
Member

Except query derivation is a query DSL. I can't see how to reconcile these two statements.

It might be a language thing, but “yet another” intended to mean “another one on top of the already existing (query derivation, store specific queries, Querydsl, query by example, custom implementations) ones”.

Generally speaking, for each request, we have to judge different aspects:

  1. Benefit of the change to the user
  2. Effects on the complexity to the maintainers (both implementation and documentation)
  3. Effects on the cognitive complexity for the users (to understand the tradeoff, when to use what etc.)

Given that all these alternatives already exist, 1 is low by definition. 2 and 3 definitely increase, and we came to the conclusion that adding what's asked for is not worth the effort. Rest assured that we have a pretty good impression of how our users use repository query methods.

@spring-projects spring-projects locked as resolved and limited conversation to collaborators Jan 8, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

10 participants