7

I have the next couple of beans:

Address {
    String name;
    String number;
    String zipcode;
    String town;
}

MyEntity {
    Address address;
    String value1;
    String value2;
}

I'm trying to do the next Hibernate query:

private final List<String> propertiesDistinct = Arrays.asList("address.name");
private final List<String> properties = Arrays.asList("address.number",
        "address.zipcode", "address.town")

ProjectionList projectionList = Projections.projectionList();

if (propertiesDistinct != null) {
    ProjectionList projectionListDistinct = Projections.projectionList();
for (String propertyDistinct : propertiesDistinct)
         projectionListDistinct.add(Projections.property(propertyDistinct).as(propertyDistinct));

    projectionList.add(Projections.distinct(projectionListAgrupar));
}

if (properties != null)
    for (String property : properties)
         projectionList.add(Projections.property(property).as(property));
criterio.setProjection(projectionList);

// MORE FILTERS ON MyEntity FIELDS
//... criterio.add(Restrinctions...);

// I want to recover the results on my bean MyEntity so I don't have to create a new one
criterio.setResultTransformer(Transformers.aliasToBean(MyEntity.class));

Problem:

Caused by: org.hibernate.PropertyNotFoundException: Could not find setter for address.name on class com.entities.MyEntity

I understand that Hibernate is looking for something like:

public String getAddressName() {} // This should be in MyEntity

Instead of:

public String getName() {} // In my Address bean

Ideas about how can I fix this without creating a new bean?

Thanks!

maqjav
  • 2,310
  • 3
  • 23
  • 35

5 Answers5

20

I wrote a ResultTransformer that can fix your problem. It's name is AliasToBeanNestedResultTransformer, check it out on github.

Sami Andoni
  • 694
  • 6
  • 7
3

Code provided in Github works fine but there is change in import for new versions of hibernate. Its as follow.

org.hibernate.property.PropertyAccessor replaced byorg.hibernate.property.access.spi.PropertyAccess

and

org.hibernate.property.PropertyAccessorFactory replaced by org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl

So you'll have change the code from

PropertyAccessor accessor = PropertyAccessorFactory.getPropertyAccessor("property");
accessor.getSetter(resultClass, (String)subclassToAlias.get(subclass).get(2)).set(root, subObject, null);

to

PropertyAccess propertyAccess = PropertyAccessStrategyBasicImpl.INSTANCE.buildPropertyAccess(resultClass, (String)subclassToAlias.get(subclass).get(2));
propertyAccess.getSetter().set(root, subObject, null);
Vicky Thakor
  • 3,847
  • 7
  • 42
  • 67
2

AliasToBeanNestedResultTransformer does not handle Nested Multi Level DTOs, so I rewrote one that handles n-level dtos.

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

    }

}

}

Ronny Shibley
  • 2,030
  • 22
  • 25
2

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.

If you get Could not find setter for address.name on class com.entities.MyEntity

It doesn't mean Hibernate is looking for public String getAddressName() {}. Instead it looks for a setter with the impossible "setAddress.name" name.

Instead of .add(Projections.property("address.name"),"address.name")) type a proper setter name as second argument to the .add() method as follows .add(Projections.property("address.name"),"addressName"))

Then, just add a setter on your "MyEntity" root object: "setAddressName".

public void setAddressName(String addressName) {
  this.address= (this.address==null) ? new Address() : address;
  this.address.setName(addressName);
}

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

Also posted here.

Olivier Tonglet
  • 3,312
  • 24
  • 40
  • I tried this approach and it fails with: `javax.persistence.PersistenceException: org.hibernate.PropertyNotFoundException: Could not resolve PropertyAccess for addressName on class `. It works if I also define a `public String getAddressName()`, but I do not want to have the getter. I'm using Hibernate 5.3.18. – Dinei Jun 24 '21 at 15:09
1

Try creating an alias like criterio.createAlias("address", "add"); and then edit your properties to be like Arrays.asList("add.number","add.zipcode", "add.town").

Hope this helps.

Ean V
  • 5,091
  • 5
  • 31
  • 39
  • I tried your solution but I got the next exception: `org.hibernate.QueryException: Criteria objects cannot be created directly on components. Create a criteria on owning entity and use a dotted property to access component property: address` – maqjav Sep 10 '13 at 08:20
  • Try `createCriteria` instead of `createAlias`. I can't test myself. – Ean V Sep 10 '13 at 08:30
  • Are you adding your projections to new criteria? Note that `createCriteria` returns an instance of `Criteria`. You should set your projections to this new one. – Ean V Sep 10 '13 at 08:52
  • 1
    Ean thanks for your help, I sholved it with Sami Andoni transformer. Thank you anyway for your time :) – maqjav Sep 11 '13 at 05:56