问题
My database has two entities; Company and Person. A Company can have many People, but a Person must have only one Company. The table structure looks as follows.
COMPANY ---------- owner PK comp_id PK c_name
PERSON ---------------- owner PK, FK1 personid PK comp_id FK1 p_fname p_sname
It has occurred to me that I could remove PERSON.OWNER and derive it through the foreign key; however, I can't make this change without affecting legacy code.
I have modeled these as JPA-annotated classes;
@Entity @Table(name = "PERSON") @IdClass(PersonPK.class) public class Person implements Serializable { @Id private String owner; @Id private String personid; @ManyToOne @JoinColumns( {@JoinColumn(name = "owner", referencedColumnName = "OWNER", insertable = false, updatable = false), @JoinColumn(name = "comp_id", referencedColumnName = "COMP_ID", insertable = true, updatable = true)}) private Company company; private String p_fname; private String p_sname; ...and standard getters/setters... }
@Entity @Table(name = "COMPANY") @IdClass(CompanyPK.class) public class Company implements Serializable { @Id private String owner; @Id private String comp_id; private String c_name; @OneToMany(mappedBy = "company", cascade=CascadeType.ALL) private List people; ...and standard getters/setters... }
My PersonPK and CompanyPK classes are nothing special, they just serve as a struct holding owner and the ID field, and override hashCode and equals(o).
So far so good. I come across a problem, however, when trying to deal with associations. It seems if I have an existing Company, and create a Person, and associate to the Person to the Company and persist the company, the association is not saved when the Person is inserted. For example, my main code looks like this:
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); CompanyPK companyPK = new CompanyPK(); companyPK.owner="USA"; companyPK.comp_id="1102F3"; Company company = em.find(Company.class, companyPK); Person person = new Person(); person.setOwner("USA"); person.setPersonid("5116628123"); //some number that doesn't exist yet person.setP_fname("Hannah"); person.setP_sname("Montana"); person.setCompany(company); em.persist(person);
This completes without error; however in the database I find that the Person record was inserted with a null in the COMP_ID field. With EclipseLink debug logging set to FINE, the SQL query is shown as:
INSERT INTO PERSON (PERSONID,OWNER,P_SNAME,P_FNAME) VALUES (?,?,?,?) bind => [5116628123,USA,Montana,Hannah,]
I would have expected this to be saved, and the query to be equivalent to
INSERT INTO PERSON (PERSONID,OWNER,COMP_ID,P_SNAME,P_FNAME) VALUES (?,?,?,?,?) bind => [5116628123,USA,1102F3,Montana,Hannah,]
What gives? Is it incorrect to say updatable/insertable=true for one half of a composite key and =false for the other half? If I have updatable/insertable=true for both parts of the foreign key, then Eclipselink fails to startup saying that I can not use the column twice without having one set to readonly by specifying these options.
回答1:
Your code should work. Ensure you recompiled/deployed it correctly. What version are you using?
Also ensure that your Company object has a valid id set.
You could also try putting ,
insertable = false, updatable = false
in your owner field's @Column and leave the entire foreign key insertable/updatable.
回答2:
I had the same problem. And the solution i've found is to anotate primary key fields as non-insertable and non-updatable. Your person entity should look like this:
@Entity
@Table(name = "PERSON")
@IdClass(PersonPK.class)
public class Person
implements Serializable {
@Id
@Column(insertable = false, updatable=false)
private String owner;
@Id
private String personid;
@Id
@Column(insertable = false, updatable=false)
private String comp_id;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "owner", referencedColumnName = "OWNER"),
@JoinColumn(name = "comp_id", referencedColumnName = "COMP_ID")
})
private Company company;
private String p_fname;
private String p_sname;
...and standard getters/setters...
}
回答3:
The way I did this was to annotate the fields in the PK class like I would have if they were on the entity.
Then in the entity don't have the fields the PK class holds. If you want to you can implement getters that passthrough
class Person {
.....
private PersonPK pk;
@Id
public PersonPK getPk() { return pk; }
@Transient
public String getOwner() {
return pk.getOwner();
}
....
}
The PersonPK needs to be serializable and annoted @Embeddable
@Embeddable
class PersonPK implements Serializable {
回答4:
Apart from the databases design being rather weird, this might work:
@Entity
public class Company
{
@EmbeddedId
private CompanyPK key;
@MapsId(value = "ownerId")
@OneToMany
@JoinColumn(name = "owner", referencedColumnName = "personid")
private Person owner;
@ManyToOne(mappedBy = "company")
private List<Person> persons;
@Column(name = "c_name")
private String name;
}
@Embeddable
public class CompanyPK
{
@Column(name = "comp_id")
private String id;
@Column(name = "owner")
private String ownerId;
}
@Entity
public class Person
{
@EmbeddedId
private PersonPK key;
@MapdId(value = "ownerId")
private Person owner;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "owner", referencedColumnName = "owner")
@JoinColumn(name = "comp_id", referencedColumnName = "comp_id")
})
private Company company;
@Column(name = "p_fname")
private String firstName;
@Column(name = "p_sname")
private String surName;
}
@Embeddable
public class PersonPK
{
@Column(name = "personid")
private String id;
@Column(name = "owner")
private String ownerId;
}
来源:https://stackoverflow.com/questions/1100700/with-eclipselink-jpa-can-i-have-a-foreign-composite-key-that-shares-a-field-wit