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
Form the Relationship
//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);
//create the person and photo detached
assertEquals(0, person.getId());
assertEquals(0, photo.getId());
//add photo to person and persist object tree
person.setPhoto(photo); //this sets the FK in person
logger.info("added photo to person:{}", person);
em.persist(person);
assertNotEquals("personId not set", 0, person.getId());
assertNotEquals("photoId not set", 0, photo.getId());
logger.info("created person:{}", person);
logger.info(" and photo:{}", photo);
em.flush();
Output
-added photo to person:Person@7ab802f4, id=0, name=john doe, phone=410-555-1212, photo=Photo@608cd501, id=0. image=46080 bytes - call next value for hibernate_sequence - call next value for hibernate_sequence -Photo@608cd501: getId()=2 -created person:Person@7ab802f4, id=1, name=john doe, phone=410-555-1212, photo=Photo@608cd501, id=2. image=46080 bytes - and photo:Photo@608cd501, id=2. image=46080 bytes - insert into ORMREL_PHOTO (image, PHOTO_ID) values (?, ?) - insert into ORMREL_PERSON (firstName, lastName, phone, PERSON_PHOTO, PERSON_ID) values (?, ?, ?, ?, ?)
Person and Photo are given an ID during the persist
Photo is inspected for its ID to be assigned to Person FK to Photo
Rows (with foreign key) are inserted into database on next flush cycle
Find Object with Relationship in Database
//verify what we can get from DB
em.flush(); em.clear();
Person person2 = em.find(Person.class, person.getId());
assertNotNull(person2);
assertNotNull(person2.getPhoto());
logger.info("found person:{}", person2);
Output
- select person0_.PERSON_ID as PERSON_I1_27_0_, person0_.firstName as firstNam2_27_0_, person0_.lastName as lastName3_27_0_, person0_.phone as phone4_27_0_, person0_.PERSON_PHOTO as PERSON_P5_27_0_ from ORMREL_PERSON person0_ where person0_.PERSON_ID=? -Person@20cdb152, ctor() -Photo$HibernateProxy$2MfejJnf@0: ctor() - select photo0_.PHOTO_ID as PHOTO_ID1_28_0_, photo0_.image as image2_28_0_ from ORMREL_PHOTO photo0_ where photo0_.PHOTO_ID=? -Photo@57fdb8a4: ctor() -found person:Person@20cdb152, id=1, name=john doe, phone=410-555-1212, photo=Photo@57fdb8a4, id=2. image=46080 bytes
Calls to em.clear()
for are test purposes only and should not
be a common thing in production code. We must clear the current instance from
the cache if we want the provider to query the database for the rows versus
pulling the existing instance from the cache. Calling em.clear()
within production code will clear all instances from the persistence context
and make your code have unwanted side-effects when called.
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());
- select applicant0_.id as id1_12_0_, applicant0_.APP_BORROWER as APP_BORR2_12_0_, applicant0_.APP_PERSON as APP_PERS3_12_0_, borrower1_.BORROWER_ID as BORROWER1_15_1_, borrower1_.endDate as endDate2_15_1_, borrower1_.startDate as startDat3_15_1_, person2_.PERSON_ID as PERSON_I1_27_2_, person2_.firstName as firstNam2_27_2_, person2_.lastName as lastName3_27_2_, person2_.phone as phone4_27_2_, person2_.PERSON_PHOTO as PERSON_P5_27_2_ 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=? -Applicant@1a531422, ctor() -Borrower@7a388990, ctor() -Person@13213f26, ctor()
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
Borrower already fetched when obtained Applicant
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 relationship
borrower.setApplication(applicant);
assertNotNull("borrower does not have applicant",
borrower.getApplication());
assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
logger.info("writing rel owner (application) to DB:{}", applicant);
logger.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 DB
assertNull("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 relationship
applicant.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 DB
assertNotNull("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