Заполните дочерний компонент с помощью Transformers.aliasToBean в Hibernate
У меня есть следующая пара бобов:
Address {
String name;
String number;
String zipcode;
String town;
}
MyEntity {
Address address;
String value1;
String value2;
}
Я пытаюсь сделать следующий запрос Hibernate:
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));
Проблема:
Caused by: org.hibernate.PropertyNotFoundException: Could not find setter for address.name on class com.entities.MyEntity
Я понимаю, что Hibernate ищет что-то вроде:
public String getAddressName() {} // This should be in MyEntity
Вместо:
public String getName() {} // In my Address bean
Идеи о том, как я могу это исправить, не создавая новый боб?
Спасибо!
5 ответов
Я написал ResultTransformer, который может решить вашу проблему. Это имя AliasToBeanNestedResultTransformer, проверьте его на github.
Код в Github работает нормально, но есть изменения в import
для новых версий Hibernate. Это как следовать.
org.hibernate.property.PropertyAccessor
заменен наorg.hibernate.property.access.spi.PropertyAccess
а также
org.hibernate.property.PropertyAccessorFactory
заменен на org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl
Так что вам придется изменить код с
PropertyAccessor accessor = PropertyAccessorFactory.getPropertyAccessor("property");
accessor.getSetter(resultClass, (String)subclassToAlias.get(subclass).get(2)).set(root, subObject, null);
в
PropertyAccess propertyAccess = PropertyAccessStrategyBasicImpl.INSTANCE.buildPropertyAccess(resultClass, (String)subclassToAlias.get(subclass).get(2));
propertyAccess.getSetter().set(root, subObject, null);
Мое решение очень простое. Он не так чист, как правильный преобразователь результатов, но полезен, когда вам просто нужно быстро спроецировать несколько свойств.
Если вы получаете Could not find setter for address.name on class com.entities.MyEntity
Это не значит, что Hibernate ищет public String getAddressName() {}
, Вместо этого он ищет сеттер с невозможным именем "setAddress.name".
Вместо .add(Projections.property("address.name"),"address.name"))
введите правильное имя сеттера в качестве второго аргумента метода.add() следующим образом .add(Projections.property("address.name"),"addressName"))
Затем просто добавьте установщик в корневой объект "MyEntity": "setAddressName".
public void setAddressName(String addressName) {
this.address= (this.address==null) ? new Address() : address;
this.address.setName(addressName);
}
Недостаток в том, что он "загрязняет" ваш объект дополнительными методами.
Также размещено здесь.
AliasToBeanNestedResultTransformer не обрабатывает вложенные многоуровневые DTO, поэтому я переписал тот, который обрабатывает n-уровневые dtos.
Надеюсь это поможет.
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));
}
}
}
}
Попробуйте создать псевдоним как criterio.createAlias("address", "add");
а затем отредактируйте ваши свойства, чтобы быть как Arrays.asList("add.number","add.zipcode", "add.town")
,
Надеюсь это поможет.