I have the following database tables:
- party, with a pk "pty_id" connected to a sequence for generating pk values.
- person with a fpk "prs_pty_id" in an identifying relation to party.pty_id.
- company ... which is not involved at the moment, but obviously this is kindof sub-superclass setup, and it could probably have been implemented with the subclassing mechanism in postgresql, but that's for another day.
So, I use Netbeans 6.9.1 to generate JPA entity classes and controller/dao code to handle this. It works just nice, I only have to add one annotation to the Party Entity bean: @GeneratedValue(strategy = GenerationType.IDENTITY). This is not needed for the Person entity bean, because it should always have the pk value of the Party that it is connected to.
So here is what I do to create a person:
PartyJpaController parController = new PartyJpaController();
PersonJpaController perController = new PersonJpaController();
Party par = new Party();
Person per = new Person();
par.setComment("jalla");
per.setName("Per Vers");
parController.create(par);
per.setPrsPtyId(par.getPtyId()); // <== why do I need to set this ...
Long partyId = par.getPtyId();
par.setPerson(per); // <== ... when this explicitly expresses the relationship?
perController.create(per);
parController.edit(par);
Party foundParty = parController.findParty(partyId);
Person foundPerson = foundParty.getPerson();
System.err.println(foundPerson.getName());
This works just fine. But why do I have to explicitly set the pk of the Person bean? It is in an identifying relationship with the Party. If I skip it, I get
java.lang.IllegalArgumentException: An instance of a null PK has been incorrectly provided for this find operation.
in perController.create(per), which is code generated by Netbeans:
EntityManager em = null;
try {
em = getEntityManager();
em.getTransaction().begin();
Party party = person.getParty();
if (party != null) {
party = em.getReference(party.getClass(), party.getPtyId()); // <== Exception thrown here
person.setParty(party);
}
em.persist(person);
if (party != null) {
party.setPerson(person);
party = em.merge(party);
}
em.getTransaction().commit();
So, I suppose the Netbeans-generated code is not quite tuned for identifying relationships? What's the best way to code this?
Software used: Eclipselink version 2.1.1 Postgresql 8.4 Netbeans 6.9.1 Java/JDK 1.6.0_21
Here are my beans, they're generated by netbeans 6.9.1 from schema, except the @GeneratedValue(strategy = GenerationType.IDENTITY) in Party, which I've added in order to use the serial/sequence pk generation in postgresql.
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.martinsolaas.webmarin.jpa; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapsId; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; /** * * @author jms */ @Entity @Table(name = "person", catalog = "webmarin", schema = "webmarin") @NamedQueries({ @NamedQuery(name = "Person.findAll", query = "SELECT p FROM Person p"), @NamedQuery(name = "Person.findByPrsPtyId", query = "SELECT p FROM Person p WHERE p.prsPtyId = :prsPtyId"), @NamedQuery(name = "Person.findByName", query = "SELECT p FROM Person p WHERE p.name = :name"), @NamedQuery(name = "Person.findByCellphone", query = "SELECT p FROM Person p WHERE p.cellphone = :cellphone"), @NamedQuery(name = "Person.findByOfficephone", query = "SELECT p FROM Person p WHERE p.officephone = :officephone")}) public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @Basic(optional = false) @Column(name = "prs_pty_id", nullable = false) @MapsId private Long prsPtyId; @Column(name = "name", length = 255) private String name; @Column(name = "cellphone", length = 55) private String cellphone; @Column(name = "officephone", length = 55) private String officephone; @JoinColumn(name = "prs_pty_id", referencedColumnName = "pty_id", nullable = false, insertable = false, updatable = false) @OneToOne(optional = false) private Party party; public Person() { } public Person(Long prsPtyId) { this.prsPtyId = prsPtyId; } public Long getPrsPtyId() { return prsPtyId; } public void setPrsPtyId(Long prsPtyId) { this.prsPtyId = prsPtyId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCellphone() { return cellphone; } public void setCellphone(String cellphone) { this.cellphone = cellphone; } public String getOfficephone() { return officephone; } public void setOfficephone(String officephone) { this.officephone = officephone; } public Party getParty() { return party; } public void setParty(Party party) { this.party = party; } @Override public int hashCode() { int hash = 0; hash += (prsPtyId != null ? prsPtyId.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { // TODO: Warning - this method won't work in the case the id fields are not set if (!(object instanceof Person)) { return false; } Person other = (Person) object; if ((this.prsPtyId == null && other.prsPtyId != null) || (this.prsPtyId != null && !this.prsPtyId.equals(other.prsPtyId))) { return false; } return true; } @Override public String toString() { return "com.martinsolaas.webmarin.jpa.Person[prsPtyId=" + prsPtyId + "]"; } }
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.martinsolaas.webmarin.jpa; import java.io.Serializable; import java.util.List; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.MapsId; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToOne; import javax.persistence.Table; /** * * @author jms */ @Entity @Table(name = "party", catalog = "webmarin", schema = "webmarin") @NamedQueries({ @NamedQuery(name = "Party.findAll", query = "SELECT p FROM Party p"), @NamedQuery(name = "Party.findByPtyId", query = "SELECT p FROM Party p WHERE p.ptyId = :ptyId"), @NamedQuery(name = "Party.findByComment", query = "SELECT p FROM Party p WHERE p.comment = :comment")}) public class Party implements Serializable { private static final long serialVersionUID = 1L; @Id @Basic(optional = false) @Column(name = "pty_id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long ptyId; @Basic(optional = false) @Column(name = "comment", nullable = false, length = 2147483647) private String comment; @JoinTable(name = "party_relationship", joinColumns = { @JoinColumn(name = "parent_pty_id", referencedColumnName = "pty_id", nullable = false)}, inverseJoinColumns = { @JoinColumn(name = "child_pty_id", referencedColumnName = "pty_id", nullable = false)}) @ManyToMany private List partyList; @ManyToMany(mappedBy = "partyList") private List partyList1; @OneToOne(cascade = CascadeType.ALL, mappedBy = "party") private Person person; @OneToOne(cascade = CascadeType.ALL, mappedBy = "party") private Company company; public Party() { } public Party(Long ptyId) { this.ptyId = ptyId; } public Party(Long ptyId, String comment) { this.ptyId = ptyId; this.comment = comment; } public Long getPtyId() { return ptyId; } public void setPtyId(Long ptyId) { this.ptyId = ptyId; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public List getPartyList() { return partyList; } public void setPartyList(List partyList) { this.partyList = partyList; } public List getPartyList1() { return partyList1; } public void setPartyList1(List partyList1) { this.partyList1 = partyList1; } public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } public Company getCompany() { return company; } public void setCompany(Company company) { this.company = company; } @Override public int hashCode() { int hash = 0; hash += (ptyId != null ? ptyId.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { // TODO: Warning - this method won't work in the case the id fields are not set if (!(object instanceof Party)) { return false; } Party other = (Party) object; if ((this.ptyId == null && other.ptyId != null) || (this.ptyId != null && !this.ptyId.equals(other.ptyId))) { return false; } return true; } @Override public String toString() { return "com.martinsolaas.webmarin.jpa.Party[ptyId=" + ptyId + "]"; } }
Eventually, here is the schema SQL
CREATE SEQUENCE webmarin.party_pty_id_seq; CREATE TABLE webmarin.party ( pty_id BIGINT NOT NULL DEFAULT nextval('webmarin.party_pty_id_seq'), comment TEXT NOT NULL, CONSTRAINT pty_pk PRIMARY KEY (pty_id) ); ALTER SEQUENCE webmarin.party_pty_id_seq OWNED BY webmarin.party.pty_id; CREATE TABLE webmarin.company ( cmp_pty_id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT cmp_pk PRIMARY KEY (cmp_pty_id) ); CREATE TABLE webmarin.party_relationship ( parent_pty_id BIGINT NOT NULL, child_pty_id BIGINT NOT NULL, CONSTRAINT ptr_pk PRIMARY KEY (parent_pty_id, child_pty_id) ); CREATE TABLE webmarin.person ( prs_pty_id BIGINT NOT NULL, name VARCHAR(255), cellphone VARCHAR(55), officephone VARCHAR(55), CONSTRAINT prs_pk PRIMARY KEY (prs_pty_id) ); ALTER TABLE webmarin.party_relationship ADD CONSTRAINT parent_party_party_relationship_fk FOREIGN KEY (parent_pty_id) REFERENCES webmarin.party (pty_id) ON DELETE NO ACTION ON UPDATE NO ACTION NOT DEFERRABLE; ALTER TABLE webmarin.party_relationship ADD CONSTRAINT child_party_party_relationship_fk FOREIGN KEY (child_pty_id) REFERENCES webmarin.party (pty_id) ON DELETE NO ACTION ON UPDATE NO ACTION NOT DEFERRABLE; ALTER TABLE webmarin.person ADD CONSTRAINT party_person_fk FOREIGN KEY (prs_pty_id) REFERENCES webmarin.party (pty_id) ON DELETE NO ACTION ON UPDATE NO ACTION NOT DEFERRABLE; ALTER TABLE webmarin.company ADD CONSTRAINT party_company_fk FOREIGN KEY (cmp_pty_id) REFERENCES webmarin.party (pty_id) ON DELETE NO ACTION ON UPDATE NO ACTION NOT DEFERRABLE;