views:

273

answers:

4

Hi all,

Can anyone tell me whether Hibernate supports associations as the pkey of an entity? I thought that this would be supported but I am having a lot of trouble getting any kind of mapping that represents this to work. In particular, with the straight mapping below:

@Entity
public class EntityBar
{
    @Id
    @OneToOne(optional = false, mappedBy = "bar")
    EntityFoo foo

    // other stuff
}

I get an org.hibernate.MappingException: "Could not determine type for: EntityFoo, at table: ENTITY_BAR, for columns: [org.hibernate.mapping.Column(foo)]"

Diving into the code it seems the ID is always considered a Value type; i.e. "anything that is persisted by value, instead of by reference. It is essentially a Hibernate Type, together with zero or more columns." I could make my EntityFoo a value type by declaring it serializable, but I wouldn't expect this would lead to the right outcome either.

I would have thought that Hibernate would consider the type of the column to be integer (or whatever the actual type of the parent's ID is), just like it would with a normal one-to-one link, but this doesn't appear to kick in when I also declare it an ID. Am I going beyond what is possible by trying to combine @OneToOne with @Id? And if so, how could one model this relationship sensibly?

A: 

You can do this by sharing a primary key between EntityFoo and EntityBar:

@Entity
public class EntityBar
{
    @Id @OneToOne
    @JoinColumn(name = "foo_id")
    EntityFoo foo;

    // other stuff
}

@Entity
public class EntityFoo
{
    @Id @GeneratedValue
    Integer id;

    // other stuff
}
sea36
But then `EntityFoo` doesn't have a mapping to `EntityBar`, so when one loads an `EntityFoo` from the database, you cannot get the associated `EntityBar` object.
Andrzej Doyle
A: 

You have to use @EmbeddedId instead of @Id here. And EntityFoo should be Embeddable.

Another way is to put an integer, and a OneToOne with updateble and instertable set to false.

Bozho
But `EntityFoo` *isn't* an embeddable component, it's a first-class entity in its own right. IIRC you can't have OneToOne mappings between an entity and a component anyway.
Andrzej Doyle
+1  A: 

Yes that is possible.

Look at the following example using Driver and DriverId class as id for Driver.

@Entity
public class Drivers {

private DriversId id; //The ID which is located in another class

public Drivers() {
}

@EmbeddedId
@AttributeOverrides({
        @AttributeOverride(name = "personId", column = @Column(name = "person_id", nullable = false))})
@NotNull
public DriversId getId() {
    return this.id;
}
   //rest of class
}

Here we are using personId as the id for Driver

And the DriversId class:

//composite-id class must implement Serializable
@Embeddable
public class DriversId implements java.io.Serializable {
private static final long serialVersionUID = 462977040679573718L;

private int personId;

public DriversId() {
}

public DriversId(int personId) {
    this.personId = personId;
}

@Column(name = "person_id", nullable = false)
public int getPersonId() {
    return this.personId;
}

public void setPersonId(int personId) {
    this.personId = personId;
}

public boolean equals(Object other) {
    if ((this == other))
        return true;
    if ((other == null))
        return false;
    if (!(other instanceof DriversId))
        return false;
    DriversId castOther = (DriversId) other;

    return (this.getPersonId() == castOther.getPersonId());
}

public int hashCode() {
    int result = 17;

    result = 37 * result + this.getPersonId();
    return result;
}
}
Shervin
I don't think that this is what the OP is asking for. My understanding is that the OP wants dedicated tables and wants to map both `EntityFoo` and `EntityBar` as entities.
Pascal Thivent
+1  A: 

If the goal is to have a shared primary key, what about this (inspired by the sample of Java Persistence With Hibernate and tested on a pet database):

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    private Address shippingAddress;

    //...
}

This is the "parent" class that get inserted first and gets a generated id. The Address looks like this:

@Entity
public class Address implements Serializable {
    @Id @GeneratedValue(generator = "myForeignGenerator")
    @org.hibernate.annotations.GenericGenerator(
        name = "myForeignGenerator",
        strategy = "foreign",
        parameters = @Parameter(name = "property", value = "user")
    )
    @Column(name = "ADDRESS_ID")
    private Long id;

    @OneToOne(mappedBy="shippingAddress")
    @PrimaryKeyJoinColumn
    User user;

    //...
}

With the above entities, the following seems to behave as expected:

User newUser = new User();
Address shippingAddress = new Address();
newUser.setShippingAddress(shippingAddress);
shippingAddress.setUser(newUser);            // Bidirectional
session.save(newUser);

When an Address is saved, the primary key value that gets inserted is the same as the primary key value of the User instance referenced by the user property.

Loading a User or an Address also just works.

Let me know if I missed something.


PS: To strictly answer the question, according to Primary Keys through OneToOne Relationships:

JPA 1.0 does not allow @Id on a OneToOne or ManyToOne, but JPA 2.0 does.

But, the JPA 1.0 compliant version of Hibernate

allows the @Id annotation to be used on a OneToOne or ManyToOne mapping*.

I couldn't get this to work with Hibernate EM 3.4 though (it worked with Hibernate EM 3.5.1, i.e. the JPA 2.0 implementation). Maybe I did something wrong.

Anyway, using a shared primary key seems to provide a valid solution.

Pascal Thivent
This doesn't *quite* satisfy my situation because I can't have a separate column in the Address table mapping to User (legacy schema). However I didn't specify this in my original post, so you've answered the question and deserve the bounty.
Andrzej Doyle
@Andrzej Thanks. Just to clarify, using the above mapping didn't result in a separate column in the Address table to map the User (which makes sense since they are sharing the same value). Tested on Derby. If you want, I can provide the DDL.
Pascal Thivent