I'm trying to map some sets in my application. I'm using the hbmtojava and hbmtoddl utilities to generate the java beans and ddl for the database (mysql). What I have is something like this:
BuildRequest is a top level element which the application deals with.
A BuildRequest can contain several comments.
A BuildRequest can contain several interestedUsers.
Originally I tried mapping them like in BuildRequest:
Code:
<set name="comments"
inverse="true"
order-by="createDate"
cascade="all-delete-orphan">
<key column="request_id"/>
<one-to-many class="model.RequestComment"/>
</set>
<set name="interestedUsers"
inverse="true"
cascade="all-delete-orphan">
<key column="request_id"/>
<one-to-many class="model.InterestedUser"/>
</set>
Here is the mapping for RequestComment:
Code:
<hibernate-mapping>
<class name="model.RequestComment"
table="request_comment">
<id name="id" column="id" type="int">
<generator class="hilo"/>
</id>
<many-to-one name="request"
class="model.BuildRequest"
column="request_id"
not-null="true"/>
<property name="comment" type="model.TextType">
<column name="commentText" sql-type="text" not-null="true"/>
</property>
<property name="author" type="string">
<column name="author" sql-type="varchar(64)" not-null="true"/>
</property>
<property name="createDate" type="timestamp">
<column name="createDate" sql-type="datetime" not-null="true"/>
</property>
</class>
</hibernate-mapping>
Interested User:
Code:
<hibernate-mapping>
<class name="model.InterestedUser"
table="interested_user">
<id name="id" column="id" type="int">
<generator class="hilo"/>
</id>
<many-to-one name="request"
class="model.BuildRequest"
column="request_id"
not-null="true"/>
<property name="user" type="string">
<column name="userName" sql-type="varchar(64)" not-null="true"/>
</property>
</class>
</hibernate-mapping>
This id key strategy worked fine for RequestComment. But for InterestedUser I really wanted the key to be request_id, userName, don't want to duplicate userids for the same request.
So I started looking into composite-ids
replace above InterestedUser mapping with:
Code:
<composite-id>
<key-many-to-one name="request"
class="model.BuildRequest"
column="request_id"/>
<key-property name="user" type="string">
<column name="userName" sql-type="varchar(64)" not-null="true"/>
</key-property>
</composite-id>
This gave me the mapping I wanted, BUT I could never get it to insert/update properly. It would not insert without using
unsaved-value="any" I think because the key was not null?(does that make sense) I then used the unsaved-value="any" and
everytime I updated a BuildRequest, it would attempt to re-insert the InterestedUsers and get key constraint violations.
So I even went so far as to write an Interceptor and override isUnsaved method, but it turns out I couldn't tell which
ones weren't saved without actually going to the DB and looking for them (not very elegant in my opinion).
Which lead me to read about composite-elements. SO here is the latest greatest mapping:
Code:
<hibernate-mapping>
<class name="model.BuildRequest"
table="build_request">
<id name="id" column="id" type="int">
<generator class="hilo"/>
</id>
<!-- a null priority will indicate complete -->
<property name="priority" type="int">
<column name="priority" sql-type="int"/>
</property>
<property name="application" type="string">
<column name="application" sql-type="varchar(64)" not-null="true"/>
</property>
<property name="requestor" type="string">
<column name="requestor" sql-type="varchar(64)" not-null="true"/>
</property>
<property name="engineer" type="string">
<column name="engineer" sql-type="varchar(64)"/>
</property>
<property name="request" type="model.TextType">
<column name="request" sql-type="text" not-null="true"/>
</property>
<property name="submitDate" type="timestamp">
<column name="submitDate" sql-type="datetime" not-null="true"/>
</property>
<property name="requestByDate" type="timestamp">
<column name="requestByDate" sql-type="datetime" not-null="true"/>
</property>
<property name="startDate" type="timestamp">
<column name="startDate" sql-type="datetime"/>
</property>
<property name="doneDate" type="timestamp">
<column name="doneDate" sql-type="datetime"/>
</property>
<property name="silent" type="boolean">
<!-- MySql does not support boolean -->
<column name="silent" sql-type="int" not-null="true"/>
</property>
<property name="status"
type="model.RequestStatusEnum"
not-null="false"
unique="false"
update="true"
insert="true">
<column name="status" sql-type="int" not-null="true" />
</property>
<property name="type"
type="model.RequestTypeEnum"
not-null="false"
unique="false"
update="true"
insert="true">
<column name="type" sql-type="int" not-null="true" />
</property>
<set name="comments" table="request_comment" lazy="true">
<key column="request_id"/>
<composite-element
class="model.RequestComment">
<property name="comment" type="model.TextType">
<column name="commentText" sql-type="text" not-null="true"/>
</property>
<property name="author" type="string">
<column name="author" sql-type="varchar(64)" not-null="true"/>
</property>
<property name="createDate" type="timestamp">
<column name="createDate" sql-type="datetime" not-null="true"/>
</property>
</composite-element>
</set>
<set name="interestedUsers" table="interested_user" lazy="true">
<key column="request_id"/>
<composite-element
class="model.InterestedUser">
<property name="user" type="string">
<column name="userName" sql-type="varchar(64)" not-null="true"/>
</property>
</composite-element>
</set>
</class>
</hibernate-mapping>
This looks like what I want EXCEPT that the composite-element for RequestComment has a BLOB/CLOB type in it. Since ALL the
fields of a composite-element are used for a key I get the following when exporting the schema:
[schemaexport] Unsuccessful: General error, message from server: "BLOB column '
commentText' used in key specification without a key length"
So my question is this. Are composite-elements the right approach? Should I use composites for InterestedUsers and a
standard many-to-one for RequestComments? Is there a work around?
Thanks in advance!
--m