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
2.92k stars 1.39k forks source link

Can I use global query comment instead `@QueryHint` or `@Meta`? #3485

Closed minkukjo closed 2 days ago

minkukjo commented 1 month ago

I'm using spring boot and spring data jpa 2.7 version for developing our service. I would like to add every jpa query method when jpa makes sql query without any annotation.

In my version, if I want to add query comment in jpa, I have to use like this,

@QueryHints({
            @QueryHint(name = org.hibernate.annotations.QueryHints.COMMENT, value = "userRepository.findAll")
    })

I want to add query comment as "jpa query method name".

When slow query is occurred, our DBA would let us which query is slow.

However, Our service has so many jpa and querydsl queries in our service so we don't know where query is used in our codes.

So we decide to add every query comment in our codes like"/ findByUserId /".

I couldn't find these kind of option in jpa.

I wonder spring data jpa team has a plan that is going to add global query hint in jpa.

I'm so appreciated your services.

Thank you!

mp911de commented 1 month ago

Right now, there's no global hook for query hints. You could however introduce functionality on your side to make it work. SimpleJpaRepository requires a slightly different approach than QuerydslJpaPredicateExecutor.

For the base repository implementation SimpleJpaRepository you need to create a subclass of SimpleJpaRepository and use that one as base repository.

In the second step, you create a wrapper for CrudMethodMetadata that holds query hints and other details that are evaluated for each query. You would augment CrudMethodMetadata.getComment() with details from CrudMethodMetadata.getMethod(). You could also augment CrudMethodMetadata.getQueryHints() with a custom wrapper.

For QuerydslJpaPredicateExecutor, you can subclass JpaRepositoryFactory and override getRepositoryFragments(…) to hook into CrudMethodMetadata.

minkukjo commented 1 month ago

@mp911de Thank you for your kindness answer. I'm going to try to add sub class of SimpleJpaRepository, JpaRepositoryFactory. Have a good day!

minkukjo commented 3 days ago

@mp911de Hi, I have a one more question about "wrapper for CrudMethodMetadata" I'm using JPA 2.7 version.

I made a subclass of SimpleJpaRepository so I succeeded to change base repository SimpleJpaRepository to CustomJpaRepository that I made.

And then I override setRepositoryMethodMetadata to apply Custom CrudMethodMetadata.

But CrudMethodMetadata is always null when I debug setRepositoryMethodMetadata when spring is about start.

How Can I make wrapper for CrudMethodMetadata?

@Repository
public class CustomCrudRepository<T, ID> extends SimpleJpaRepository<T, ID> {

  public CustomCrudRepository(
      JpaEntityInformation<T, ?> entityInformation,
      EntityManager entityManager) {
    super(entityInformation, entityManager);
  }

  @Autowired
  public CustomCrudRepository(Class<T> domainClass, EntityManager em) {
    this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
  }

  @Override
  public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) { // <- parameter is always null
    CrudMethodMetadata repositoryMethodMetadata = super.getRepositoryMethodMetadata();

    repositoryMethodMetadata.getMethod().getName();

    if (repositoryMethodMetadata instanceof CustomCrudMethodMetadata) {
      System.out.println("True!");
    }

    if (crudMethodMetadata instanceof CustomCrudMethodMetadata) {
      System.out.println("True!");
    }
    super.setRepositoryMethodMetadata(crudMethodMetadata);
  }
}

I copied DefaultCrudMethodMetadata to make custom CrudMethodMetadata like below


public class CustomCrudMethodMetadata implements CrudMethodMetadata {

  @Nullable
  private final LockModeType lockModeType;
  private final QueryHints queryHints;
  private final QueryHints queryHintsForCount;
  private final Optional<EntityGraph> entityGraph;
  private final Method method;

  CustomCrudMethodMetadata(Method method) {
    Assert.notNull(method, "Method must not be null!");
    this.lockModeType = findLockModeType(method);
    this.queryHints = findQueryHints(method, (it) -> true);
    this.queryHintsForCount = findQueryHints(
        method, org.springframework.data.jpa.repository.QueryHints::forCounting);
    this.entityGraph = findEntityGraph(method);
    this.method = method;
  }

  private static Optional<EntityGraph> findEntityGraph(Method method) {
    return Optional.ofNullable(
        AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class));
  }

  @Nullable
  private static LockModeType findLockModeType(Method method) {
    Lock annotation = AnnotatedElementUtils.findMergedAnnotation(method, Lock.class);
    return annotation == null ? null : (LockModeType) AnnotationUtils.getValue(annotation);
  }

  private static QueryHints findQueryHints(Method method,
      Predicate<org.springframework.data.jpa.repository.QueryHints> annotationFilter) {
    MutableQueryHints queryHints = new MutableQueryHints();
    org.springframework.data.jpa.repository.QueryHints queryHintsAnnotation = (org.springframework.data.jpa.repository.QueryHints) AnnotatedElementUtils.findMergedAnnotation(
        method, org.springframework.data.jpa.repository.QueryHints.class);
    if (queryHintsAnnotation != null && annotationFilter.test(queryHintsAnnotation)) {
      QueryHint[] var4 = queryHintsAnnotation.value();
      int var5 = var4.length;

      for (int var6 = 0; var6 < var5; ++var6) {
        QueryHint hint = var4[var6];
        queryHints.add(hint.name(), hint.value());
      }
    }

    QueryHint queryHintAnnotation = AnnotationUtils.findAnnotation(method, QueryHint.class);
    if (queryHintAnnotation != null) {
      queryHints.add(queryHintAnnotation.name(), queryHintAnnotation.value());
    }

    return queryHints;
  }

  @Nullable
  public LockModeType getLockModeType() {
    return this.lockModeType;
  }

  public QueryHints getQueryHints() {
    return this.queryHints;
  }

  public QueryHints getQueryHintsForCount() {
    return this.queryHintsForCount;
  }

  public Optional<EntityGraph> getEntityGraph() {
    return this.entityGraph;
  }

  public Method getMethod() {
    return this.method;
  }
}

How Can I customize CrudMethodMetadata to apply global query hint in SimpleJpaRepository?

Could you give me more hint to achieve my goal that is made global query comment in simpleJpaRepository

I was so appreciated your answer. Thanks a lot!! 😄