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

в 20:28, , рубрики: dirty hack, hibernate, java, метки: , ,

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

Задача

Есть 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js