2

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.

Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376

1 Answers1

1

Is optional @SecondaryTable @Table an option?

@Entity
@Table(name="MAIN_DATA")
@SecondaryTable(name="EXTRA_DATA")
[@org.hibernate.annotations.Table(
   appliesTo="EXTRA_DATA",
   fetch=FetchMode.SELECT,
   optional=true)

the you can have in MainItem

class MainItem {
  @Column(name="extra", table="EXTRA_DATA")
  private String extra;
  // ...and others field

  public void setExtraData(ExtraData extra) {
    this.extra = extra.getExtra();
    // etc...  
  }

  public ExtraData getExtraData() {
    ExtraData extraData = new ExtraData();

    extraData.setExtra(this.extra);
    // etc...  
  }
}

I hope it can help

Luca Basso Ricci
  • 17,829
  • 2
  • 47
  • 69
  • Thank you for the suggestion, but I'm actually trying to separate `ExtraData` from `MainItem` as much as possible. It looks like your suggestion would entail duplicating everything from `ExtraData` into `MainItem`, which is precisely one of the things I want to avoid. – Bruno Aug 17 '13 at 16:27
  • if you want to link `MainItem/ExtraData` only in terms of properties and not data (avoiding fields duplication) you can have in `ExtraData` all properties accessor, a link to `MainItem` (call it `mainItem`) and in `ExtraData.setExtra(string value) { this.mainItem.extra = value; }` – Luca Basso Ricci Aug 17 '13 at 16:42
  • That wouldn't really work either, I just want an instance of `ExtraData` to depend on an instance of `MainItem`, but I don't really want `MainItem` to have anything else to do with what's in `ExtraData` (ideally, `MainItem` shouldn't even need to have any reference to `ExtraData`, but it seems I need it for cascades to work, I've edited my question to add more details). – Bruno Aug 17 '13 at 16:57
  • I meant to have a transient instance of MainItem in ExtraData, but it is not what you want,sorry – Luca Basso Ricci Aug 17 '13 at 17:05
  • This wasn't quite was I was after, but I'll +1 and accept this, since this could be a reasonable workaround. I might accept another answer later on (if there's one). – Bruno Sep 12 '13 at 20:14