Appending custom conditions on spring data jpa repository method queries

后端 未结 2 2047
梦如初夏
梦如初夏 2021-02-04 19:46

Short Version

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

Full Vers

相关标签:
2条回答
  • 2021-02-04 20:06

    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.

    0 讨论(0)
  • 2021-02-04 20:23

    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)
    0 讨论(0)
提交回复
热议问题