Enterprise Java Development@TOPIC@

Java Persistence API: Relations

Mapping Entity Relations to the Database

Revision: v2013-10-01

Built on: 2014-03-07 00:05 EST

Abstract

This presentation provides information for mapping Java entity class relationships to the database using JPA.


Purpose
1. Goals
2. Objectives
1. One-to-One Relationships
1.1. One-to-One: Uni-directional
1.2. @OneToOne Annotation
1.3. @JoinColumn Annotation
1.4. @JoinColumns Annotation
1.5. One-to-One: Bi-directional
1.6. Bi-directional Relationships and Ownership
1.7. Other Topics
1.8. Summary
2. One-to-Many Relationships
2.1. One-to-Many: Uni-directional
2.2. @OneToMany Annotation
2.3. One-to-Many: Bi-directional
2.4. @ManyToOne Annotation
2.5. One-to-Many: Element Collection
2.6. @ElementCollection Annotation
2.7. Other Topics
2.8. Summary
3. Relationship Capabilities
3.1. Fetching
3.1.1. fetch=LAZY
3.1.2. fetch=EAGER
3.2. Cascades
3.3. Orphan Removal
3.4. Summary
4. Object Collections
4.1. Object Identity
4.1.1. Instance Id
4.1.2. Primary Key Id
4.1.3. Switching Ids
4.1.4. Business Id
4.2. Collection Types
4.3. Summary
5. Foreign Keys
5.1. Primary Key Join
5.1.1. @PrimaryKeyJoinAnnotation
5.2. Using Composite Primary Key Property as Foreign Key
5.2.1. Using @IdClass Composite Primary Key Property as Foreign Key
5.2.2. Using @EmbeddedId Composite Primary Key Property as Foreign Key
5.3. Using Foreign Key in Composite Primary Key
5.3.1. Using Foreign Key in @IdClass Composite Primary Key
5.3.2. Using Foreign Key in @EmbeddedId Composite Primary Key (@MapsId)
5.4. Join Tables
5.4.1. @JoinTable Annotation
5.5. Summary
6. Many-to-One Relationships
6.1. Many-to-One: Uni-directional
6.2. Summary
7. Many-to-Many Relationships
7.1. Many-to-Many: Uni-directional
7.2. Many-to-Many: Bi-directional
7.3. Summary


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


  • 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 1.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=false)

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


  • No additional foreign key used to satisfy the bi-directional aspect of relation



Note

Notice only owning entity table is updated when relationship formed.

Figure 1.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







Uni-directional

Only one side ("owner") knows of the relationship

  • Uses the @OneToMany annotation

  • Defines mapping to database

    • Uses either @JoinColumn or @JoinTable

    Note

    @JoinTable adds the foreign key to the child table and not to the owning entity class table in this uni-directional case

Bi-directional

Both classes know of the relationship

  • Many side required to be owning side and maps relation to the database

    • Uses the @ManyToOne annotation

    • Uses either @JoinColumn or @JoinTable

    • Changes here change the database

  • One side required to be inverse and names the other entity's property

    • @OneToMany(mappedBy="owning-property")

    • Changes here do *not* change database




  • Relationship formed when inverse side added to owning collection


  • The foreign key is in the inverse entity class table

  • No construct in inverse class maps to this foreign key column






  • @XxxToXxx Relationship annotated with fetch=LAZY property


  • Parent accessed, debug printed, and then child collection accessed


  • Debug for parent printed before child rows retrieved

  • Child rows retrieved when parent collection accessed



  • Same as fetch=LAZY case


  • Child objects fetched with parent

  • Access to child collection occurs after all children fetched




Note

In this case, the primary key is used as the foreign key and must be set. To use the foreign key as the primary key -- use @MapsId

    //@PrimaryKeyJoinColumn  //the two tables will be joined by PKs
    @MapsId
    private Person identity;
    public Borrower(Person identity) {
        this.identity = identity;
    }


  • Setting primary key prior to persisting

  • Foreign key used twice -- once for relation and once for primary key




  • HOUSE_ID is both primary and foreign key for ROOM









  • No longer modeling separate primary key - derived from foreign key











  • Separate table created to hold foreign keys

  • Can be used for all relationship enumatations and directions


  • Join table name either derived from associated tables or explicitly named

  • Join table columns either derived from referenced table column or explicitly named


  • Unique constraint enforces the (1)-to-Many aspect of relationship









Uni-directional

Only one side ("owner") knows of the relationship

  • Uses the @ManyToMany annotation

  • Defines mapping to database

    • Must use @JoinTable

Bi-directional

Both classes know of the relationship

  • One side required to be owning side and maps relation to the database

    • Uses the @ManyToMany annotation

    • Must use @JoinTable

    • Changes here change the database

  • One side required to be inverse and names the other entity's property

    • @ManyToMany(mappedBy="owning-property")

    • Changes here do *not* change database





  1. Rows added to join table rather than updating entity class tables with foreign key




  • Owning and inverse sides now both need to be set