Пост будет кратким и весьма техническим.
Задача
Есть 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()));
}
Спасибо за внимание. Надеюсь, это вам пригодится :)
Автор: kreon