spring-projects / spring-data-jpa

Simplifies the development of creating a JPA-based data access layer.
https://spring.io/projects/spring-data-jpa/
Apache License 2.0
3.03k stars 1.42k forks source link

Insufficient documentation for Value expressions #3688

Open sergey-morenets opened 4 days ago

sergey-morenets commented 4 days ago

Spring Data 3.4.0 brings new feature - Value Expressions (https://github.com/spring-projects/spring-data-jpa/issues/3619)

However the latest documentation section(https://docs.spring.io/spring-data/jpa/reference/jpa/value-expressions.html) has only one example of the code:

@Document("orders-#{tenantService.getOrderCollection()}-${tenant-config.suffix}")
class Order {
  // …
}

This example is from Spring Data Mongo and can't be used for Spring Data JPA. Moreover the whole section is copied from Spring Data Mongo documentation without any changes (https://docs.spring.io/spring-data/mongodb/reference/mongodb/value-expressions.html)

So neither the documentation nor GitHub ticket have examples how to use new feature in Spring Data JPA. So it'd be nice to add few examples in the documentation.

mp911de commented 4 days ago

Usage of Config Properties is documented at https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html#jpa.query.spel-expressions at Example 20. Using Value Expressions in Repository Query Methods: Configuration Properties

Let me know what else you're looking for.

For JPA, the evaluation of annotation values is subject to JPA providers, where we cannot inject additional functionality. However, the mentioned page about Value Expressions is part of our common documentation.

Maybe the issue is that we created some over-expectation by our naming. Value Expressions are SpEL expressions plus Property Placeholders whereas we previously only supported SpEL expressions.

sergey-morenets commented 3 days ago

This section misses two important topics.

  1. What is the scope of this functionality? Examples demonstrate how we can use Value Expressions in @Query annotation. Are there any other places (annotations) where can we use them?
  2. Example 2. Expression Examples includes this example:

#{tenantService.getOrderCollection()}

But if I try to use it in @Query annotation (productService is bean id):

    @Query("""          
            FROM Product WHERE name=:name 
            and department=?#{productService.getDepartment()}""")
    Product findByName(String name);

then I receive an exception: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'productService' cannot be found on object of type 'java.lang.Object[]' - maybe not public or not valid?

So it's not clear whether we can access bean properties/methods in Value Expressions and if yes then how?

mp911de commented 2 days ago

Documenting the scope makes sense. For the second issue, it works as designed.

It would make sense to call out that the default root object is the array of query method parameters when working with queries. Any beans must be accessed via ?#{#productService.getDepartment()} (note the missing # was added before productService). Any other properties, variables and functions must be contributed by declaring a EvaluationContextExtension as per documentation.

By explaining scopes in the docs, we can make the second case more clear.

sergey-morenets commented 13 hours ago

@mp911de

Unfortunately your advise didn't help. I added #:

    @Query("""          
            FROM Product WHERE name=:name 
            and department=?#{#productService.getDepartment()}""")
    Product findByName(String name);

But now error is different:

org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method getDepartment() on null context object

Although productService bean is available in the application context.

sergey-morenets commented 13 hours ago

@mp911de

I investigated and debugged this issue and it seems that it's not possible to access Spring beans using this construction:

?#{#productService.getDepartment()}

There's BeanResolver interface which JavaDocs clearly state that @ or & characters should be used instead of #:

/**
 * A bean resolver can be registered with the evaluation context and will kick in
 * for bean references: {@code @myBeanName} and {@code &myBeanName} expressions.
 *
 * <p>The {@code &} variant syntax allows access to the factory bean where relevant.
 *
 * @author Andy Clement
 * @since 3.0.3
 */
@FunctionalInterface
public interface BeanResolver {

So the correct construction is


    @Query("""          
            FROM Product WHERE name=:name 
            and department=?#{@productService.getDepartment()}""")
    Product findByName(String name);