【问题标题】:how to select multiple objects in spring data jpa using specifications如何使用规范在spring data jpa中选择多个对象
【发布时间】:2020-01-26 22:08:20
【问题描述】:

我关注了 @Query,它运行良好。但是现在我有一个场景,屏幕需要一个过滤器,它会在查询中添加一些 where 子句。

@Query

("
        SELECT 
            ef, ed, ea
        FROM EntityA ea
            JOIN EntityB eb
            JOIN EntityC ec
            JOIN EntityD ed
            JOIN EntityE ee
            JOIN EntityF ef
        WHERE
            TRUNC(ee.date) = TRUNC(:date)

        -- conditions based on screen filter parameters
        AND ef.amount = :amount
        AND LOWER(ec.name) LIKE LOWER('%' || :name || '%')
        AND ec.projectId = :projectId
        AND ed.divisionId = :divisionId

")

发现有很好的Specifications 支持,可以根据需要动态创建查询。

但不确定如何使用 Specifications 一次性选择多个对象 efedea,否则我必须再编写 4 个查询以根据过滤条件返回结果。

注意出于性能原因不使用预加载,因为实体被多个服务使用。

【问题讨论】:

  • 使用 StringBuilder 并根据规范构建查询,然后将该字符串作为查询传递给存储库。
  • '注意:与传统/传统循环相比,流在性能方面要好得多'我的回答:完全不,它可能更糟;)

标签: spring-data-jpa criteria-api


【解决方案1】:

我能够通过实现Custom Repositories,在该实现类中自动连接EntityManager,然后根据传递的参数构建最终的JPQL 来实现这一点。 Eugen 的blog 就是一个很好的例子。

以前我有以下结构

public interface EntityARepository extends JpaRepository<EntityA, Long> {
    @Query(...)
    List<EntityA> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}

public interface EntityAService {
    List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}

@Service
public class EntityAServiceImpl implements EntityAService {

    @Autowired
    EntityARepository entityARepository;

    @Override
    public List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId) {
        ...
        ...
        ...
    }
}

通过使用自定义存储库,一切都变得像

public interface EntityACustomRepository {
    List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}

public interface EntityARepository extends JpaRepository<EntityA, Long> {
    //@Query(...)
    //List<EntityA> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}

@Repository
public class EntityACustomRepositoryImpl implements EntityACustomRepository {

    // autowiring entityManager helped to create and execute dynamic jpql
    @Autowired
    EntityManager entityManager;

    public List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId) {
        String jpql = "SELECT " +
            " ef, ed, ea " +
        " FROM EntityA ea " +
            " JOIN EntityB eb " +
            " JOIN EntityC ec " +
            " JOIN EntityD ed " +
            " JOIN EntityE ee " +
            " JOIN EntityF ef " +
        " WHERE " +
            " TRUNC(ee.date) = TRUNC(:date) "
        ;

        //conditions based on screen filter parameters
        if(amount!=null && amount>0L) {
            jpql += " AND ef.amount = :amount ";
        }
        if(name!=null && name.trim().length()>0) {
            jpql += " AND LOWER(ec.name) LIKE LOWER('%' || :name || '%') ";
        }
        if(projectId!=null && projectId>0L) {
            jpql += " AND ec.projectId = :projectId ";
        }
        if(divisionId!=null && divisionId>0L) {
            jpql += " AND ed.divisionId = :divisionId ";
        }

        Query query = entityManager.createQuery(jpql);
        query.setParameter("date", filterDate);

        if(amount!=null && amount>0L) {
            query.setParameter("amount", amount);
        }
        if(name!=null && name.trim().length()>0) {
            query.setParameter("name", name);
        }
        if(projectId!=null && projectId>0L) {
            query.setParameter("projectId", projectId);
        }
        if(divisionId!=null && divisionId>0L) {
            query.setParameter("divisionId", divisionId);
        }

        return query.getResultList();
    }

}

public interface EntityAService {
    List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId);
}

@Service
public class EntityAServiceImpl implements EntityAService {

    @Autowired
    EntityARepository entityARepository;

    @Override
    public List<Object[]> findAllBy(Date filterDate, Long amount, String name, Long projectId, Long divisionId) {
        return entityARepository.findAllBy(filterDate, amount, name, projectId, divisionId);
    }

}

【讨论】:

    【解决方案2】:

    如果您使用 JPA 映射子实体,则可以使用规范 API 查询子实体,例如与@OneToMany。

    @Entity
    @Table(...)
    public class EntityA {
        // Omitting fields
    
        @OneToMany(...)
        private List<EntityB> bList = new ArrayList<>();
    
    }
    
    
    public class EntityASpecification implements Specification<EntityA> {
    
        private SearchCriteriaValueClass criteria;
    
        public EntityASpecification(SearchCriteriaValueClass criteria) {
            this.criteria = criteria;
        }
    
        @Override
        public Predicate toPredicate(Root<EntityA> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    
            ListJoin<EntityA, EntityB> pathToEntityB = root.join(EntityA_.bList, JoinType.INNER);
            Predicate amountInBIsEqual = builder.equal(pathToEntityB.get(EntityB_.amount), criteria.getAmount);
    
            Path<SomeEntityAField> pathToAField = root.get(EntityA_.someAField);
            Predicate someValueInA = pathToAField.in(criteria.getCollectionForAFieldToBeIn());
    
            query.distinct(true);
            return builder.and(amountInBIsEqual, someValueInA);
        }
    }
    
    

    显然,规范是为一个实体定义的,并且只能返回该实体的实例。除了以has-a关系连接之外,我没有看到任何(安全且有效的)方法可以在一个方法调用中返回F、D和A的实例。

    【讨论】:

      【解决方案3】:

      规范仅用于动态创建 where 子句。

      如果您还需要控制 select 子句,我建议您在存储库的 custom method 中使用 JPA Criteria API

      【讨论】:

      • 那么我想知道“查询”的目的是什么 toPredicate(Root root, CriteriaQuery> query, CriteriaBuilder builder); ?或者它可能仅适用于 where 子句?
      猜你喜欢
      • 2017-10-28
      • 2020-03-03
      • 2017-08-19
      • 2022-11-21
      • 2018-05-07
      • 2017-01-09
      • 2017-06-01
      • 2019-05-25
      相关资源
      最近更新 更多