I'm trying to use a OneToOne
relationship to add optional data (ExtraData
) to a main class (MainItem
).
All instances of ExtraData
should be linked to an instance of MainItem
, but not all instances of MainItem
need to have an instance of ExtraData
.
(I'm primarily interested in a unidirectional relationship, but it seems that I need a bidirectional relationship to be able to cascade the updates and deletions of MainItem
to ExtraData
.)
I'm having trouble using @Id
, @OneToOne
and @JoinColumn
together with the bidirectional relationship.
The classes are as follows (unidirectional):
@Entity
@Table(name = "main_item")
public class MainItem implements Serializable {
@Id
@Column(name = "id")
private int id;
@Column(name = "name")
private String name;
// + getters, setters, toString, ...
}
@Entity
@Table(name = "extra_data")
public class ExtraData implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@OneToOne
@JoinColumn(name = "item_id")
private MainItem item;
@Column(name = "extra")
private String extra;
// + getters, setters, toString, ...
}
I'm using hbm2ddl.auto=update
, but I've also inserted the following data directly:
INSERT INTO main_item(id, name) VALUES (1, 'Test A');
INSERT INTO main_item(id, name) VALUES (2, 'Test B');
INSERT INTO extra_data(item_id, extra) VALUES (1, 'Extra A');
With this unidirectional relationship, getting the extra data and their main items works fine:
Collection<ExtraData> extras = session.createCriteria(ExtraData.class)
.list();
for (ExtraData extra : extras) {
System.out.println(String.format("%s: %s", extra.getItem(), extra));
}
This is the output:
Hibernate: select this_.item_id as item2_0_0_, this_.extra as extra1_0_0_ from extra_data this_
Hibernate: select mainitem0_.id as id1_1_0_, mainitem0_.name as name2_1_0_ from main_item mainitem0_ where mainitem0_.id=?
Test A: Extra A
When I add the other @OneToOne
relationship to make the relationship bidirectional, the very same query no longer gets the MainItem
instances when getting the ExtraData
instances.
Here is the code added to MainItem
(+get/set):
@OneToOne(mappedBy = "item", cascade = CascadeType.ALL)
private ExtraData extraData;
Now, the output of the previous code is:
Hibernate: select this_.item_id as item2_0_0_, this_.extra as extra1_0_0_ from extra_data this_
null: Extra A
I don't really understand why adding this other inverted relationship prevents the MainItem
instance to be retrieved.
It seems it all has to do with using @Id
, @OneToOne
and @JoinColumn
together in ExtraData
.
I realise this was not possible with older version, but I'm using Hibernate 4.2, which should support this (I'm not sure whether there's a specific configuration setting to activate, though).
Using the same column into to members makes it work, but I'm not sure this won't cause other conflicts:
@Id
@Column(name = "item_id")
private int itemId;
@OneToOne
@JoinColumn(name = "item_id")
private MainItem item;
I haven't managed to get a variant without using a separate @Id
from the @OneToOne
member, but the following variants seem to work (at least for that test query).
What's the correct way to do this with Hibernate 4.2?
Using
@PrimaryKeyJoinColumn
:@Id @Column(name = "item_id") private int itemId; @OneToOne @PrimaryKeyJoinColumn private MainItem item;
Using
@MapsId
:@Id @Column(name = "item_id") private int itemId; @OneToOne @MapsId private MainItem item;
EDIT:
Just to clarify, I'm not really after a full bidirectional relationship between MainItem
and ExtraData
, but I'm trying to separate both sets of data as much as possible.
In plain SQL, I'd do something like this:
CREATE TABLE main_item (
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE extra_data (
item_id INTEGER PRIMARY KEY
REFERENCES main_item(id) ON UPDATE CASCADE ON DELETE CASCADE,
extra TEXT
);
Here, the concerns in main_item
don't have to know anything about extra_data
. Whatever has to do with extra_data
could be coded and handled by someone else, possibly later.
Yes, if someone deletes a row in main_item
referenced by another row in extra_data
, the row in extra_data
will be deleted too.
With Hibernate, because the cascading isn't declared in the generated foreign key constraint, and because it seems to be declared from the other side's perspective, it seems I need the bidirectional relationship, at least to have @OneToOne(mappedBy = "item", cascade = CascadeType.ALL) private ExtraData extraData;
in MainItem
, so that a deletion is cascaded (otherwise the deletion of a MainData
instance will fail, because the foreign key constraint is in the database).
Ideally, I'd like the code and data model related to ExtraData
to depend on the code in MainItem
, but the original MainItem
not to have to know about ExtraData
and whatever new members it brings.