Question

I've got a User an a Log class (which I cannot change):

class User {
    private long id;
    private String name;
    private Set<Log> accessLogs;
    private Set<Log> actionLogs;
}

class Log {
    private String what;
    private Date when;
}

A possible mapping will look like:

<class name="com.example.User" table="users">
    <id name="id" access="field">
        <generator class="native" />
    </id>

    <property name="name" length="256" />

    <set name="accessLogs" table="user_access_logs" cascade="all-delete-orphan" order-by="`when`">
        <key column="user_id" />
        <composite-element class="com.example.Log">
            <property name="what" length="512" />
            <property name="when" column="`when`" />
        </composite-element>
    </set>

    <set name="actionLogs" table="user_action_logs" cascade="all-delete-orphan" order-by="`when`">
        <key column="user_id" />
        <composite-element class="com.example.Log">
            <property name="what" length="512" />
            <property name="when" column="`when`" />
        </composite-element>
    </set>

</class>

This works fine and maps on the following database tables:

users
+----+------+
| id | name |
+----+------+
|  1 | john |
|  2 | bill |
|  3 | nick |
+----+------+

user_access_logs
+---------+------------+---------------------+
| user_id | what       | when                |
+---------+------------+---------------------+
|       1 | logged in  | 2010-09-21 11:25:03 |
|       1 | logged out | 2010-09-21 11:38:24 |
|       1 | logged in  | 2010-09-22 10:19:39 |
|       2 | logged in  | 2010-09-22 11:03:18 |
|       1 | logged out | 2010-09-22 11:48:16 |
|       2 | logged in  | 2010-09-26 12:45:18 |
+---------+------------+---------------------+

user_action_logs
+---------+---------------+---------------------+
| user_id | what          | when                |
+---------+---------------+---------------------+
|       1 | edit profile  | 2010-09-21 11:28:13 |
|       1 | post comment  | 2010-09-21 11:30:40 |
|       1 | edit profile  | 2010-09-21 11:31:17 |
|       1 | submit link   | 2010-09-22 10:21:02 |
|       2 | submit review | 2010-09-22 11:10:22 |
|       2 | submit link   | 2010-09-22 11:11:39 |
+---------+---------------+---------------------+

My question is how is it possible in hibernate to map those 2 sets (accessLogs and actionLogs) into the same table producing the following schema for the logs:

user_logs
+---------+---------------+---------------------+--------+
| user_id | what          | when                | type   |
+---------+---------------+---------------------+--------+
|       1 | logged in     | 2010-09-21 11:25:03 | access |
|       1 | edit profile  | 2010-09-21 11:28:13 | action |
|       1 | post comment  | 2010-09-21 11:30:40 | action |
|       1 | edit profile  | 2010-09-21 11:31:17 | action |
|       1 | logged out    | 2010-09-21 11:38:24 | access |
|       1 | logged in     | 2010-09-22 10:19:39 | access |
|       1 | submit link   | 2010-09-22 10:21:02 | action |
|       2 | logged in     | 2010-09-22 11:03:18 | access |
|       2 | submit review | 2010-09-22 11:10:22 | action |
|       2 | submit link   | 2010-09-22 11:11:39 | action |
|       1 | logged out    | 2010-09-22 11:48:16 | access |
|       2 | logged in     | 2010-09-26 12:45:18 | access |
+---------+---------------+---------------------+--------+

Edit: I want to retain the Java code and Set semantics as is. I'm looking for something like a discriminator, a formula or a hibernate API extension which will solve this without touching the Java code. Maybe something along the lines (imaginary hibernate configuration follows):

<set name="accessLogs" table="user_logs" ... formula="type='access'">
    <key column="user_id" />
    <composite-element class="Log">
        ...
    </composite-element>
</set>

<set name="actionLogs" table="user_logs" ... formula="type='action'">
    <key column="user_id" />
    <composite-element class="Log">
        ...
    </composite-element>
</set>

Edit2: I haven't got a full solution to this yet. I'm sure it can be done, possibly by extending some hibernate API.

Was it helpful?

Solution

As Maurizio wrote, you need to use the where attribute to retrieve only the rows that belong to each collection.

To insert rows using the additional column, you need to add the following element inside the <set>:

<sql-insert>
  insert into user_logs(user_id, what, [when], type)
  values(?, ?, ?, 'access')
</sql-insert>

That essentially tells Hibernate to use that specific SQL instead of generating it. Note that I had to manually escape the when column.

Bonus: to add the extra column to the DB, use the following (after closing the <class>)

<database-object>
  <create>alter table user_logs add type char(6)</create>
  <drop></drop>
</database-object>

Interesting bit: I wrote and tested this using NHibernate, but it was ported directly, so it should work exactly the same.

OTHER TIPS

Did you try to map set in this way:

<set name="accessLogs" table="user_logs" where="type='access'">    

why doesn't it work to just combine the lists into one list? Both of your lists are of the same class.

EDIT - If you want to preserve having 2 lists, then create a third list that is the combination, and only persist that. You will have to control access to accessors of the two lists, so you know when to update the third, but shouldn't be too hard.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top