如何编写Hibernate Criteria以通过Projection List获取嵌套对象?

时间:2015-03-10 17:25:01

标签: java spring hibernate criteria hibernate-criteria

我想在Hibernate Projection List中获取嵌套对象值。我在OneToMany和ManyToOne关系中拥有Pojo'Pinme'和'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 tariff.amount,charge.name FROM charge,tariff WHERE   charge.name LIKE的%';

我尝试了以下标准。

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查询编写Criteria?

3 个答案:

答案 0 :(得分:12)

我经历过这种要求。我试图使用Transformers.aliasToBean将嵌套对象作为嵌套对象,这将无法正常工作。默认情况下,Transformers.aliasToBean无法选择嵌套对象作为嵌套对象。

您可以查看我的问题

Using Projecions to fetch a particular column from child table

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

这是由samiandoni撰写的Custom Transformer

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的变压器。它能够将更深的嵌套对象作为相应的对象获取。

答案 1 :(得分:2)

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

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

然后,只需在根对象“setTariffAmount”上添加一个setter。

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

缺点是它用额外的方法“污染”你的物体。

答案 2 :(得分:1)

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));
        }

    }

}

}