how to write Hibernate Criteria to take nested objects by Projection List?
Asked Answered
C

3

11

I want to take Nested object values in Hibernate Projection List. I having Pojo 'Charge' and 'Tariff' class with OneToMany and ManyToOne relations.

My sample code is as following:

Charge

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

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

I want to take amount value from tariff by charge model.

I write sql criteria that works ie.

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

and i tried with following criteria.

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

I just check with restclient it returns null Value. How to write for Criteria for this sql Query ?

Coze answered 10/3, 2015 at 17:25 Comment(0)
J
14

I've experienced this kind of requirement. I tried to get nested objects as nested objects using Transformers.aliasToBean, which will not work. By default, Transformers.aliasToBean don't have the capability to select nested object as nested object.

You can take a look at my question

Using Projecions to fetch a particular column from child table

To get Nested object as nested object, you need a Custom Transformer which is capable of doing that.

Here's a Custom Transformer written by samiandoni

https://github.com/samiandoni/AliasToBeanNestedResultTransformer

From the provided Readme in that link

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

The above transformer is capable of Fetching first level Nested object as Nested object and it doesn't support further deep nested objects. So after some digging I've found another Custom transformer which is capable of Fetching deep Nested objects as Nested objects

Note:

Author: 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<?>>();
    }

}

Just replace samiandoni's Transformer with the above transformer. It's capable of fetching further deep Nested Objects as respective Objects.

Jasik answered 10/3, 2015 at 17:38 Comment(3)
If i have a list of car objects in person class can still be used AliasToBeanNestedResultTransformer?Influential
@hadi, Yes and No. You can still use AliasToBeanNestedResultTransformer for OneToMany (aka Collections), but the result would be individual parent row for every child object. So you won't be able to fetch it as collections inside parent object, only as seperate parent object for every child row. I'm also looking for AliasToBeanNestedResultTransformer which can support OneToMany (aka Collections)Jasik
For anyone who might stumble across this answer, Miguel Resendiz's original answer has been updated to accommodate OneToMany cases as well. Check it out here - linkExplorer
G
3

My solution is very basic. It's not as clean as a proper result transformer but it's useful when you just need to do a quick projection for a few properties.

Instead of .add(Projections.property("tariff.amount"),"amount")) type .add(Projections.property("tariff.amount"),"tariffAmount"))

Then, just add a setter on your root object "setTariffAmount".

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

The drawback is that it "dirties" your object with extra methods.

Grind answered 26/7, 2017 at 8:15 Comment(1)
I liked this solution. Note: I had to implement also getter to the entity before it started working (Hibernate 5.1).Photoconductivity
L
2

The AliasToBeanNestedResultTransformer does not handle Multilevel Nested DTO's. Meaning, you won't be able to do company.employee.location each in its own DTO.

Here is a Transformer I wrote that handles Multilevel Nested DTOs. You may used it by calling:

criteria.setResultTransformer( AliasToBeanNestedMultiLevelResultTransformer(mappingBean));

Hope it helps.

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

    }

}

}

Lucretialucretius answered 2/5, 2016 at 14:19 Comment(1)
Checkout this thread as well: #18713735Lucretialucretius

© 2022 - 2024 — McMap. All rights reserved.