JPA Dynamic Order By with Criteria API

时间:2015-12-24 11:38:58

标签: jpa eclipselink jpa-2.0 criteria-api

我使用以下代码片段进行动态排序,使用JPA Criteria API

Root<Employee> root = criteriaQuery.from(Employee);
Join<Employee, Project> joinProject = 
        root.join(Employee_.projectList, JoinType.LEFT);

if (sortDirection.equals("asc")) {               
criteriaQuery.orderBy(cb.asc(root.get(sortField)));

如果我将Employee实体的属性传递给order by语句,它可以正常工作,但是如果将Project实体的属性传递给order by语句,则会抛出异常,说明

The attribute [projectName] is not present in the managed type

因为projectNameProject实体的一个属性,它使用joinProject与Employee连接。按顺序声明我正在使用root.get(sortField)。如果它是joinProject.get(sortField),当Project的属性按语句传递时,它将正常工作。

我的问题是

如何修改我的Order By语句以满足传递的所有属性?

我是否需要有条件地检查哪个属性并相应地使用条件或是否有更好的方法来执行此操作?

欣赏对此的见解。

2 个答案:

答案 0 :(得分:4)

一个简单场景的特定方法(预定的一级连接)可能是这样的:

    Root<Employee> root = criteriaQuery.from(Employee.class);
    Join<Employee, Project> joinProject = root.join(Employee_.projectList, JoinType.LEFT);

    Class<?> baseClass = fieldTypeMap.get(sortField);
    From<?, ?> from;
    if(baseClass == Employee.class)
    {
        from = root;
    }
    else if(baseClass == Project.class)
    {
        from = joinTeam;
    }
    else ...

    Expression<?> expr = from.get(sortField);

    if(sortDirection.equals("asc")) 
    {               
        criteriaQuery.orderBy(cb.asc(expr));
    }

    ...

其中fieldTypeMap类似于:

private final static Map<String, Class<?>> fieldTypeMap = new HashMap<>();
static {
    fieldTypeMap.put("employeeName", Employee.class);
    fieldTypeMap.put("projectName", Project.class);
    ...
}

然而,这是快速而又脏,丑陋且无法维护的。

如果你想要一个通用的方法,事情可能会有点复杂。

就我个人而言,我使用自己的类建立在EntityManagerCriteriaBuilder和Metamodel之上,它提供动态过滤和多重排序功能。 但是这样的事情应该足够有意义:

protected static List<Order> buildOrderBy(CriteriaBuilder builder, Root<?> root, List<SortMeta> sortList)
{
    List<Order> orderList = new LinkedList<>();

    for(SortMeta sortMeta : sortList)
    {
        String sortField = sortMeta.getSortField();
        SortOrder sortOrder = sortMeta.getSortOrder();

        if(sortField == null || sortField.isEmpty() || sortOrder == null)
        {
            continue;
        }

        Expression<?> expr = getExpression(root, sortField);

        if(sortOrder == SortOrder.ASCENDING)
        {
            orderList.add(builder.asc(expr));
        }
        else if(sortOrder == SortOrder.DESCENDING)
        {
            orderList.add(builder.desc(expr));
        }
    }

    return orderList;
}

protected static Expression<?> getExpression(Root<?> root, String sortField)
{
    ManagedType<?> managedType = root.getModel();
    From<?, Object> from = (From<?, Object>) root;

    String[] elements = sortField.split("\\.");
    for(String element : elements)
    {
        Attribute<?, ?> attribute = managedType.getAttribute(element);
        if(attribute.getPersistentAttributeType() == PersistentAttributeType.BASIC)
        {
            return from.get(element);
        }

        from = from.join(element, JoinType.LEFT);
        managedType = EntityUtils.getManagedType(from.getJavaType());
    }

    return from;
}

这种方式的连接基于排序字段,现在是一个像“projectList.name”或“office.responsible.age”这样的虚线路径表达式

public static <X> ManagedType<X> getManagedType(Class<X> clazz)
{
    try
    {
        return getMetamodel().managedType(clazz);
    }
    catch(IllegalArgumentException e)
    {
        return null;
    }
}

public static Metamodel getMetamodel()
{
    return getEntityManagerFactory().getMetamodel();
}

public static EntityManagerFactory getEntityManagerFactory()
{
    try
    {
        return InitialContext.doLookup("java:module/persistence/EntityManagerFactory");
    }
    catch(NamingException e)
    {
        throw new RuntimeException(e.getMessage(), e);
    }
}

要使其在webapp上运行,您必须在web.xml上声明上下文引用:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">

    <display-name>my_app_name</display-name>

    ...

    <persistence-context-ref>
        <persistence-context-ref-name>java:module/persistence/EntityManager</persistence-context-ref-name>
        <persistence-unit-name>my_pu_name</persistence-unit-name>
    </persistence-context-ref>

    <persistence-unit-ref>
        <persistence-unit-ref-name>java:module/persistence/EntityManagerFactory</persistence-unit-ref-name>
        <persistence-unit-name>my_pu_name</persistence-unit-name>
    </persistence-unit-ref>
</web-app>

<强>更新

我不知道EclipseLink如何处理分组,但Hibernate在某些情况下也没有正确执行连接(实际上是连接条件)。

例如,当满足所有这些条件时:

  • 基于SINGLE_TABLE查询作为类层次结构(而不​​是叶子)一部分的实体(作为Root / From)
  • 加入/获取子类的属性
  • 通过属性名称(String)而不是Attribute
  • 加入/获取

要解决此问题,我总是将属性名称解析为属性并重新使用已经“走过”的联接(我说事情可能会变得复杂吗?):

public class MetaDescriptor extends BusinessObject implements Serializable, MemberEx, ColumnDescriptor
{
    private static final long serialVersionUID = 1L;

    @BusinessKey
    protected final Attribute<?, ?> attribute;

    @BusinessKey
    protected final MetaDescriptor parent;

    protected List<MetaDescriptor> childList;

    protected final Type<?> elementType;

    ...

    protected MetaDescriptor(Attribute<?, ?> attribute, MetaDescriptor parent)
    {
        this.attribute = attribute;
        this.parent = parent;

        if(attribute instanceof SingularAttribute)
        {
            SingularAttribute<?, ?> singularAttribute = (SingularAttribute<?, ?>) attribute;
            elementType = singularAttribute.getType();
        }
        else if(attribute instanceof PluralAttribute)
        {
            PluralAttribute<?, ?, ?> pluralAttribute = (PluralAttribute<?, ?, ?>) attribute;
            elementType = pluralAttribute.getElementType();
        }
        else
        {
            elementType = null;
        }
    }

    public static MetaDescriptor getDescriptor(ManagedType<?> managedType, String path)
    {
        return getDescriptor(managedType, path, null);
    }

    public static MetaDescriptor getDescriptor(From<?, ?> from, String path)
    {
        if(from instanceof Root)
        {
            return getDescriptor(((Root<?>) from).getModel(), path);
        }

        return getDescriptor(from.getJavaType(), path);
    }

    public static MetaDescriptor getDescriptor(Class<?> clazz, String path)
    {
        ManagedType<?> managedType = EntityUtils.getManagedType(clazz);
        if(managedType == null)
        {
            return null;
        }

        return getDescriptor(managedType, path);
    }

    private static MetaDescriptor getDescriptor(ManagedType<?> managedType, String path, MetaDescriptor parent)
    {
        if(path == null)
        {
            return null;
        }

        Entry<String, String> slice = StringUtilsEx.sliceBefore(path, '.');
        String attributeName = slice.getKey();

        Attribute<?, ?> attribute;
        if("class".equals(attributeName))
        {
            attribute = new ClassAttribute<>(managedType);
        }
        else
        {
            try
            {
                attribute = managedType.getAttribute(attributeName);
            }
            catch(IllegalArgumentException e)
            {
                Class<?> managedClass = managedType.getJavaType();

                // take only if it is unique
                attribute = StreamEx.of(EntityUtils.getMetamodel().getManagedTypes())
                    .filter(x -> managedClass.isAssignableFrom(x.getJavaType()))
                    .flatCollection(ManagedType::getDeclaredAttributes)
                    .filterBy(Attribute::getName, attributeName)
                    .limit(2)
                    .collect(Collectors.reducing((a, b) -> null))
                    .orElse(null);

                if(attribute == null)
                {
                    return null;
                }
            }
        }

        MetaDescriptor descriptor = new MetaDescriptor(attribute, parent);

        String remainingPath = slice.getValue();
        if(remainingPath.isEmpty())
        {
            return descriptor;
        }

        Type<?> elementType = descriptor.getElementType();
        if(elementType instanceof ManagedType)
        {
            return getDescriptor((ManagedType<?>) elementType, remainingPath, descriptor);
        }

        throw new IllegalArgumentException();
    }

    @Override
    public <T> Expression<T> getExpression(CriteriaBuilder builder, From<?, ?> from)
    {
        From<?, Object> parentFrom = getParentFrom(from);

        if(attribute instanceof ClassAttribute)
        {
            return (Expression<T>) parentFrom.type();
        }

        if(isSingular())
        {
            return parentFrom.get((SingularAttribute<Object, T>) attribute);
        }

        return getJoin(parentFrom, JoinType.LEFT);
    }

    private <X, T> From<X, T> getParentFrom(From<?, ?> from)
    {
        return OptionalEx.of(parent)
            .map(x -> x.getJoin(from, JoinType.LEFT))
            .select(From.class)
            .orElse(from);
    }

    public <X, T> Join<X, T> getJoin(From<?, ?> from, JoinType joinType)
    {
        From<?, X> parentFrom = getParentFrom(from);

        Join<X, T> join = (Join<X, T>) StreamEx.of(parentFrom.getJoins())
            .findAny(x -> Objects.equals(x.getAttribute(), attribute))
            .orElseGet(() -> buildJoin(parentFrom, joinType));

        return join;
    }

    private <X, T> Join<X, T> buildJoin(From<?, X> from, JoinType joinType)
    {
        if(isSingular())
        {
            return from.join((SingularAttribute<X, T>) attribute, joinType);
        }

        if(isMap())
        {
            return from.join((MapAttribute<X, ?, T>) attribute, joinType);
        }

        if(isSet())
        {
            return from.join((SetAttribute<X, T>) attribute, joinType);
        }

        if(isList())
        {
            return from.join((ListAttribute<X, T>) attribute, joinType);
        }

        if(isCollection())
        {
            return from.join((CollectionAttribute<X, T>) attribute, joinType);
        }

        throw new ImpossibleException();
    }

    public Order buildOrder(CriteriaBuilderEx builder, From<?, ?> from, SortOrder direction)
    {
        if(direction == null)
        {
            return null;
        }

        Expression<?> expr = getExpression(builder, from);

        return direction == SortOrder.ASCENDING ? builder.asc(expr) : builder.desc(expr);
    }
}

有了这个结构,我现在可以放心了:

public static List<Order> buildOrderList(CriteriaBuilderEx builder, From<?, ? extends Object> from, List<SortMeta> list)
{
    return StreamEx.of(list)
        .nonNull()
        .map(x -> buildOrder(builder, from, x.getSortField(), x.getSortOrder()))
        .nonNull()
        .toList();
}

public static Order buildOrder(CriteriaBuilderEx builder, From<?, ? extends Object> from, String path, SortOrder direction)
{
    if(path == null || path.isEmpty() || direction == null)
    {
        return null;
    }

    MetaDescriptor descriptor = MetaDescriptor.getDescriptor(from, path);
    if(descriptor == null)
    {
        return null;
    }

    return descriptor.buildOrder(builder, from, direction);
}

答案 1 :(得分:1)

如果sortField仅存在于一个实体中:

 try{
    path = root.get(sortField);       
 }catch (IllegalArgumentException e){
    path = joinProject.get(sortField);
 }

 criteriaQuery.orderBy(cb.asc(path));