Enterprise Java Development@TOPIC@
Uni-directional
Only one class ("owner") knows of the relationship
Uses the @OneToOne annotation
Defines mapping to database
Uses either @JoinColumn or @JoinTable
Bi-directional
Both classes know of the relationship
Both classes use the @OneToOne annotation
One class is considered the owner and maps relation to the database
Uses either @JoinColumn or @JoinTable
Changes here change the database
One class is considered the inverse and names the other entity's property
@OneToOne(mappedBy="owning-property")
Changes here do *not* change database
Figure 56.3. One-to-One Uni-directional Example Database Schema
create table ORMREL_PERSON ( PERSON_ID bigint generated by default as identity, firstName varchar(255), lastName varchar(255), phone varchar(255), +----- PERSON_PHOTO bigint, | primary key (PERSON_ID) | ) | create table ORMREL_PHOTO ( +-----> PHOTO_ID bigint generated by default as identity, image blob, primary key (PHOTO_ID) ) alter table ORMREL_PERSON add constraint FK14D7C425DCCB1C0D foreign key (PERSON_PHOTO) references ORMREL_PHOTO
Relation realized through a foreign key
Foreign key represented by a separate column
Foreign key from owning entity table references primary key of inverse entity table
Figure 56.4. One-to-One Uni-directional Example Java Mapping
@Entity
@Table(name="ORMREL_PERSON")
public class Person {
@Id @GeneratedValue @Column(name="PERSON_ID")
private long id;
@OneToOne(cascade={
CascadeType.ALL}, //have creates, deletes, etc. cascade to photo
fetch=FetchType.LAZY) //a hint that we don't need this
@JoinColumn(name="PERSON_PHOTO") //define local foreign key column
+----- private Photo photo;
|
| @Entity
| @Table(name="ORMREL_PHOTO")
| public class Photo {
| @Id @GeneratedValue @Column(name="PHOTO_ID")
+----> private long id;
Figure 56.5. One-to-One Uni-directional Usage
//create the owning side
ejava.examples.orm.rel.annotated.Person person = new Person();
person.setFirstName("john");
person.setLastName("doe");
person.setPhone("410-555-1212");
//create the inverse side
ejava.examples.orm.rel.annotated.Photo photo = new Photo();
photo.setImage(image);
//add photo to person and persist object tree
person.setPhoto(photo); //this sets the FK in person
log.info("added photo to person:" + person);
em.persist(person);
assertTrue("personId not set", person.getId() != 0);
assertTrue("photoId not set", photo.getId() != 0);
log.info("created person:" + person);
-added photo to person:Person@1201f5bc, id=0, name=john doe, phone=410-555-1212, photo=Photo@130b5045, id=0. image=46080 bytes Hibernate: insert into ORMREL_PHOTO (PHOTO_ID, image) values (null, ?) Hibernate: insert into ORMREL_PERSON (PERSON_ID, firstName, lastName, phone, PERSON_PHOTO) values (null, ?, ?, ?, ?) -created person:Person@1201f5bc, id=1, name=john doe, phone=410-555-1212, photo=Photo@130b5045, id=1. image=46080 bytes
//verify what we can get from DB
em.flush(); em.clear();
Person person2 = em.find(Person.class, person.getId());
assertNotNull(person2);
assertNotNull(person2.getPhoto());
log.info("found person:" + person2);
Hibernate: select person0_.PERSON_ID as PERSON1_24_0_, person0_.firstName as firstNam2_24_0_, person0_.lastName as lastName3_24_0_, person0_.phone as phone4_24_0_, person0_.PERSON_PHOTO as PERSON5_24_0_ from ORMREL_PERSON person0_ where person0_.PERSON_ID=? Hibernate: select photo0_.PHOTO_ID as PHOTO1_25_0_, photo0_.image as image2_25_0_ from ORMREL_PHOTO photo0_ where photo0_.PHOTO_ID=? -found person:Person@29564bb9, id=1, name=john doe, phone=410-555-1212, photo=Photo@785e7845, id=1. image=46080 bytes
optional:boolean (default=true)
Designates whether relation is required. Default is true.
fetch:FetchType (default=EAGER)
Use EAGER or LAZY fetching of relationship when loading this entity. More of coverage of fetch in Fetching section
orphanRemoval:boolean (default=false)
Remote entity only exists for use by this relation. Automatically delete when relation terminated.
cascade:CascadeType[] (default=none)
Perform actions taken on this entity on related entity
targetEntity:Class
Define type for related class (if related Java type over-generalized)
mappedBy:String
Used by inverse side to specify owning entity property that maps relation to DB
Defines a foreign key mapping
name
Database column name
referencedColumnName (default=primary key)
Primarily used during composite key mappings to signify which property goes with which primary key member
unique (default=false)
Foreign key is unique within entity table
nullable (default=true)
Foreign key is optional within entity table. Used for 0..1 relationships
insertable (default=true)
Foreign key is part of SQL INSERT for entity
updatable (default=true)
Foreign key is part of SQL UPDATEs for the entity
table
Used in multi-table mappings to identify source table for column
columnDefinition
Customized DDL for column definition when generating schema
Used to define multiple @JoinColumns when using composite foreign keys
@OneToOne
@JoinColumns({ //defines an array of @JoinColumns
@JoinColumn(...),
@JoinColumn(...)
})
Navigate relationship from both sides of the Java relationship
Figure 56.6. One-to-One Uni-directional Example Database Schema
create table ORMREL_APPLICANT ( id bigint generated by default as identity, +----- APP_BORROWER bigint, | APP_PERSON bigint not null, | primary key (id) | ) | create table ORMREL_BORROWER ( +----> BORROWER_ID bigint not null, endDate date, startDate date, primary key (BORROWER_ID) ) alter table ORMREL_APPLICANT add constraint FKD1860812DA35F52F foreign key (APP_BORROWER) references ORMREL_BORROWER
No additional foreign key used to satisfy the bi-directional aspect of relation
Figure 56.7. One-to-One Uni-directional Example Database Java Mapping
@Entity @Table(name="ORMREL_APPLICANT")
public class Applicant {
@Id @GeneratedValue
private long id;
+--> @OneToOne(optional=true) //we may exist without Borrower
| @JoinColumn(name="APP_BORROWER")//we own relationship to Borrower
+----- private Borrower borrower;
| |
| | @Entity @Table(name="ORMREL_BORROWER")
| | public class Borrower {
| | @Id @Column(name="BORROWER_ID")
`----> private long id;
|
| @OneToOne(fetch=FetchType.LAZY,
| optional=true, //lets make this optional for demo
| mappedBy="borrower") //the other side owns foreign key column
+-- private Applicant application;
Figure 56.8. One-to-One Uni-directional Example Usage
//locate them from DB
Applicant applicant2 = em.find(Applicant.class, applicant.getId());
Borrower borrower2 = em.find(Borrower.class, borrower.getId());
//form relationship
borrower2.setApplication(applicant2); //set inverse side
applicant2.setBorrower(borrower2); //set owning side
update ORMREL_APPLICANT set APP_BORROWER=?, APP_PERSON=? where id=?
Notice only owning entity table is updated when relationship formed.
Figure 56.9. One-to-One Uni-directional Example Usage (cont.)
//locate them from DB
em.flush(); em.clear();
Applicant applicant3 = em.find(Applicant.class, applicant.getId());
Borrower borrower3 = em.find(Borrower.class, borrower.getId());
assertEquals(applicant.getId(), borrower3.getApplication().getId());
assertEquals(borrower.getId(), applicant3.getBorrower().getId());
Hibernate: select applicant0_.id as id1_12_2_, applicant0_.APP_BORROWER as APP2_12_2_, applicant0_.APP_PERSON as APP3_12_2_, borrower1_.BORROWER_ID as BORROWER1_15_0_, borrower1_.endDate as endDate2_15_0_, borrower1_.startDate as startDat3_15_0_, person2_.PERSON_ID as PERSON1_24_1_, ... from ORMREL_APPLICANT applicant0_ left outer join ORMREL_BORROWER borrower1_ on applicant0_.APP_BORROWER=borrower1_.BORROWER_ID inner join ORMREL_PERSON person2_ on applicant0_.APP_PERSON=person2_.PERSON_ID where applicant0_.id=? Hibernate: select applicant0_.id as id1_12_2_, applicant0_.APP_BORROWER as APP2_12_2_, applicant0_.APP_PERSON as APP3_12_2_, borrower1_.BORROWER_ID as BORROWER1_15_0_, borrower1_.endDate as endDate2_15_0_, borrower1_.startDate as startDat3_15_0_, person2_.PERSON_ID as PERSON1_24_1_, ... from ORMREL_APPLICANT applicant0_ left outer join ORMREL_BORROWER borrower1_ on applicant0_.APP_BORROWER=borrower1_.BORROWER_ID inner join ORMREL_PERSON person2_ on applicant0_.APP_PERSON=person2_.PERSON_ID where applicant0_.APP_BORROWER=? -
Notice the extra joins that occur with default fetch mode=EAGER
inner joins used for optional=false relationships
left outer joins used for optional=true relationships
Only changes made to owning side impact database
persist (set)
update (change)
remove (null out)
Actions taken on owning side not automatically propagated to inverse side
Inverse reference to owning side not changed (for persist, update, or remove)
Must either
Manually update inverse side
Refresh object if proper state in database
Figure 56.10. Update Inverse-side Only Example (Setup)
Borrower borrower = em.find(Borrower.class, borrowerId);
Applicant applicant = em.find(Applicant.class, applicantId);
assertNull("borrower has unexpected applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
//set ONLY the inverse side of the relationshipborrower.setApplication(applicant);
assertNotNull("borrower does not have applicant",
borrower.getApplication());assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
log.info("writing rel owner (application) to DB:" + applicant);
log.info("writing rel inverse (borrower) to DB:" + borrower);
![]() | Only inverse side being set |
![]() | Setter does not automatically propagate to other side in cache |
Figure 56.11. Update Inverse-side Only Example (Verify)
//commit changes to the DB, but since only inserse side of relationship
//was set, no FK data gets written
em.getTransaction().commit(); em.clear();
assertFalse("borrower was managed", em.contains(borrower));
assertFalse("application was managed", em.contains(applicant));
borrower = em.find(Borrower.class, borrowerId);
applicant = em.find(Applicant.class, applicantId);
//verify that relationship from cache never written to DBassertNull("borrower has unexpected applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
![]() | Not setting owning side caused relationship to *not* be written to database -- thus lost when read back in from database |
Figure 56.12. Update Owning-side Only Example (Setup)
Borrower borrower = em.find(Borrower.class, borrowerId);
Applicant applicant = em.find(Applicant.class, applicantId);
assertNull("borrower has unexpected applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
//set ONLY the owning side of the relationshipapplicant.setBorrower(borrower);
assertNull("borrower has unexpected applicant:" +
borrower.getApplication(),
borrower.getApplication());assertNotNull("applicant does not have borrower",
applicant.getBorrower());
![]() | Updating only the owning side |
![]() | Change does *not* automatically propagate to other side |
Figure 56.13. Update Owning-side Only Example (Verify)
//commit changes to the DB, since the owning side was set, we do
//get changes made to DB
em.getTransaction().commit(); em.clear();
borrower = em.find(Borrower.class, borrowerId);
applicant = em.find(Applicant.class, applicantId);
//verify that relationship from cache written to DBassertNotNull("borrower was not updated with applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNotNull("applicant was not updated with borrower",
applicant.getBorrower());
![]() | Relationship retained because owning side written to database |
Primary Key Join
No separate foreign key column
Primary key column used to also represent the foreign key
Discussed in Foreign Key section
Link Table
Neither entity class table directly references to other
Separate table used to realize relationship
Discussed in Join Tables section
Represents two classes that reference the other using a Java reference
Relationship realized through either a foreign key or join table
May be represented at the entity level as either a uni-directional or bi-directional relationship
Only the owning side is mapped to the database
Inverse side is populated from database but does not change the database