使用 AliasToBeanResultTransformer 设置的休眠 HQL 投影问题
Posted
技术标签:
【中文标题】使用 AliasToBeanResultTransformer 设置的休眠 HQL 投影问题【英文标题】:Hibernate HQL Projection Issue with Set Using AliasToBeanResultTransformer 【发布时间】:2014-06-26 13:19:59 【问题描述】:我在使用AliasToBeanResultTransformer 时遇到了Hibernate HQL Projection 的问题,基本上我试图返回的结果没有正确映射到bean,情况如下:
我使用的 HQL 查询是这样的:
select entity.categoryTypes as categoryTypes from nz.co.doltech.ims.server.entities.IncidentEntity entity where (entity.id = :id105019)
我想根据其连接关系从IncidentEntity
中获取CategoryType
。当我不尝试在其上使用任何变压器时,这可以正常工作。 categoryTypes
是一个 Set 并且转换器不断尝试检查方法的参数类型并失败,因为它没有找到 CategoryTypeEntity
,而是找到了 java.util.Set
,就好像它试图将单个 CategoryTypeEntity
映射到 categoryTypes
字段一样.我会认为因为它是一个 Set ,所以它会将数据作为Set
提取出来,然后尝试将其映射到categoryTypes
字段。显然不是。
@javax.persistence.Entity(name = "incidents")
@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
public class IncidentEntity implements Entity
...
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "incident_categorytype", joinColumns =
@JoinColumn(name = "incident_id", nullable = false, updatable = false) ,
inverseJoinColumns =
@JoinColumn(name = "categorytype_id", nullable = false, updatable = false)
)
private Set<CategoryTypeEntity> categoryTypes = new HashSet<CategoryTypeEntity>();
...
public Set<CategoryTypeEntity> getCategoryTypes()
return categoryTypes;
public void setCategoryTypes(Set<CategoryTypeEntity> categoryTypes)
this.categoryTypes = categoryTypes;
这是我打的电话:
Query query = session.createQuery("select entity.categoryTypes as categoryTypes from nz.co.doltech.ims.server.entities.IncidentEntity entity " +
"where (entity.id = :id105019)")
query.setResultTransformer(Transformers.aliasToBean(IncidentEntity.class));
return query.list();
我得到的例外是:
Caused by: org.hibernate.PropertyAccessException: IllegalArgumentException occurred while calling setter of nz.co.doltech.ims.server.entities.IncidentEntity.categoryTypes
...
Caused by: java.lang.IllegalArgumentException: argument type mismatch
而休眠日志消息是:
Jun 27, 2014 12:32:11 AM org.hibernate.property.BasicPropertyAccessor$BasicSetter set
SEVERE: IllegalArgumentException in class: nz.co.doltech.ims.server.entities.IncidentEntity, setter method of property: categoryTypes
Jun 27, 2014 12:32:11 AM org.hibernate.property.BasicPropertyAccessor$BasicSetter set
SEVERE: expected type: java.util.Set, actual value: nz.co.doltech.ims.server.entities.CategoryTypeEntity
使用 Hibernate 3.6.10
谁能看到这里发生了什么?这看起来真的不像正常行为,也许我做错了什么。希望能得到任何帮助!
更新:这很奇怪,与问题没有直接关系。当我将休眠 use_query_cache 属性设置为 true 时,我在 AliasToBeanResultTransformer 中不断将投影结果设为 null (然后结果返回为 null (或 [null, null, null] 取决于返回的数量。我认为这可能是一个错误? 关于手头的问题,当我删除结果转换器时,它按预期返回 3 CategoryTypeEntites
。添加后,我得到一个 CategoryTypeEntity 正在 Transformers 的 transformTuple 方法中处理。对这两个问题都感到困惑。
干杯, 本
【问题讨论】:
【参考方案1】:设法通过重写 AliasToBeanResultTransformer 类来解决此问题。如果集合类型匹配并且集合泛型类型匹配,它将不会插入到集合中。我还发现了 samiandoni 制作的一个很棒的嵌套 bean 转换器,它也允许我映射嵌套投影值 :) 以下是我为遇到同样问题的其他人实现它的方法:
@SuppressWarnings("serial","rawtypes")
public class AliasToBeanResultTransformer implements ResultTransformer, Serializable
// IMPL NOTE : due to the delayed population of setters (setters cached
// for performance), we really cannot properly define equality for
// this transformer
private final Class resultClass;
private boolean isInitialized;
private String[] aliases;
private Setter[] setters;
private Getter[] getters;
public AliasToBeanResultTransformer(Class resultClass)
if ( resultClass == null )
throw new IllegalArgumentException( "resultClass cannot be null" );
isInitialized = false;
this.resultClass = resultClass;
@Override
public Object transformTuple(Object[] tuple, String[] aliases)
Object result;
try
if ( ! isInitialized )
initialize( aliases );
else
check( aliases );
result = resultClass.newInstance();
for ( int i = 0; i < aliases.length; i++ )
Setter setter = setters[i];
if ( setter != null )
Class paramType = setter.getMethod().getParameterTypes()[0];
if(paramType != null)
Object obj = tuple[i];
// Check if parameter is a collection
if(!obj.getClass().equals(paramType) && isCollection(paramType))
insertToList(result, obj, getters[i], aliases[i]);
else
setter.set( result, obj, null );
catch ( InstantiationException e )
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
catch ( IllegalAccessException e )
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
return result;
@Override
public List transformList(List collection)
return collection;
@SuppressWarnings("unchecked")
private boolean insertToList(Object result, Object obj, Getter getter, String alias)
Class genClass;
try
genClass = ReflectUtils.getGenericType(resultClass.getDeclaredField(alias));
// Check if the collection can take the obj
if(genClass.equals(obj.getClass()))
Collection collection = (Collection) getter.get(result);
collection.add(obj);
return true;
catch (NoSuchFieldException | SecurityException e)
return false;
private void initialize(String[] aliases)
PropertyAccessor propertyAccessor = new ChainedPropertyAccessor(
new PropertyAccessor[]
PropertyAccessorFactory.getPropertyAccessor( resultClass, null ),
PropertyAccessorFactory.getPropertyAccessor( "field" )
);
this.aliases = new String[ aliases.length ];
setters = new Setter[ aliases.length ];
getters = new Getter[ aliases.length ];
for ( int i = 0; i < aliases.length; i++ )
String alias = aliases[ i ];
if ( alias != null )
this.aliases[ i ] = alias;
setters[ i ] = propertyAccessor.getSetter( resultClass, alias );
getters[ i ] = propertyAccessor.getGetter( resultClass, alias );
isInitialized = true;
private void check(String[] aliases)
if ( ! Arrays.equals( aliases, this.aliases ) )
throw new IllegalStateException(
"aliases are different from what is cached; aliases=" + Arrays.asList( aliases ) +
" cached=" + Arrays.asList( this.aliases ) );
private boolean isCollection(Class clazz)
return Collection.class.isAssignableFrom(clazz);
public boolean equals(Object o)
if ( this == o )
return true;
if ( o == null || getClass() != o.getClass() )
return false;
AliasToBeanResultTransformer that = ( AliasToBeanResultTransformer ) o;
if ( ! resultClass.equals( that.resultClass ) )
return false;
if ( ! Arrays.equals( aliases, that.aliases ) )
return false;
return true;
public int hashCode()
int result = resultClass.hashCode();
result = 31 * result + ( aliases != null ? Arrays.hashCode( aliases ) : 0 );
return result;
你也需要实现这个 RefectUtil 方法:
public static Class<?> getGenericType(Field field)
ParameterizedType type = (ParameterizedType) field.getGenericType();
return (Class<?>) type.getActualTypeArguments()[0];
然后你也可以让它与 samiandoni 的转换器一起工作(只要确保它使用你编辑的 AliasToBeanResultTransformer 类):
/**
* @author samiandoni
*
*/
@SuppressWarnings("rawtypes")
public class AliasToBeanNestedResultTransformer implements ResultTransformer, Serializable
private static final long serialVersionUID = -8047276133980128266L;
private final Class<?> resultClass;
public AliasToBeanNestedResultTransformer(Class<?> resultClass)
this.resultClass = resultClass;
@SuppressWarnings("unchecked")
public Object transformTuple(Object[] tuple, String[] aliases)
Map<Class<?>, List<?>> subclassToAlias = new HashMap<Class<?>, List<?>>();
List<String> nestedAliases = new ArrayList<String>();
try
for (int i = 0; i < aliases.length; i++)
String alias = aliases[i];
if (alias.contains("."))
nestedAliases.add(alias);
String[] sp = alias.split("\\.");
String fieldName = sp[0];
String aliasName = sp[1];
Class<?> subclass = resultClass.getDeclaredField(fieldName).getType();
if (!subclassToAlias.containsKey(subclass))
List<Object> list = new ArrayList<Object>();
list.add(new ArrayList<Object>());
list.add(new ArrayList<String>());
list.add(fieldName);
subclassToAlias.put(subclass, list);
((List<Object>)subclassToAlias.get(subclass).get(0)).add(tuple[i]);
((List<String>)subclassToAlias.get(subclass).get(1)).add(aliasName);
catch (NoSuchFieldException e)
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
Object[] newTuple = new Object[aliases.length - nestedAliases.size()];
String[] newAliases = new String[aliases.length - nestedAliases.size()];
int i = 0;
for (int j = 0; j < aliases.length; j++)
if (!nestedAliases.contains(aliases[j]))
newTuple[i] = tuple[j];
newAliases[i] = aliases[j];
++i;
ResultTransformer rootTransformer = new AliasToBeanResultTransformer(resultClass);
Object root = rootTransformer.transformTuple(newTuple, newAliases);
for (Class<?> subclass : subclassToAlias.keySet())
ResultTransformer subclassTransformer = new AliasToBeanResultTransformer(subclass);
Object subObject = subclassTransformer.transformTuple(
((List<Object>)subclassToAlias.get(subclass).get(0)).toArray(),
((List<Object>)subclassToAlias.get(subclass).get(1)).toArray(new String[0])
);
PropertyAccessor accessor = PropertyAccessorFactory.getPropertyAccessor("property");
accessor.getSetter(resultClass, (String)subclassToAlias.get(subclass).get(2)).set(root, subObject, null);
return root;
@Override
public List transformList(List collection)
return collection;
【讨论】:
以上是关于使用 AliasToBeanResultTransformer 设置的休眠 HQL 投影问题的主要内容,如果未能解决你的问题,请参考以下文章
在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?
Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)