Hibernate: Join a collection of components with Criteria queries

Q: I’m getting ‘org.hibernate.MappingException: collection was not an association’ when I try to join a collection of components with Criteria queries
A: Hibernate currently does not support joining a collection of components or other value types with Criteria. Use HQL or submit a patch that implements this functionality.

Ненавижу Hibernate, это было первое что, пришло в голову. И сразу ассоциация – швейцарский нож с вилкой, ложкой, пинцетом и зубочисткой, ножницами и пилочкой для ногтей, но в пятницу и без открывашки! Засада? Точно. Верится? Мало! Вот и я не поверил, что нельзя при помощи Criteria сделать фильтр по коллекции компонентов. И не зря не поверил – оказалось можно, но и как дефлорация пива ножом без открывашки, не совсем удобно и не каждый справится.
Скажем, есть система, оперируемая данными некоторых сущностей. В процессе работы система ведет журнал событий, куда записывает, что, когда и с чем она творила.

public enum EnityType { USER, SITE, PLACE } public class EntityReference { @SuppressWarnings({"UnusedDeclaration"}) private EnityType type; @SuppressWarnings({"UnusedDeclaration"}) private Integer id; public EnityType getType() {return type;} public Integer getId() {return id;} } public class LogRecord { private Integer id; private Date recordedAt; private String description; private List<? extends EntityReference> affectedEntities = new ArrayList<EntityReference>(); protected LogRecord() { } public LogRecord(final Integer id, final String description, final List<? extends EntityReference> list) { this.id = id; this.description = description; this.affectedEntities = list; this.recordedAt = new Date(); } public Integer getId() {return id;} public Date getRecordedAt() {return recordedAt;} public String getDescription() {return description;} public List<? extends EntityReference> getAffectedEntities() {return affectedEntities;} }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping <acronym title="Document Type Definition">DTD</acronym> 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="LogRecord" table="LOG"> <id name="id" access="field" column="RECORD_ID"> <generator class="native" /> </id> <property name="recordedAt" access="field" column="RECORDED_AT" type="timestamp" not-null="true" update="false"/> <property name="description" access="field" column="DESCRIPTION" not-null="true" update="false"/> <bag name="affectedEntities" access="field" table="LOG_ENTITIES" order-by="ENTITY_TYPE,ENTITY_ID"> <key column="RECORD_ID"/> <composite-element class="EntityReference"> <property name="type" access="field" type="entityType" column="ENTITY_TYPE"/> <property name="id" access="field" column="ENTITY_ID"/> </composite-element> </bag> </class> </hibernate-mapping>

Конечно же, если не сразу, то на следующий день появится естественное желание найти все записи в журнале, соответствующие операциям с некоторыми сущностями. Логично было бы использовать следящий критерий поиска

getSession().createCriteria(LogRecord.class) .createCriteria("affectedEntities") .add(Restrictions.eq("type", EnityType.SITE)) .add(Restrictions.in("id", Arrays.asList(100, 101, 102))) .list();

Но как раз тут мы и отгребаем от Hibernate обозначенную выше ошибку. Сразу паника, кофе, Google. Ничего не находится, но надо что-то делать. И тут в голову приходит если не гениальная то вполне себе ничего идея – «Раз Hibernate пока не в состоянии делать join по коллекции компонентов, то не надо его пытаться заставить это делать. Надо просто компоненты представить в виде сущностей и искать по атрибутам этих сущностей». Подумано - сделано.

public class LoggedEntityReference extends EntityReference { @SuppressWarnings({"UnusedDeclaration"}) private Integer logRecordId; }

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping <acronym title="Document Type Definition">DTD</acronym> 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="LoggedEntityReference" table="LOG_ENTITIES"> <composite-id> <key-property name="logRecordId" access="field" column="RECORD_ID"/> <key-property name="type" access="field" type="entityType" column="ENTITY_TYPE"/> <key-property name="id" access="field" column="ENTITY_ID"/> </composite-id> </class> </hibernate-mapping>

getSession().createCriteria(LogRecord.class) .add(Subqueries.in("id", DetachedCriteria.forClass(LoggedEntityReference.class) .add(Restrictions.eq("type", EnityType.SITE)) .add(Restrictions.in("id", Arrays.asList(100, 101, 102))) .setProjection(Projections.property("logRecordId"))) ).list();

Обожаю Hibernate, это было то, с чем шел пить пиво.

 

Leave a reply