JPA 动态条件-api 查询
JPA dynamic criteria-api query
我想知道是否有一种通用方法可以将标准 api 与更复杂的模型结合使用?
我有一个与其他实体具有一对一关系的实体 class。我的服务包装器通过条件 api 进行数据库查询,从前端获取参数以计算分页、排序和过滤。
实体
@Entity
public class Person implements Serializable {
@Id
private Long id;
private String name;
private String givenName;
@Temporal(TemporalType.DATE)
private Date birthdate;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "INFORMATION_ID")
private Information information;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "ADDRESS_ID")
private Address address;
...
}
@Entity
public class Information implements Serializable {
@Id
private Long id;
private String detail;
...
}
@Entity
public class Address implements Serializable {
@Id
private Long id;
private String street;
private String city;
...
}
服务
@Stateless
public class PersonService {
@PersistenceContext(unitName = "ProblemGenericDatatableFilterPU")
private EntityManager em;
public List<Person> findAllPersons222(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = builder.createQuery(Person.class);
Root<Person> rootPerson = criteriaQuery.from(Person.class);
Join<Person, Information> joinPersonInformation = rootPerson.join(Person_.information);
Join<Person, Address> joinPersonAddress = rootPerson.join(Person_.address);
// select
criteriaQuery.select(rootPerson);
// filter
List<Predicate> allPredicates = new ArrayList<>();
for(Entry<String, Object> currentEntry : filters.entrySet()) {
Predicate currentPredicate;
if(currentEntry.getKey().startsWith("information_")) {
currentPredicate = builder.like(
builder.lower(joinPersonInformation.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
else if(currentEntry.getKey().startsWith("address_")) {
currentPredicate = builder.like(
builder.lower(joinPersonAddress.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
else {
currentPredicate = builder.like(
builder.lower(rootPerson.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
allPredicates.add(currentPredicate);
}
criteriaQuery.where(builder.and(allPredicates.toArray(new Predicate[0])));
// order
if(sortField != null && !sortField.isEmpty()) {
Order orderBy;
if(sortField.startsWith("information_")) {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(joinPersonInformation.get(sortField))
: builder.asc(joinPersonInformation.get(sortField)));
}
else if(sortField.startsWith("address_")) {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(joinPersonAddress.get(sortField))
: builder.asc(joinPersonAddress.get(sortField)));
}
else {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(rootPerson.get(sortField))
: builder.asc(rootPerson.get(sortField)));
}
criteriaQuery.orderBy(orderBy);
}
Query query = em.createQuery(criteriaQuery);
// pagination
query.setFirstResult(first);
query.setMaxResults(pageSize);
return query.getResultList();
}
}
我需要根据访问 属性 的 root/join 来区分过滤和排序的情况。另外,我需要在 facelet 中使用命名约定。 count-query 除了排序之外也是如此。
现在我问自己是否有一些 "dot-notation" 或任何东西可以让这个案子变得可有可无。在 e。 G。本机 SQL 我会做一些事情,比如创建一个子查询和 select 来自内部投影的所有别名值 (select * from (select person.name as name, address.street as street, ...) where name = ... and street like ...
).
如有任何建议,我将不胜感激。
我终于有时间来处理我的问题了。我找到了一个不完美的解决方案,但对我有用。
当我搜索另一个问题时,我找到了 this article by Leonardo Shikida 并找到了一个非常方便的 Path<?> getPath(...)
方法(我还深入研究了 CriteriaAPI 中出色的继承关系:路径、根、加入、来自等)。考虑到这一点,我想起了我以前的问题,并想出了一种更通用的方法。所以这就是我所做的:
首先我创建了我需要的所有连接(即 Root<?>
和 Join<? ?>
)并将它们放在 Map<String, From<?, ?>>
中,其中 String
是一个元素以点分符号查询属性(命名约定和完整解决方案的缺点)并且 From
是相应的来源。
使用地图,我可以以或多或少通用的方式进行过滤和排序。
为了使其工作,前端需要使用完全相同的命名约定并相应地传递 filters-Map(即 JSF 在 p:column 中使用 primefaces field
属性)。
public List<Person> newFindAllPersons(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters)
{
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = builder.createQuery(Person.class);
// setting up the required joins
Root<Person> rootPerson = criteriaQuery.from(Person.class);
Join<Person, Information> joinPersonInformation = rootPerson.join(Person_.information);
Join<Person, Address> joinPersonAddress = rootPerson.join(Person_.address);
Join<Address, Information> joinAddressInformation = joinPersonAddress.join(Address_.information);
// putting all joins into a map with a dot`ted name
Map<String, From<?, ?>> mapFieldToFrom = new HashMap<>();
mapFieldToFrom.put("person", rootPerson);
mapFieldToFrom.put("person.address", joinPersonAddress);
mapFieldToFrom.put("person.information", joinPersonInformation);
mapFieldToFrom.put("person.address.information", joinAddressInformation);
// select
criteriaQuery.select(rootPerson);
// filter
List<Predicate> allPredicates = new ArrayList<>();
for(Entry<String, Object> currentEntry : filters.entrySet())
{
Predicate currentPredicate = builder.like(
builder.lower(getStringPath(currentEntry.getKey(), mapFieldToFrom)),
builder.lower(builder.literal("%" + String.valueOf(currentEntry.getValue()) + "%"))
);
allPredicates.add(currentPredicate);
}
criteriaQuery.where(builder.and(allPredicates.toArray(new Predicate[0])));
// order
if(sortField != null && !sortField.isEmpty())
{
Path<?> actualPath = getStringPath(sortField, mapFieldToFrom);
Order orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(actualPath)
: builder.asc(actualPath));
criteriaQuery.orderBy(orderBy);
}
Query query = em.createQuery(criteriaQuery);
// pagination
query.setFirstResult(first);
query.setMaxResults(pageSize);
return query.getResultList();
}
/**
* divides the given field at the last dot and takes <br>
* - the first part as the key in the map to retrieve the From<?, ?> <br>
* - the last part as the name of the column in the entity
*/
private Path<String> getStringPath(String field, Map<String, From<?, ?>> mapFieldToFrom)
{
if(!field.matches(".+\..+"))
{
throw new IllegalArgumentException("field '" + field + "' needs to be a dotted path (i. e. customer.address.city.zipcode)");
}
String fromPart = field.substring(0, field.lastIndexOf('.'));
String fieldPart = field.substring(field.lastIndexOf('.') + 1);
From<?, ?> actualFrom = mapFieldToFrom.get(fromPart);
if(actualFrom == null)
{
throw new IllegalStateException("the given map does not contain a from or for the value '" + fromPart + "' or is null");
}
return actualFrom.get(fieldPart);
}
前端示例
<p:dataTable>
<!-- mapFieldToFrom.put("person", rootPerson); -->
<p:column field="person.name">
</p:column>
<!-- mapFieldToFrom.put("person.address", joinPersonAddress); -->
<p:column field="person.address.street">
</p:column>
</p:dataTable>
我想知道是否有一种通用方法可以将标准 api 与更复杂的模型结合使用?
我有一个与其他实体具有一对一关系的实体 class。我的服务包装器通过条件 api 进行数据库查询,从前端获取参数以计算分页、排序和过滤。
实体
@Entity
public class Person implements Serializable {
@Id
private Long id;
private String name;
private String givenName;
@Temporal(TemporalType.DATE)
private Date birthdate;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "INFORMATION_ID")
private Information information;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "ADDRESS_ID")
private Address address;
...
}
@Entity
public class Information implements Serializable {
@Id
private Long id;
private String detail;
...
}
@Entity
public class Address implements Serializable {
@Id
private Long id;
private String street;
private String city;
...
}
服务
@Stateless
public class PersonService {
@PersistenceContext(unitName = "ProblemGenericDatatableFilterPU")
private EntityManager em;
public List<Person> findAllPersons222(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = builder.createQuery(Person.class);
Root<Person> rootPerson = criteriaQuery.from(Person.class);
Join<Person, Information> joinPersonInformation = rootPerson.join(Person_.information);
Join<Person, Address> joinPersonAddress = rootPerson.join(Person_.address);
// select
criteriaQuery.select(rootPerson);
// filter
List<Predicate> allPredicates = new ArrayList<>();
for(Entry<String, Object> currentEntry : filters.entrySet()) {
Predicate currentPredicate;
if(currentEntry.getKey().startsWith("information_")) {
currentPredicate = builder.like(
builder.lower(joinPersonInformation.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
else if(currentEntry.getKey().startsWith("address_")) {
currentPredicate = builder.like(
builder.lower(joinPersonAddress.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
else {
currentPredicate = builder.like(
builder.lower(rootPerson.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
allPredicates.add(currentPredicate);
}
criteriaQuery.where(builder.and(allPredicates.toArray(new Predicate[0])));
// order
if(sortField != null && !sortField.isEmpty()) {
Order orderBy;
if(sortField.startsWith("information_")) {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(joinPersonInformation.get(sortField))
: builder.asc(joinPersonInformation.get(sortField)));
}
else if(sortField.startsWith("address_")) {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(joinPersonAddress.get(sortField))
: builder.asc(joinPersonAddress.get(sortField)));
}
else {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(rootPerson.get(sortField))
: builder.asc(rootPerson.get(sortField)));
}
criteriaQuery.orderBy(orderBy);
}
Query query = em.createQuery(criteriaQuery);
// pagination
query.setFirstResult(first);
query.setMaxResults(pageSize);
return query.getResultList();
}
}
我需要根据访问 属性 的 root/join 来区分过滤和排序的情况。另外,我需要在 facelet 中使用命名约定。 count-query 除了排序之外也是如此。
现在我问自己是否有一些 "dot-notation" 或任何东西可以让这个案子变得可有可无。在 e。 G。本机 SQL 我会做一些事情,比如创建一个子查询和 select 来自内部投影的所有别名值 (select * from (select person.name as name, address.street as street, ...) where name = ... and street like ...
).
如有任何建议,我将不胜感激。
我终于有时间来处理我的问题了。我找到了一个不完美的解决方案,但对我有用。
当我搜索另一个问题时,我找到了 this article by Leonardo Shikida 并找到了一个非常方便的 Path<?> getPath(...)
方法(我还深入研究了 CriteriaAPI 中出色的继承关系:路径、根、加入、来自等)。考虑到这一点,我想起了我以前的问题,并想出了一种更通用的方法。所以这就是我所做的:
首先我创建了我需要的所有连接(即 Root<?>
和 Join<? ?>
)并将它们放在 Map<String, From<?, ?>>
中,其中 String
是一个元素以点分符号查询属性(命名约定和完整解决方案的缺点)并且 From
是相应的来源。
使用地图,我可以以或多或少通用的方式进行过滤和排序。
为了使其工作,前端需要使用完全相同的命名约定并相应地传递 filters-Map(即 JSF 在 p:column 中使用 primefaces field
属性)。
public List<Person> newFindAllPersons(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters)
{
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = builder.createQuery(Person.class);
// setting up the required joins
Root<Person> rootPerson = criteriaQuery.from(Person.class);
Join<Person, Information> joinPersonInformation = rootPerson.join(Person_.information);
Join<Person, Address> joinPersonAddress = rootPerson.join(Person_.address);
Join<Address, Information> joinAddressInformation = joinPersonAddress.join(Address_.information);
// putting all joins into a map with a dot`ted name
Map<String, From<?, ?>> mapFieldToFrom = new HashMap<>();
mapFieldToFrom.put("person", rootPerson);
mapFieldToFrom.put("person.address", joinPersonAddress);
mapFieldToFrom.put("person.information", joinPersonInformation);
mapFieldToFrom.put("person.address.information", joinAddressInformation);
// select
criteriaQuery.select(rootPerson);
// filter
List<Predicate> allPredicates = new ArrayList<>();
for(Entry<String, Object> currentEntry : filters.entrySet())
{
Predicate currentPredicate = builder.like(
builder.lower(getStringPath(currentEntry.getKey(), mapFieldToFrom)),
builder.lower(builder.literal("%" + String.valueOf(currentEntry.getValue()) + "%"))
);
allPredicates.add(currentPredicate);
}
criteriaQuery.where(builder.and(allPredicates.toArray(new Predicate[0])));
// order
if(sortField != null && !sortField.isEmpty())
{
Path<?> actualPath = getStringPath(sortField, mapFieldToFrom);
Order orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(actualPath)
: builder.asc(actualPath));
criteriaQuery.orderBy(orderBy);
}
Query query = em.createQuery(criteriaQuery);
// pagination
query.setFirstResult(first);
query.setMaxResults(pageSize);
return query.getResultList();
}
/**
* divides the given field at the last dot and takes <br>
* - the first part as the key in the map to retrieve the From<?, ?> <br>
* - the last part as the name of the column in the entity
*/
private Path<String> getStringPath(String field, Map<String, From<?, ?>> mapFieldToFrom)
{
if(!field.matches(".+\..+"))
{
throw new IllegalArgumentException("field '" + field + "' needs to be a dotted path (i. e. customer.address.city.zipcode)");
}
String fromPart = field.substring(0, field.lastIndexOf('.'));
String fieldPart = field.substring(field.lastIndexOf('.') + 1);
From<?, ?> actualFrom = mapFieldToFrom.get(fromPart);
if(actualFrom == null)
{
throw new IllegalStateException("the given map does not contain a from or for the value '" + fromPart + "' or is null");
}
return actualFrom.get(fieldPart);
}
前端示例
<p:dataTable>
<!-- mapFieldToFrom.put("person", rootPerson); -->
<p:column field="person.name">
</p:column>
<!-- mapFieldToFrom.put("person.address", joinPersonAddress); -->
<p:column field="person.address.street">
</p:column>
</p:dataTable>