Appending custom conditions on spring data jpa repository method queries

隐身守侯 提交于 2019-12-21 02:43:30

问题


Short Version

I am looking for a way to have all the findBy methods of a repository class appended with a particular condition

Full Version

Let's assume I have a Product entity and Customer entity. Both of them extends the OwnerAwareEntity and they inherit ownerRef field which identifies the owner of the entity( It could be a merchant or a partner ). I want to have the findBy methods of the Product and Customer modified in runtime such that they are appended with an additional condition of the ownerRef. The ownerRef value could be identified from the user session.

Example

The parent entity class that provides the common ownerRef field

public class OwnerAwareEntity implements Serializable {

 private String ownerRef;

}

Customer entity extending OwnerAwareEntity

public class Customer extends OwnerAwareEntity {

  private String firstname;

  private String mobile ;

}

Product entity extending OwnerAwareEntity

public class Product extends OwnerAwareEntity {

  private String code;

  private String name;

}

Repository class for Product & Customer extending an OwnerAwareRepository

public interface OwnerAwareRepository extends JpaRepository {

}

public interface ProductRepository extends OwnerAwareRepository {

  Product findByCode(String code );

}

public interface CustomerRepository extends OwnerAwareRepository {

  Customer findByFirstname(String firstname );

}

This, when executed, should result in a query like below

select P from Product P where P.code=?1 and P.ownerRef='aValue'
&
select C from Customer C where C.firstname=?1 and C.ownerRef='aValue'

What should be my approach to have this appending of condition achieved?. I only want this appending to be happening when the parent repository is OwnerAwareRepository.


回答1:


TL;DR: I used @Filter of Hibernate and then created an Aspect to intercept the methods

Defined a base class entity with the following structure

OwnerAwareEntity.java

import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;    
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;

@MappedSuperclass
@FilterDef(name = "ownerFilter", parameters = {@ParamDef(name = "ownerRef", type = "long")})
@Filter(name = "ownerFilter", condition = "OWNER_REF = :ownerRef")
public class OwnerAwareEntity implements Serializable{

    @Column(name = "OWNER_REF",nullable = true)
    private Long ownerRef;

}

We set the filter on this entity. The hibernate @Filter allows us to set a condition to be appended to the select where clause.

Next, defined a base repository for the entity of type OwnerAwareEntity

OwnerAwareRepository.java

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface OwnerAwareRepository<T, ID extends java.io.Serializable> extends JpaRepository<T, ID> {

}

Created an Aspect that will intercept all the methods from the repositories that extend OwnerAwareRepository

OwnerFilterAdvisor.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Aspect
@Component
@Slf4j
public class OwnerFilterAdvisor {

    @PersistenceContext
    private EntityManager entityManager;

    @Pointcut("execution(public * com.xyz.app.repository.OwnerAwareRepository+.*(..))")
    protected void ownerAwareRepositoryMethod(){

    }

    @Around(value = "ownerAwareRepositoryMethod()")
    public Object enableOwnerFilter(ProceedingJoinPoint joinPoint) throws Throwable{

        // Variable holding the session
        Session session = null;

        try {

            // Get the Session from the entityManager in current persistence context
            session = entityManager.unwrap(Session.class);

            // Enable the filter
            Filter filter = session.enableFilter("ownerFilter");

            // Set the parameter from the session
            filter.setParameter("ownerRef", getSessionOwnerRef());

        } catch (Exception ex) {

            // Log the error
            log.error("Error enabling ownerFilter : Reason -" +ex.getMessage());

        }

        // Proceed with the joint point
        Object obj  = joinPoint.proceed();

        // If session was available
        if ( session != null ) {

            // Disable the filter
            session.disableFilter("ownerFilter");

        }

        // Return
        return obj;

    }


    private Long getSessionOwnerRef() {

// Logic to return the ownerRef from current session

    }
}

The advisor is set to intercept all the methods from classes that extends the OwnerAwareRepository. On the interception, the current hibernate Session is obtained from entityManager ( of current persistence context ) and the filter is enabled with the param value of "ownerRef".

Also have a configuration file created to have the advisor scanned

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"com.xyz.app.advisors"})
public class AOPConfig {
}

Once these files are in place, you need to have the following things done for the entities that need to be owner aware

  1. The entity needs to extend OwnerAwareEntity
  2. The entity repository class need to extend OwnerAwareRepository

Dependencies

This setup requires the spring aop to be in the dependencies. You may add the following to the pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Advantages

  1. Works with all select queries ( findBy methods, findAll etc )
  2. @Query methods also gets intercepted
  3. Simple implementation

Caveats

  • The where clause of delete or update is not affected by
    this filter.
  • If the repository contains a save/update/delete method and if the
    method is not tagged as @Transactional, then interceptor will give
    error ( You can catch and have the method proceed normally in these
    cases)



回答2:


You can use Predicate of QueryDSL (or Specification) in Spring Data JPA methods.

Example:

interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {
}

Predicate predicate = QUser.user.firstname.equalsIgnoreCase("dave")
    .and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);

To work with QueryDSL add to you pom.xml:

<dependencies>
//..
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <version>4.1.4</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>4.1.4</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>

        <plugin>
            <groupId>com.mysema.maven</groupId>
            <artifactId>apt-maven-plugin</artifactId>
            <version>1.1.3</version>
            <executions>
                <execution>
                    <goals>
                        <goal>process</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>target/generated-sources</outputDirectory>
                        <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Then compile your project and you will get Q-classes of your entities. More info is here.



来源:https://stackoverflow.com/questions/45080171/appending-custom-conditions-on-spring-data-jpa-repository-method-queries

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!