【问题标题】:Appending custom conditions on spring data jpa repository method queries在spring数据jpa存储库方法查询上附加自定义条件
【发布时间】:2017-07-13 12:08:22
【问题描述】:

短版

我正在寻找一种方法,让存储库类的所有 findBy 方法都附加特定条件

完整版

假设我有一个 Product 实体和 Customer 实体。它们都扩展了 OwnerAwareEntity 并继承了 ownerRef 字段,该字段标识了实体的所有者(可以是商家或合作伙伴)。我想在运行时修改 Product 和 Customer 的 findBy 方法,以便它们附加 ownerRef 的附加条件。可以从用户会话中识别 ownerRef 值。

示例

提供公共 ownerRef 字段的父实体类

public class OwnerAwareEntity implements Serializable {

 private String ownerRef;

}

扩展 OwnerAwareEntity 的客户实体

public class Customer extends OwnerAwareEntity {

  private String firstname;

  private String mobile ;

}

产品实体扩展 OwnerAwareEntity

public class Product extends OwnerAwareEntity {

  private String code;

  private String name;

}

扩展 OwnerAwareRepository 的产品和客户的存储库类

public interface OwnerAwareRepository extends JpaRepository {

}

public interface ProductRepository extends OwnerAwareRepository {

  Product findByCode(String code );

}

public interface CustomerRepository extends OwnerAwareRepository {

  Customer findByFirstname(String firstname );

}

这在执行时会产生如下查询

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'

我应该采取什么方法来实现这种附加条件?我只希望在父存储库是 OwnerAwareRepository 时发生这种附加。

【问题讨论】:

    标签: spring hibernate jpa spring-data-jpa spring-aop


    【解决方案1】:

    TL;DR:我使用了Hibernate的@Filter,然后创建了一个Aspect来拦截方法

    定义了一个具有以下结构的基类实体

    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;
    
    }
    

    我们在这个实体上设置了过滤器。 hibernate @Filter 允许我们设置要附加到 select where 子句的条件。

    接下来,为 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> {
    
    }
    

    创建了一个切面,它将拦截来自扩展 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
    
        }
    }
    

    advisor 设置为拦截扩展 OwnerAwareRepository 的类中的所有方法。在拦截时,当前休眠会话是从 entityManager (当前持久性上下文的)中获取的,并且使用“ownerRef”的参数值启用过滤器。

    还创建了一个配置文件以扫描顾问程序

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan(basePackages = {"com.xyz.app.advisors"})
    public class AOPConfig {
    }
    

    这些文件到位后,您需要为需要了解所有者的实体完成以下操作

    1. 实体需要扩展 OwnerAwareEntity
    2. 实体仓库类需要扩展 OwnerAwareRepository

    依赖关系

    此设置要求 spring aop 在依赖项中。您可以在 pom.xml 中添加以下内容

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

    优势

    1. 适用于所有选择查询(findBy 方法、findAll 等)
    2. @Query 方法也会被拦截
    3. 简单的实现

    注意事项

    • 删除或更新的where子句不受
      这个过滤器。
    • 如果存储库包含保存/更新/删除方法并且如果
      方法未标记为@Transactional,则拦截器将给出
      错误(您可以在这些
      中捕获并让该方法正常进行 案例)

    【讨论】:

    • 当一个实体没有任何过滤器时如何使用这个solotion?
    • @MohammadMirzaeyan:此解决方案依赖于使用休眠的过滤器功能来注入自定义条件。通过使用谓词或拦截查询生成,可能还有其他一些可用的解决方案。不幸的是,我没有对此进行任何进一步的研究,因为基于过滤器的解决方案足以满足我的用例。
    • 绝对是一个不错的解决方案,但是在我的代码库中,一些域模型有条件,而有些则没有,如果域模型上没有任何过滤器,代码会抛出一些异常,怎么能我们处理吗?我在想也许你也解决了这个问题。
    • 如果问题是只有部分域需要应用此功能,您可以创建一个具有过滤器的基本域(如解决方案中的 OwnerAwareEntity )并扩展您想要的域使用基域应用它。这样,只有 OwnerAwareEntity(基本域)扩展域适用于过滤器。
    【解决方案2】:

    您可以在 Spring Data JPA 方法中使用 Predicate 的 QueryDSL(或 Specification)。

    例子:

    interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {
    }
    
    Predicate predicate = QUser.user.firstname.equalsIgnoreCase("dave")
        .and(user.lastname.startsWithIgnoreCase("mathews"));
    
    userRepository.findAll(predicate);
    

    要使用 QueryDSL,请添加 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>
    

    然后编译您的项目,您将获得实体的 Q 类。 更多信息是here

    【讨论】:

    • 类中的findBy方法只是举例。存储库中可能有任意数量的 findBy 方法,并希望将其全部附加特定条件。
    • 您的“任意数量的 findBy”方法应该做什么? )) 所有findBy 方法都用于静态定义条件组合。但是QueryDslPredicateExecutor 只有一个findAll 方法,其中predicate 作为参数使您能够在运行时构建任何条件组合。
    猜你喜欢
    • 2017-11-15
    • 1970-01-01
    • 2021-04-01
    • 1970-01-01
    • 2019-06-17
    • 2017-12-03
    • 2018-10-06
    • 2016-08-29
    • 1970-01-01
    相关资源
    最近更新 更多