如何使用规范 select spring 数据 jpa 中的多个对象

how to select multiple objects in spring data jpa using specifications

我有以下 @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支持根据要求动态创建查询。

但不确定如何 select 多个对象 ef, ed & ea 使用 Specifications 一次性完成,否则我必须根据过滤条件向 return 结果再写 4 个查询。

N.B. 出于性能原因不使用预先加载,因为多个服务使用实体。

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

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

如果您使用 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);
    }
}

显然,规范是为一个实体定义的,并且只能 return 个实例。我没有看到 return 在一个方法调用中使用 F、D 和 A 的实例的任何(安全有效的)方式,除了它们以具有关系的方式连接。

我能够通过实施 Custom Repositories, auto-wiring the EntityManager in that implementation class and then building the final JPQL based on parameters passed. A good example is in Eugen's 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);
    }

}