Question

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.

Était-ce utile?

La solution

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top