简体   繁体   中英

JPA/Hibernate: How to associate composite foreign keys with partial primary keys

I have two tables, each with a composite key:

CREATE TABLE Software (
  id int not null,
  version int not null,
  PRIMARY KEY (id, version)
);

CREATE TABLE System (
  name char(10) not null,
  version int not null,
  PRIMARY KEY (name, version)
);

Now, I need an association where I can express the concept "a Software has minimum requirements on the version of the System where it can run". The following table does it at the DB level:

CREATE TABLE Requirement (
  software_id int not null,
  software_version int not null,
  system_name char(10) not null,
  system_version int not null,
  PRIMARY KEY (software_id, software_version, system_name),
  FOREIGN KEY (software_id, software_version) REFERENCES Software (id,version),
  FOREIGN KEY (system_name, system_version) REFERENCES System (name,version)
)

Note that Requirement has three fields as primary key. If I were to make all four primary, then I could express more than one requirement for a software (ie OpenLib v1.2 depends on Ubuntu 13.04 and on Ubuntu 13.10), whereas a software can only have one requirement for a given OS (in the example, OpenLib v1.2 depends on Ubuntu 13.04).

How can I map this to JPA/Hibernate @Entity objects (using annotations)? (Including the related OneToMany/ManyToOne associations.)

The main problem I'm facing is the fact that only part of the System's key is used as Primary key in Requirement.

Note: I don't have to abide to a specific legacy database, so the database design can be modified. I am also open to both @EmbeddedId and/or @IdClass solutions. I would prefer compliance with JPA though.

The properties, in my code, need to be mapped through getter/setter, not directly through the fields, but I guess solutions using fields only are fine, as long as the conversion is straight forward.

Thank you.

** EDIT 28/8/2014 **

Consider the following simple test scenario that I am using to test the solutions given:

Software software = new Software(1,10);
System system = new System(2,20);
session.save(software);
session.save(system);
Requirement req = new Requirement();
req.setSoftware(software);
req.setSystem(system);
session.save(req);

You can code Requirement class as follows:

@Entity
public class Requirement {

    @EmbeddedId
    private RequirementKey id;

    @ManyToOne
    @JoinColumns({
            @JoinColumn(name = "name", referencedColumnName = "name", 
                        insertable = false, updatable = false),
            @JoinColumn(name = "system_version",
                        referencedColumnName = "version",
                        insertable = false, updatable = false)
    })
    private System system;

    @ManyToOne
    @JoinColumns({
            @JoinColumn(name = "id", referencedColumnName = "id", 
                        insertable = false, updatable = false),
            @JoinColumn(name = "version", 
                        referencedColumnName = "version",
                        insertable = false, updatable = false)
    })
    private Software software;

}

with RequirementKey :

@Embeddable
public class RequirementKey implements Serializable {
    int id;
    int version;
    String name;
}

I think there is better solution, but I cannot figure out how it may be done.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM