3.5.3.4.5. 自定义排序

UI table 中按照实体属性排序的功能是通过 CollectionContainerSorter 实现的,需要为 CollectionContainer 设置该排序器。标准的实现是,如果数据在一页以内能显示,则在内存做数据排序,否则会使用合适的 "order by" 语句发送数据库的请求。"order by" 语句是中间层的 JpqlSortExpressionProvider bean创建的。

有些实体属性需要一个特殊的排序实现。下面我们用个例子解释一下如何自定义排序:假设有 Foo 实体带有 String 类型的 number 属性,但是我们知道该属性其实是只保存数字。所以我们希望排序的顺序是 1, 2, 3, 10, 11。但是,默认的排序行为会产生这样的结果:1, 10, 11, 2, 3

首先,在 web 模块创建一个 CollectionContainerSorter 类的子类,在内存进行排序:

package com.company.demo.web;

import com.company.demo.entity.Foo;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.Sort;
import com.haulmont.cuba.gui.model.BaseCollectionLoader;
import com.haulmont.cuba.gui.model.CollectionContainer;
import com.haulmont.cuba.gui.model.impl.CollectionContainerSorter;
import com.haulmont.cuba.gui.model.impl.EntityValuesComparator;

import javax.annotation.Nullable;
import java.util.Comparator;
import java.util.Objects;

public class CustomCollectionContainerSorter extends CollectionContainerSorter {

    public CustomCollectionContainerSorter(CollectionContainer container,
                                           @Nullable BaseCollectionLoader loader) {
        super(container, loader);
    }

    @Override
    protected Comparator<? extends Entity> createComparator(Sort sort, MetaClass metaClass) {
        MetaPropertyPath metaPropertyPath = Objects.requireNonNull(
                metaClass.getPropertyPath(sort.getOrders().get(0).getProperty()));

        if (metaPropertyPath.getMetaClass().getJavaClass().equals(Foo.class)
                && "number".equals(metaPropertyPath.toPathString())) {
            boolean isAsc = sort.getOrders().get(0).getDirection() == Sort.Direction.ASC;
            return Comparator.comparing(
                    (Foo e) -> e.getNumber() == null ? null : Integer.valueOf(e.getNumber()),
                    EntityValuesComparator.asc(isAsc));
        }
        return super.createComparator(sort, metaClass);
    }
}

如果有几个界面需要这个自定义的排序,可以在界面中实例化 CustomCollectionContainerSorter

public class FooBrowse extends StandardLookup<Foo> {

    @Inject
    private CollectionContainer<Foo> fooDc;
    @Inject
    private CollectionLoader<Foo> fooDl;

    @Subscribe
    private void onInit(InitEvent event) {
        CustomCollectionContainerSorter sorter = new CustomCollectionContainerSorter(fooDc, fooDl);
        fooDc.setSorter(sorter);
    }
}

如果排序器定义了一些全局的行为,则可以创建自定义的工厂在系统级别实例化该排序器:

package com.company.demo.web;

import com.haulmont.cuba.gui.model.*;
import javax.annotation.Nullable;

public class CustomSorterFactory extends SorterFactory {

    @Override
    public Sorter createCollectionContainerSorter(CollectionContainer container,
                                                  @Nullable BaseCollectionLoader loader) {
        return new CustomCollectionContainerSorter(container, loader);
    }
}

然后在 web-spring.xml 注册该工厂以替换默认工厂:

<bean id="cuba_SorterFactory" class="com.company.demo.web.CustomSorterFactory"/>

现在我们在 core 模块为数据库级别的排序创建我们自己定义的 JpqlSortExpressionProvider 实现:

package com.company.demo.core;

import com.company.demo.entity.Foo;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.cuba.core.app.DefaultJpqlSortExpressionProvider;

public class CustomSortExpressionProvider extends DefaultJpqlSortExpressionProvider {

    @Override
    public String getDatatypeSortExpression(MetaPropertyPath metaPropertyPath, boolean sortDirectionAsc) {
        if (metaPropertyPath.getMetaClass().getJavaClass().equals(Foo.class)
                && "number".equals(metaPropertyPath.toPathString())) {
            return String.format("CAST({E}.%s BIGINT)", metaPropertyPath.toString());
        }
        return String.format("{E}.%s", metaPropertyPath.toString());
    }
}

spring.xml 注册此 expression provider,覆盖默认:

<bean id="cuba_JpqlSortExpressionProvider" class="com.company.demo.core.CustomSortExpressionProvider"/>