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, это было то, с чем шел пить пиво.
Изменен
