Использование CompositeUserType для связи сущностей в Hibernate

от автора

Пост будет кратким и весьма техническим.

Задача

Есть Java-приложение, имеющее внутри большое количество ORM-сущностей (Entity).
Необходимо реализовать сущность ExtendedAttributes, которую можно прикрепить к любой другой сущности без дополнительной доработки.

Решение

На помощь к нам приходит CompositeUserType, который содержит внутри себя class и id той сущности, которую мы хотим привязать. Вот и всё решение. А дальше — код.

Для сокращения поста импорты, геттеры, сеттеры и методы «по-умолчанию» из классов вырезаны.

ExtendedAttributes.class — именно его мы и реализуем в рамках данной задачи

@Entity @Table(name = "extattributes") @TypeDef(name = "entityType", typeClass = hibernate.GenericEntityType.class) public class ExtendedAttributes extends GenericEntity { 	private static final long serialVersionUID = 1L; 	@Id 	private Long id; 	@Type(type = "entityType") 	@Columns(columns = { @Column(name = "object_id"), 			@Column(name = "object_class") }) 	private GenericEntity object; 	private String property; 	private String value;  	@Override 	public Class<?> getType() { 		return ExtendedAttributes.class; 	}         @Override 	public Long getId() { 		return id; 	}  } 

GenericEntity.class — его наследуют все сущности в данном приложении.

public abstract class GenericEntity implements Serializable { 	private static final long serialVersionUID = 1L; 	public abstract Serializable getId(); 	public abstract Class<?> getType(); } 

EntityWrapper.class — обертка для несуществующих сущностей.

public class EntityWrapper extends GenericEntity { 	private static final long serialVersionUID = 1L; 	private String name;  	public EntityWrapper(String name) { 		super(); 		this.name = name; 	}  	@Override 	public Serializable getId() { 		return name; 	}  	@Override 	public Class<?> getType() { 		return EntityWrapper.class; 	}  } 

GenericEntityType.class — а вот и тот самый тип. Состоит из двух String-полей, возвращает GenericEntity.
determineIdType() — единственный настоящий костыль, но чем заменить я пока не нашел.
Id других типов в приложении нет.

public class GenericEntityType implements CompositeUserType { 	private static final Type[] SQL_TYPES = { StringType.INSTANCE, 			StringType.INSTANCE }; 	private static final String[] SQL_NAMES = { "id", "class" };  	@Override 	public String[] getPropertyNames() { 		return SQL_NAMES; 	}  	@Override 	public Type[] getPropertyTypes() { 		return SQL_TYPES; 	}  	@Override 	public Class<?> returnedClass() { 		return GenericEntity.class; 	}  	@Override 	public boolean equals(Object o1, Object o2) throws HibernateException { 		if (o1 == o2) 			return true; 		if (o1 != null && o2 != null) 			return o1.equals(o2); 		return false; 	}  	@Override 	public Object getPropertyValue(Object object, int index) 			throws HibernateException { 		GenericEntity dto = (GenericEntity) object; 		if (index == 0) { 			return dto.getId(); 		} else if (index == 1) { 			return dto.getType(); 		} else { 			throw new HibernateException("Unknown index [ " + index + " ]"); 		} 	}  	@Override 	public int hashCode(Object object) throws HibernateException { 		return (object != null) ? object.hashCode() : 0; 	}  	@Override 	public boolean isMutable() { 		return false; 	}  	@Override 	public Object nullSafeGet(ResultSet rs, String[] names, 			SessionImplementor session, Object owner) 			throws HibernateException, SQLException { 		if (names.length == 2) { 			String id = (String) StringType.INSTANCE.get(rs, names[0], session); 			String clazz = (String) StringType.INSTANCE.get(rs, names[1], 					session); 			if (id != null && clazz != null) { 				try { 					Class.forName(clazz); 					return session.immediateLoad(clazz, determineIdType(id)); 				} catch (ClassNotFoundException e) { 					return new EntityWrapper(id); 				} 			} 		} 		return null; 	}  	@Override 	public void nullSafeSet(PreparedStatement st, Object value, int index, 			SessionImplementor session) throws HibernateException, SQLException { 		if (value == null) { 			StringType.INSTANCE.set(st, null, index, session); 			ClassType.INSTANCE.set(st, null, index + 1, session); 		} else { 			final GenericEntity dto = (GenericEntity) value; 			StringType.INSTANCE.set(st, dto.getId().toString(), index, session); 			ClassType.INSTANCE.set(st, dto.getType(), index + 1, session); 		}  	}  	/** 	 * Автоопределение типа. Long/String/Double 	 *  	 * @param id 	 * @return 	 */ 	private Serializable determineIdType(String id) { 		try { 			if (id.matches("^\\d+$")) { 				return Long.valueOf(id); 			} else if (id.matches("^\\d+[\\.,]\\d+$")) { 				return Double.valueOf(id); 			} else { 				return id; 			} 		} catch (NumberFormatException e) { 			return id; 		} 	} 

А теперь — как получить:

	public static Criterion criterionForEntity(String field, GenericEntity object) { 		String id = ""; 		if (object.getId() != null) { 			id = object.getId().toString(); 		} 		return Restrictions.and(Restrictions.eq(field + ".id", id), 				Restrictions.eq(field + ".class", object.getType() 						.getCanonicalName())); 	} 

Спасибо за внимание. Надеюсь, это вам пригодится 🙂

ссылка на оригинал статьи http://habrahabr.ru/post/158547/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *