I'm having trouble using Hibernate 3.3.1 to implement a many-to-many association using an association class and composite keys, so I'm putting this out to the community in order to gain feedback on what I'm doing wrong in terms of design and/or Hibernate usage.
Background to the problem:
I'm building an open source project that implements a service for common collective intelligence tasks for a Web application, such as building a tag cloud from user tags, collaborative filtering, and clustering (e.g. k-Means). One of the goals of my project is to support a multi-tenant, shared schema deployment model, such as the one described at:
http://www.oracle.com/technology/tech/saas/pdf/saas-data-architecture-whitepaper.pdf
Consequently, this means I'll have composite keys. Here is the simplified ER diagram of my database, which I'm definitely willing to change in order to use Hibernate (admittedly, I'm not a very good relational database designer).
http://docs.google.com/Doc?id=dj52w55_0w2p6sbfd
I'm having trouble using Hibernate 3.3.1 to implement the many-to-many association between Users and Items. My current design uses a class named "UserItemIx" which encapsulates all the possible interactions between a User and an Item, such as:
+ User rating an Item
+ User viewing an Item one or more times
+ User marking an Item as a favorite
+ many more interactions I'd like to support, such as User adding a Tag to an Item
Here are the hbm declarations I'm using (based on the example in the Hibernate doc: 23.4.3. Many-to-many with shared composite key attribute)
Code:
<class name="User" table="ciobjx_user">
<composite-id name="id" class="ID">
<key-property name="tenantId" column="tenant_id"/>
<key-property name="objectId" column="object_id"/>
</composite-id>
<natural-id mutable="true">
<property name="userId" column="user_id" length="60"/>
</natural-id>
<version name="version" column="version"/>
<property name="lastName" column="last_name" length="60" not-null="true"/>
<property name="language" column="language" length="10" not-null="true"/>
<property name="isActive" column="is_active" type="boolean" not-null="true"/>
<property name="createdOn" column="created_on" type="long" not-null="true"/>
<property name="firstName" column="first_name" length="60" not-null="false"/>
<property name="middleName" column="middle_name" length="60" not-null="false"/>
<property name="email" column="email" length="90" not-null="false"/>
<set name="userItems" table="ciobjx_user_item">
<key>
<column name="user_id"/>
<column name="tenant_id"/>
</key>
<many-to-many class="UserItemIx">
<column name="item_id"/>
<formula>tenant_id</formula>
</many-to-many>
</set>
</class>
<class name="URLItem" table="ciobjx_item">
<composite-id name="id" class="ID">
<key-property name="tenantId" column="tenant_id"/>
<key-property name="objectId" column="object_id"/>
</composite-id>
<natural-id mutable="true">
<property name="uri" column="uri" length="255"/>
</natural-id>
<version name="version" column="version"/>
<property name="mimeType" column="mimetype" length="60" not-null="true"/>
<property name="language" column="language" length="3" not-null="true"/>
<property name="createdBy" column="created_by" length="60" not-null="true"/>
<property name="createdOn" column="created_on" type="long" not-null="true"/>
<property name="type" column="type" length="60" not-null="true"/>
<property name="title" column="title" length="255" not-null="true"/>
<property name="isCacheable" column="is_cacheable" type="boolean" not-null="true"/>
<property name="isUpdatable" column="is_updatable" type="boolean" not-null="true"/>
<property name="timeToLive" column="time_to_live" type="long" not-null="true"/>
<property name="encoding" column="encoding" length="60" not-null="false"/>
<set name="userItems" table="ciobjx_user_item" inverse="true">
<key>
<column name="item_id"/>
<column name="tenant_id"/>
</key>
<many-to-many class="UserItemIx">
<column name="user_id"/>
<formula>tenant_id</formula>
</many-to-many>
</set>
</class>
<class name="UserItemIx" table="ciobjx_user_item">
<composite-id name="id" class="UserItemID">
<key-property name="tenantId" column="tenant_id"/>
<key-property name="userId" column="user_id"/>
<key-property name="itemId" column="item_id"/>
</composite-id>
<many-to-one name="user" class="User" insert="false" update="false">
<column name="tenant_id"/>
<column name="user_id"/>
</many-to-one>
<many-to-one name="item" class="URLItem" insert="false" update="false">
<column name="tenant_id"/>
<column name="item_id"/>
</many-to-one>
<component name="rating" class="Rating" lazy="true">
<property name="ratedOn" column="rated_on" not-null="false"/>
<property name="rating" column="rating" not-null="false"/>
</component>
<property name="isFavorite" column="is_favorite" not-null="false"/>
<set name="views" table="ciobjx_user_item_view">
<key>
<column name="tenant_id"/>
<column name="user_id"/>
<column name="item_id"/>
</key>
<composite-element class="View">
<property name="occurredAt" column="viewed_on"/>
<property name="referrer" column="referrer"/>
</composite-element>
</set>
</class>
When I run my unit test, I get:
Code:
Caused by: org.hibernate.MappingException: collection element mapping has wrong number of columns: net.ciobjx.URLItem.userItems type: net.ciobjx.UserItemIx
at org.hibernate.mapping.Collection.validate(Collection.java:302)
at org.hibernate.mapping.Set.validate(Set.java:42)
at org.hibernate.cfg.Configuration.validate(Configuration.java:1139)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1320)
I've even tried just mapping UserItemIx as:
Code:
<class name="UserItemIx" table="ciobjx_user_item">
<composite-id name="id" class="UserItemID">
<key-property name="tenantId" column="tenant_id"/>
<key-property name="userId" column="user_id"/>
<key-property name="itemId" column="item_id"/>
</composite-id>
</class>
Received the same error.
Can anyone shed some light on what may be wrong with my configuration or database design?