I'm trying to create a one-to-many Map relationship and I'm having trouble declaring it in the Hibernate (4.x) mapping file. I just... can't figure it out.
The entity classes are something like this, where I want to use IPLogEntry#ip
as the map key (it is immutable):
class User {
private long id;
private Map<String,IPLogEntry> ipHistory = new HashMap<String,IPLogEntry>();
... other fields;
public void addIPEntry (String ip) {
IPLogEntry e = ipHistory.get(ip);
if (e == null) {
e = new IPLogEntry(this, ip);
ipHistory.put(ip, e);
}
e.doOtherStuff(...);
}
}
class IPLogEntry {
private long id;
private User user;
private String ip;
... other fields;
IPLogEntry (User user, String ip) {
this.user = user;
this.ip = ip;
... init other fields;
}
}
And tables like:
╔═══════════════════╗ ╔══════════════════════════════════════════════════╗
║ users ║ ║ ip_log ║
╟─────────────┬─────╢ ╟─────────────┬───────────────┬──────────────┬─────╢
║ id (bigint) │ ... ║ ║ id (bigint) │ user (bigint) │ ip (varchar) │ ... ║
╚═════════════╧═════╝ ╚═════════════╧═══════════════╧══════════════╧═════╝
Note that ip_log.ip
is both the map key and the IPLogEntry#ip
field value.
And after a bunch of hand-waving I've got this failed attempt at a mapping (by the way, I don't need cascade delete
support right now... don't ask):
<class name="d13.dao.User" table="users">
<id name="id"><generator class="native"/></id>
<map name="ipHistory" cascade="save-update,merge">
<key column="user"/>
<map-key column="ip" type="string"/>
<element type="d13.dao.IPLogEntry"/>
</map>
...
</class>
<class name="d13.dao.IPLogEntry" table="ip_log">
<id name="id"><generator class="native"/></id>
<many-to-one name="user" class="d13.dao.User" not-null="true"/>
<property name="ip" not-null="true"/>
...
</class>
That gets me this on initialization:
Error: creating static hibernate session factoryCould not determine type for:
d13.dao.IPLogEntry,
at table: users_ipHistory,
for columns: [org.hibernate.mapping.Column(elt)]
I think the IPLogEntry
side is right, it's the User
side and the usage of map
that I'm having trouble with.
I've been staring at:
map
, map-key
, and element
.String
instead of a full object for the element type, and also I can't actually tell what purpose that example code serves so it's hard to relate to.I can't figure out what to do. So I know this is a basic question, but, what mapping descriptor should I use to make this work?
Well, it's very simple with annotation-based mapping
@OneToMany(mappedBy = "user", cascade = { PERSIST, MERGE })
@MapKey(name = "ip")
private Map<String, IPLogEntry> ipHistory = new HashMap<>();
I'm not experienced in XML mapping, nevertheless it should be:
<class name="d13.dao.User" table="users">
<id name="id">
<generator class="native"/>
</id>
<map name="ipHistory" cascade="save-update,merge" inverse="true">
<key column="user_id"/>
<map-key column="ip" type="string"/>
<one-to-many class="d13.dao.IPLogEntry"/>
</map>
...
</class>
<class name="d13.dao.IPLogEntry" table="ip_log">
<id name="id">
<generator class="native"/>
</id>
<many-to-one name="user" class="d13.dao.User" column="user_id" not-null="true"/>
<property name="ip" not-null="true"/>
...
</class>
See Example 7.29. Bidirectional association with indexed collection