Hibernate HQL Проекция проблема с множеством с использованием AliasToBeanResultTransformer
У меня проблема с Hibernate HQL Projection с использованием AliasToBeanResultTransformer, в основном результат, который я пытаюсь вернуть, не отображается должным образом в bean-компонент, вот ситуация:
HQL-запрос, который я использую, таков:
select entity.categoryTypes as categoryTypes from nz.co.doltech.ims.server.entities.IncidentEntity entity where (entity.id = :id105019)
Я хочу получить CategoryType
из IncidentEntity
на основе его отношений присоединения. Это прекрасно работает, когда я не пытаюсь использовать какой-либо трансформатор на нем. categoryTypes
является множеством, и преобразователь продолжает пытаться проверить типы параметров метода и терпит неудачу, потому что вместо поиска CategoryTypeEntity
он находит java.util.Set
как будто его пытается отобразить один CategoryTypeEntity
в categoryTypes
поле. Я бы подумал, что, поскольку это набор, он будет вытягивать данные как 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 в hibernates установлено значение true, в AliasToBeanResultTransformer я продолжаю получать результат проекции как ноль (тогда результат возвращается как ноль (или [ноль, ноль, ноль] в зависимости от того, сколько возвращено. Я думаю, это может быть ошибкой). Применительно к проблеме, когда я удаляю преобразователь результата, он возвращает 3 CategoryTypeEntites
как и ожидалось. Когда он добавлен, я получаю один CategoryTypeEntity, который обрабатывается в методе Transformers transformTuple. Действительно запутался в обоих этих вопросах.
Ура, Бен
1 ответ
Решите эту проблему, переписав класс AliasToBeanResultTransformer. Он не будет вставлен в коллекцию, если совпадают типы коллекций и совпадают универсальные типы коллекций. Я также нашел замечательный преобразователь вложенных бинов, созданный samiandoni, который позволит мне отображать значения вложенных проекций тоже:) Вот как я реализовал это для всех, у кого была такая же проблема:
@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;
}
}