【问题标题】:how to write Hibernate Criteria to take nested objects by Projection List?如何编写 Hibernate Criteria 以通过 Projection List 获取嵌套对象?
【发布时间】:2015-05-12 06:34:59
【问题描述】:

我想在 Hibernate Projection List 中获取嵌套对象值。我有具有 OneToMany 和 ManyToOne 关系的 Pojo 'Charge' 和 'Tariff' 类。

我的示例代码如下:

收费

private String id;              
private Tariff tariff;
private String name;

@OneToMany(cascade=   {CascadeType.ALL},fetch=FetchType.EAGER,mappedBy="charge")
public Tariff getTariff() {
    return tariff;
}
public void setTariff(Tariff tariff) {
    this.tariff = tariff;
}

关税

private String id;
private String amount;
private Charge charge;

@ManyToOne(cascade={CascadeType.PERSIST},fetch=FetchType.EAGER)
@JoinColumn(name="charge_id")
public Charge getCharge() {
    return charge;
}
public void setCharge(Charge charge) {
    this.charge = charge;
}

我想按收费模式从关税中获取金额值。

我写的 sql 条件是有效的。

SELECT 关税.amount,charge.name FROM charge,tariff WHERE charge.name LIKE 's%';

我尝试了以下标准。

Criteria cr = getSession().createCriteria(Charge.class,"charge")
    .createAlias("charge.tariff","tariff")
    .setProjection(Projections.projectionList()
    .add(Projections.property("chargeName"),"chargeName")
    .add(Projections.property("id"),"id")
    .add(Projections.property("tariff.amount"),"amount"))
    .add(Restrictions.like("chargeName", name+"%"))
    .setResultTransformer(Transformers.aliasToBean(Charge.class));
     return  cr.list(); 

我只是用 restclient 检查它返回 null 值。如何为此 sql Query 编写 Criteria ?

【问题讨论】:

    标签: java spring hibernate criteria hibernate-criteria


    【解决方案1】:

    我的解决方案非常基本。它不像正确的结果转换器那样干净,但是当您只需要对一些属性进行快速投影时,它很有用。

    而不是.add(Projections.property("tariff.amount"),"amount")) 输入.add(Projections.property("tariff.amount"),"tariffAmount"))

    然后,只需在您的根对象“setTariffAmount”上添加一个设置器。

    public void setTariffAmount(String tariffAmount) {
      this.tariff = (this.tariff==null) ? new Tariff() : tariff;
      tariff.setAmount(tariffAmount);
    }
    

    缺点是它用额外的方法“弄脏”了你的对象。

    【讨论】:

    • 我喜欢这个解决方案。注意:我还必须在实体开始工作之前实现 getter(Hibernate 5.1)。
    【解决方案2】:

    AliasToBeanNestedResultTransformer 不处理多级嵌套 DTO。这意味着,您将无法在自己的 DTO 中执行 company.employee.location 。

    这是我编写的用于处理多级嵌套 DTO 的 Transformer。您可以通过调用来使用它:

    criteria.setResultTransformer( AliasToBeanNestedMultiLevelResultTransformer(mappingBean));

    希望对你有帮助。

    public class AliasToBeanNestedMultiLevelResultTransformer extends AliasedTupleSubsetResultTransformer {
    
    private static final long serialVersionUID = -8047276133980128266L;
    
    public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
        return false;
    }
    
    private boolean initialized;
    private Class<?> resultClass;
    private Map<String,Class<?>> clazzMap = new HashMap<>();
    private Map<String,Setter> settersMap = new HashMap<>();
    
    public AliasToBeanNestedMultiLevelResultTransformer(Class<?> resultClass) {
        this.resultClass = resultClass;
    }
    
    public Object transformTuple(Object[] tuples, String[] aliases) {
    
        Map<String,Object> nestedObjectsMap = new HashMap<>();
    
        Object result;
        try {
            result = resultClass.newInstance();
    
            if (!initialized){
                initialized = true;
                initialize(aliases);
            }
    
            for (int a=0;a<aliases.length;a++){
    
                String alias = aliases[a];
                Object tuple = tuples[a];
    
                Object baseObject = result;
    
                int index = alias.lastIndexOf(".");
                if(index>0){
                    String basePath = alias.substring(0, index);
                    baseObject = nestedObjectsMap.get(basePath);
                    if (baseObject == null){
                        baseObject = clazzMap.get(basePath).newInstance();
                        nestedObjectsMap.put(basePath, baseObject);
                    }
                }
    
                settersMap.get(alias).set(baseObject, tuple,null);
    
            }
    
            for (Entry<String,Object> entry:nestedObjectsMap.entrySet()){
                Setter setter = settersMap.get(entry.getKey());
                if (entry.getKey().contains(".")){
    
                    int index = entry.getKey().lastIndexOf(".");
                    String basePath = entry.getKey().substring(0, index);
                    Object obj = nestedObjectsMap.get(basePath);
    
                    setter.set(obj, entry.getValue(), null);
                }
                else{
                    setter.set(result, entry.getValue(), null);
                }
            }
    
        }catch ( InstantiationException | IllegalAccessException e) {
            throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
        }
    
        return result;
    }
    
    
    private void initialize(String[] aliases) {
    
        PropertyAccessor propertyAccessor = new ChainedPropertyAccessor(
                new PropertyAccessor[] {
                        PropertyAccessorFactory.getPropertyAccessor( resultClass, null ),
                        PropertyAccessorFactory.getPropertyAccessor( "field" )
                }
        );
    
        for (int a=0;a<aliases.length;a++){
    
            String alias = aliases[a];
    
            Class<?> baseClass = resultClass;
    
            if (alias.contains(".")){
    
                String[] split = alias.split("\\.");
    
                StringBuffer res = new StringBuffer();
    
                for (int i=0;i<split.length;i++){
    
                    if (res.length()>0) res.append(".");
    
                    String item = split[i];
                    res.append(item);
    
                    String resString = res.toString();
    
                    if (i==split.length-1){
                        clazzMap.put(resString,baseClass);
                        settersMap.put(resString, propertyAccessor.getSetter(baseClass, item));
                        break;
                    }
    
                    Class<?> clazz = clazzMap.get(resString);
                    if (clazz==null){
                        clazz = propertyAccessor.getGetter(baseClass,item).getReturnType();
                        settersMap.put(resString, propertyAccessor.getSetter(baseClass, item));
                        clazzMap.put(resString,clazz);
                    }
                    baseClass = clazz;
                }
            }
            else{
                clazzMap.put(alias, resultClass);
                settersMap.put(alias, propertyAccessor.getSetter(resultClass, alias));
            }
    
        }
    
    }
    

    }

    【讨论】:

    【解决方案3】:

    我遇到过这种要求。我尝试使用Transformers.aliasToBean 将嵌套对象作为嵌套对象,但这是行不通的。默认情况下,Transformers.aliasToBean 没有选择嵌套对象作为嵌套对象的能力。

    你可以看看我的问题

    Using Projecions to fetch a particular column from child table

    要将嵌套对象作为嵌套对象,您需要一个能够执行此操作的自定义转换器。

    这是一个由 samiandoni 编写的自定义转换器

    https://github.com/samiandoni/AliasToBeanNestedResultTransformer

    来自该链接中提供的自述文件

    class Person {
      private Long id;
      private String name;
      private Car car;
      // getters and setters
    }
    
    class Car {
      private Long id;
      private String color;
      // getters and setters
    }
    
    List<Person> getPeople() {
      ProjectionList projections = Projections.projectionList()
        .add(Projections.id().as("id"))
        .add(Projections.property("name").as("name"))
        .add(Projections.property("c.id").as("car.id"))
        .add(Projections.property("c.color").as("car.color"));
    
      Criteria criteria = getCurrentSession().createCriteria(Person.class)
        .createAlias("car", "c")
        .setProjection(projections)
        .setResultTransformer(new AliasToBeanNestedResultTransformer(Person.class));
    
      return (List<Person>) criteria.list();
    }
    
    // each car of Person will be populated
    

    上面的转换器能够获取第一级嵌套对象作为嵌套对象,它不支持更深的嵌套对象。因此,经过一番挖掘,我发现了另一个自定义转换器,它能够将深层嵌套对象作为嵌套对象获取

    注意:

    作者: Miguel Resendiz

    import java.lang.reflect.Field;
    import java.lang.reflect.ParameterizedType;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    import org.hibernate.HibernateException;
    import org.hibernate.property.PropertyAccessor;
    import org.hibernate.property.PropertyAccessorFactory;
    import org.hibernate.property.Setter;
    import org.hibernate.transform.AliasToBeanResultTransformer;
    import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
    import org.hibernate.transform.ResultTransformer;
    
    /**
     * Help to transform alises with nested alises
     * 
     * @author Miguel Resendiz
     * 
     */
    public class AliasToBeanNestedResultTransformer extends
    AliasedTupleSubsetResultTransformer {
    
        private static final long serialVersionUID = -8047276133980128266L;
    
        private static final int TUPE_INDEX = 0;
        private static final int ALISES_INDEX = 1;
        private static final int FIELDNAME_INDEX = 2;
    
        private static final PropertyAccessor accessor = PropertyAccessorFactory
                .getPropertyAccessor("property");
    
        private final Class<?> resultClass;
    
        private Object[] entityTuples;
        private String[] entityAliases;
    
        private Map<String, Class<?>> fieldToClass = new HashMap<String, Class<?>>();
        private Map<String, List<?>> subEntities = new HashMap<String, List<?>>();
        private List<String> nestedAliases = new ArrayList<String>();
        private Map<String, Class<?>> listFields = new HashMap<String, Class<?>>();
    
        public boolean isTransformedValueATupleElement(String[] aliases,
                int tupleLength) {
            return false;
        }
    
        public AliasToBeanNestedResultTransformer(Class<?> resultClass) {
    
            this.resultClass = resultClass;
        }
    
        public Object transformTuple(Object[] tuple, String[] aliases) {
    
            handleSubEntities(tuple, aliases);
            cleanParams(tuple, aliases);
            ResultTransformer rootTransformer = new AliasToBeanResultTransformer(
                    resultClass);
            Object root = rootTransformer.transformTuple(entityTuples,
                    entityAliases);
    
            loadSubEntities(root);
    
            cleanMaps();
            return root;
        }
    
        private void handleSubEntities(Object[] tuple, String[] aliases)
                throws HibernateException {
            String fieldName = "";
            String aliasName = "";
            try {
                for (int i = 0; i < aliases.length; i++) {
                    String alias = aliases[i];
                    if (alias.contains(".")) {
    
                        String[] sp = alias.split("\\.");
                        StringBuilder aliasBuilder = new StringBuilder();
                        for (int j = 0; j < sp.length; j++) {
                            if (j == 0) {
                                fieldName = sp[j];
                            } else {
                                aliasBuilder.append(sp[j]);
                                aliasBuilder.append(".");
                            }
                        }
                        aliasName = aliasBuilder.substring(0,
                                aliasBuilder.length() - 1);
    
                        nestedAliases.add(alias);
                        manageEntities(fieldName, aliasName, tuple[i]);
                    }
                }
            } catch (NoSuchFieldException e) {
                throw new HibernateException("Could not instantiate resultclass: "
                        + resultClass.getName() + " for field name: " + fieldName
                        + " and alias name:" + aliasName);
            }
        }
    
        private Class<?> findClass(String fieldName) throws NoSuchFieldException,
        SecurityException {
            if (fieldToClass.containsKey(fieldName)) {
                return fieldToClass.get(fieldName);
            } else {
                Class<?> subclass = resultClass.getDeclaredField(fieldName)
                        .getType();
    
                if (subclass.equals(List.class) || subclass.equals(Set.class)) {
                    if (subclass.equals(List.class)) {
                        listFields.put(fieldName, LinkedList.class);
                    } else {
                        listFields.put(fieldName, HashSet.class);
                    }
                    Field field = resultClass.getDeclaredField(fieldName);
                    ParameterizedType genericType = (ParameterizedType) field
                            .getGenericType();
                    subclass = (Class<?>) genericType.getActualTypeArguments()[0];
    
                }
                fieldToClass.put(fieldName, subclass);
                return subclass;
            }
        }
    
        @SuppressWarnings("unchecked")
        private void manageEntities(String fieldName, String aliasName,
                Object tupleValue) throws NoSuchFieldException, SecurityException {
            Class<?> subclass = findClass(fieldName);
            if (!subEntities.containsKey(fieldName)) {
                List<Object> list = new ArrayList<Object>();
                list.add(new ArrayList<Object>());
                list.add(new ArrayList<String>());
                list.add(FIELDNAME_INDEX, subclass);
                subEntities.put(fieldName, list);
            }
            ((List<Object>) subEntities.get(fieldName).get(TUPE_INDEX))
            .add(tupleValue);
            ((List<String>) subEntities.get(fieldName).get(ALISES_INDEX))
            .add(aliasName);
        }
    
        private void cleanParams(Object[] tuple, String[] aliases) {
            entityTuples = new Object[aliases.length - nestedAliases.size()];
            entityAliases = new String[aliases.length - nestedAliases.size()];
    
            for (int j = 0, i = 0; j < aliases.length; j++) {
                if (!nestedAliases.contains(aliases[j])) {
                    entityTuples[i] = tuple[j];
                    entityAliases[i] = aliases[j];
                    ++i;
                }
            }
        }
    
        @SuppressWarnings({ "unchecked", "rawtypes" })
        private void loadSubEntities(Object root) throws HibernateException {
            try {
                for (String fieldName : subEntities.keySet()) {
                    Class<?> subclass = (Class<?>) subEntities.get(fieldName).get(
                            FIELDNAME_INDEX);
    
                    ResultTransformer subclassTransformer = new AliasToBeanNestedResultTransformer(
                            subclass);
    
                    Object subObject = subclassTransformer.transformTuple(
                            ((List<Object>) subEntities.get(fieldName).get(0))
                            .toArray(),
                            ((List<Object>) subEntities.get(fieldName).get(1))
                            .toArray(new String[0]));
    
                    Setter setter = accessor.getSetter(resultClass, fieldName);
                    if (listFields.containsKey(fieldName)) {
                        Class<?> collectionClass = listFields.get(fieldName);
                        Collection subObjectList = (Collection) collectionClass
                                .newInstance();
                        subObjectList.add(subObject);
                        setter.set(root, subObjectList, null);
                    } else {
                        setter.set(root, subObject, null);
                    }
                }
            } catch (Exception e) {
                throw new HibernateException(e);
            }
        }
    
        private void cleanMaps() {
            fieldToClass = new HashMap<String, Class<?>>();
            subEntities = new HashMap<String, List<?>>();
            nestedAliases = new ArrayList<String>();
            listFields = new HashMap<String, Class<?>>();
        }
    
    }
    

    只需将 samiandoni 的 Transformer 替换为上述变压器即可。它能够获取更深的嵌套对象作为各自的对象。

    【讨论】:

    • 如果我在 person 类中有一个汽车对象列表,仍然可以使用 AliasToBeanNestedResultTransformer?
    • @hadi,是和否。您仍然可以将AliasToBeanNestedResultTransformer 用于OneToMany (aka Collections),但结果将是individual parent row for every child object。因此,您将无法将其作为父对象内的集合获取,而只能作为每个子行的单独父对象获取。我也在找可以支持OneToMany (aka Collections)AliasToBeanNestedResultTransformer
    • 对于任何可能偶然发现此答案的人,Miguel Resendiz 的原始答案已更新以适应 OneToMany 案例。在这里查看 - link
    猜你喜欢
    • 1970-01-01
    • 2015-12-12
    • 2020-07-03
    • 1970-01-01
    • 1970-01-01
    • 2016-04-01
    • 1970-01-01
    • 2021-11-30
    • 2011-05-11
    相关资源
    最近更新 更多