如何使用规范 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);
}
}
我有以下 @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);
}
}