Enterprise Java Development@TOPIC@
Built on: 2014-03-07 00:05 EST
Copyright © 2014 jim stafford (jcstaff@apl.jhu.edu)
Abstract
This presentation provides information for mapping Java entity class relationships to the database using JPA.
At the completion of this topic, the student shall
have an understanding of:
Object/Relational Mapping (ORM) based on the Java Persistence API (JPA)
Ownership (owning and inverse sides)
Relationship mapping strategies
Cardinality (one-to-one, one-to-many, many-to-one, and many-to-many)
Direction (uni-directional, bi-directional)
Realization (foreign key and link table)
Simple types/element collections
Orphan removal
Cascades
Foreign key reuse (primary key as foreign key (insert/update=false) and foreign key as primary key(@MapsId))
be able to:
Be able to map a Java entity class relationships to the database using class annotations and ORM descriptors
Be able to map one-to-one, one-to-many, many-to-one, and many-to-many relationships
Be able form relationships using foreign keys and link tables
Be able define cascades for relationships
Uni-directionalOnly one class ("owner") knows of the relationship
Uses the @OneToOne annotation
Defines mapping to database
Uses either @JoinColumn or @JoinTable
Bi-directionalBoth 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 1.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_PHOTORelation 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.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 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 bytesoptional: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:ClassDefine type for related class (if related Java type over-generalized)
mappedBy:StringUsed by inverse side to specify owning entity property that maps relation to DB
Defines a foreign key mapping
nameDatabase 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
tableUsed in multi-table mappings to identify source table for column
columnDefinitionCustomized 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 1.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 1.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 1.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 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
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 1.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 1.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 1.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 1.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
Uni-directionalOnly one side ("owner") knows of the relationship
Uses the @OneToMany annotation
Defines mapping to database
Uses either @JoinColumn or @JoinTable
@JoinTable adds the foreign key to the child table and not to the owning entity class table in this uni-directional case
Bi-directionalBoth 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
This example uses the @JoinColumn technique of inserting foreign key into child table of inverse entity class
No construct in child/inverse side to map to foreign key
Figure 2.3. One-to-Many Uni-directional Database Schema
create table ORMREL_INVENTORY (
/----> id bigint generated by default as identity,
| name varchar(255),
| primary key (id)
| )
| create table ORMREL_MEDIA (
| MEDIA_ID bigint generated by default as identity,
| title varchar(255),
`----- INVENTORY_ID bigint,
primary key (MEDIA_ID)
)
alter table ORMREL_MEDIA
add constraint FK639A68F4BCF517CD
foreign key (INVENTORY_ID)
references ORMREL_INVENTORYFigure 2.4. One-to-Many Uni-directional Database Java Mapping
@Entity @Table(name="ORMREL_INVENTORY")
public class Inventory {
@Id @GeneratedValue
private long id;
private String name;
@OneToMany(cascade={CascadeType.ALL})
/----- @JoinColumn(name="INVENTORY_ID")
| private Collection<Media> media = new ArrayList<Media>();
|
| @Entity @Table(name="ORMREL_MEDIA")
| public class Media {
| @Id @GeneratedValue @Column(name="MEDIA_ID")
`----> private long id;
Figure 2.5. One-to-Many Uni-directional Database Usage
ejava.examples.orm.rel.annotated.Inventory inventory = new Inventory();
inventory.setName("testLinkCreate");
em.persist(inventory);
for(int i=0; i<5; i++) {
ejava.examples.orm.rel.annotated.Media media = new Media();
em.persist(media);
log.info("created media:" + media);
inventory.getMedia().add(media);
}
log.info("created inventory:" + inventory);
Relationship formed when inverse side added to owning collection
Figure 2.6. One-to-Many Uni-directional Database Usage
Hibernate:
insert into ORMREL_INVENTORY (id, name)
values (null, ?)
Hibernate:
insert into ORMREL_MEDIA (MEDIA_ID, title)
values (null, ?)
-created media:ejava.examples.orm.rel.annotated.Media@5ae9fa73, id=1, title=null, authors(0)={}
...
Hibernate:
insert into ORMREL_MEDIA (MEDIA_ID, title)
values (null, ?)
-created media:Media@433a3459, id=5, title=null, authors(0)={}
-created inventory:Inventory@5b9e6638, id=1, name=testLinkCreate, media(5)={1,2,3,4,5,}
Hibernate:
update ORMREL_MEDIA
set INVENTORY_ID=?
where MEDIA_ID=?
...
Hibernate:
update ORMREL_MEDIA
set INVENTORY_ID=?
where MEDIA_ID=?The foreign key is in the inverse entity class table
No construct in inverse class maps to this foreign key column
Identical properties to @OneToOne annotation with the exception of no "optional" property
Figure 2.7. One-to-Many Bi-directional Example Database Schema
create table ORMREL_BORROWER (
+----> BORROWER_ID bigint not null,
| endDate date,
| startDate date,
| primary key (BORROWER_ID)
| )
| create table ORMREL_CHECKOUT (
| CHECKOUT_ID bigint generated by default as identity,
| outDate date,
| returnDate date,
`----- CHECKOUT_BID bigint not null,
primary key (CHECKOUT_ID)
)
alter table ORMREL_CHECKOUT
add constraint FK7F287E16C07B41F3
foreign key (CHECKOUT_BID)
references ORMREL_BORROWERFigure 2.8. One-to-Many Bi-directional Example Java Mapping
@Entity @Table(name="ORMREL_CHECKOUT")
public class Checkout {
@Id @GeneratedValue @Column(name="CHECKOUT_ID")
private long id;
+--> @ManyToOne(optional=false)
| @JoinColumn(name="CHECKOUT_BID")
+----- private Borrower borrower;
| |
| | @Entity @Table(name="ORMREL_BORROWER")
| | public class Borrower {
| | private static Log log = LogFactory.getLog(Borrower.class);
| | @Id @Column(name="BORROWER_ID")
`----> private long id;
|
| @OneToMany(mappedBy="borrower", //this relationship is owned by Checkout
| fetch=FetchType.LAZY) //try to limit what we get back
`--- private Collection<Checkout> checkouts = new ArrayList<Checkout>();
Figure 2.9. One-to-Many Bi-directional Example Usage (Create)
//get a borrower
Borrower borrower = em.find(Borrower.class, borrowerId);
assertNotNull(borrower);
assertTrue(borrower.getCheckouts().size() == 0);
//create 1st checkout
Checkout checkout = new Checkout(new Date());
checkout.setBorrower(borrower); //set owning side of the relation
borrower.addCheckout(checkout); //set inverse side of relation
//wrapper around - borower.getCheckouts().add(checkout)
em.persist(checkout); //persist owning side of the relation
Hibernate: -- query for the parent object, lazily loads child objects
select
borrower0_.BORROWER_ID as BORROWER1_15_0_,
borrower0_.endDate as endDate2_15_0_,
borrower0_.startDate as startDat3_15_0_
from ORMREL_BORROWER borrower0_
where borrower0_.BORROWER_ID=?
//get a borrower
//create a couple more
for(int i=0; i<5; i++) {
Checkout co = new Checkout(new Date());
co.setBorrower(borrower); //set owning side of the relation
borrower.addCheckout(co); //set inverse side of relation
em.persist(co); //persist owning side of the relation
}
Hibernate:
insert into ORMREL_CHECKOUT (CHECKOUT_ID, CHECKOUT_BID, outDate, returnDate)
values (null, ?, ?, ?)Figure 2.10. One-to-Many Bi-directional Example Usage (Verify)
//check the DB
em.flush(); em.clear();
Borrower borrower2 = em.find(Borrower.class, borrower.getId());
assertEquals(6, borrower2.getCheckouts().size());
Maps collection of simple data types to child table
Uses orphanRemoval for child values
Figure 2.11. Element Collection Example Database Schema
create table RELATIONEX_SUSPECT (
+----> id integer generated by default as identity,
| name varchar(32),
| primary key (id)
| )
| create table RELATIONEX_SUSPECT_ALIASES (
`----- SUSPECT_ID integer not null,
ALIAS varchar(32),
unique (SUSPECT_ID, ALIAS)
)
alter table RELATIONEX_SUSPECT_ALIASES
add constraint FK3FD160E6DE29C9CF
foreign key (SUSPECT_ID)
references RELATIONEX_SUSPECTFigure 2.12. Element Collection Example Database Java Mapping
@Entity
@Table(name="RELATIONEX_SUSPECT")
public class Suspect {
@Id @GeneratedValue
private int id;
@Column(length=32)
private String name;
@ElementCollection
@CollectionTable(
name="RELATIONEX_SUSPECT_ALIASES",
joinColumns=@JoinColumn(name="SUSPECT_ID"),
uniqueConstraints=@UniqueConstraint(columnNames={"SUSPECT_ID", "ALIAS"}))
@Column(name="ALIAS", length=32)
private Set<String> aliases;
Figure 2.13. Element Collection Example Database Usage
Suspect suspect = new Suspect();
suspect.setName("william");
em.persist(suspect);
suspect.getAliases().add("bill");
suspect.getAliases().add("billy");Link Table
Foreign key not placed in child table
Separate table used to realize relationship
Discussed in Join Tables section
Determines how when related objects are retrieved
LAZYOnly parent object immediately retrieved -- child objects retrieved on demand
Can result in better performance when child data is not needed - unused objects not retrieved
Can result in poor performance when all child data is needed - retrieves objects one at a time rather than in bulk
Can result in lazy load exception if unloaded data accessed after it can no longer be retrieved from database
Can be simulated, on demand, using value or result class queries with JPA-QL
EAGER (the default)Provider required to have loaded prior to transaction committing
Can result in better performance when all child data is needed
Can result in poor performance when no child data or limited child data is needed
Should never result in a lazy load exception
Can be simulated, on-demand, using "join fetch" queries with JPA-QL
Figure 3.1. fetch=LAZY Example Declaration
@OneToMany(mappedBy="borrower", //this relationship is owned by Checkout
fetch=FetchType.LAZY)
private Collection<Checkout> checkouts = new ArrayList<Checkout>();
@XxxToXxx Relationship annotated with fetch=LAZY property
Figure 3.2. fetch=LAZY Example Use
Borrower borrower2 = em.find(Borrower.class, borrower.getId());
log.info("found borrower: " + borrower.getName());
assertEquals(6, borrower2.getCheckouts().size());
Parent accessed, debug printed, and then child collection accessed
Figure 3.3. fetch=LAZY SQL Output
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_,
...
person2_.PERSON_PHOTO as PERSON5_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=?
-found borrower: john smith
Hibernate:
select
checkouts0_.CHECKOUT_BID as CHECKOUT4_15_1_,
checkouts0_.CHECKOUT_ID as CHECKOUT1_16_1_,
checkouts0_.CHECKOUT_ID as CHECKOUT1_16_0_,
checkouts0_.CHECKOUT_BID as CHECKOUT4_16_0_,
checkouts0_.outDate as outDate2_16_0_,
checkouts0_.returnDate as returnDa3_16_0_
from ORMREL_CHECKOUT checkouts0_
where checkouts0_.CHECKOUT_BID=?
Debug for parent printed before child rows retrieved
Child rows retrieved when parent collection accessed
Figure 3.4. fetch=EAGER Example Declaration
@OneToMany(mappedBy="borrower", //this relationship is owned by Checkout
fetch=FetchType.EAGER)
private Collection<Checkout> checkouts = new ArrayList<Checkout>();
Figure 3.5. fetch=EAGER Example Use
Borrower borrower3 = em.find(Borrower.class, borrowerId);
log.info("found borrower: " + borrower.getName());
assertEquals(0,borrower3.getCheckouts().size());
Same as fetch=LAZY case
Figure 3.6. fetch=EAGER SQL Output
Hibernate:
select
applicant0_.id as id1_12_3_,
applicant0_.APP_BORROWER as APP2_12_3_,
applicant0_.APP_PERSON as APP3_12_3_,
borrower1_.BORROWER_ID as BORROWER1_15_0_,
borrower1_.endDate as endDate2_15_0_,
borrower1_.startDate as startDat3_15_0_,
checkouts2_.CHECKOUT_BID as CHECKOUT4_15_5_,
checkouts2_.CHECKOUT_ID as CHECKOUT1_16_5_,
checkouts2_.CHECKOUT_ID as CHECKOUT1_16_1_,
checkouts2_.CHECKOUT_BID as CHECKOUT4_16_1_,
checkouts2_.outDate as outDate2_16_1_,
checkouts2_.returnDate as returnDa3_16_1_,
person3_.PERSON_ID as PERSON1_24_2_,
...
person3_.PERSON_PHOTO as PERSON5_24_2_
from ORMREL_APPLICANT applicant0_
left outer join ORMREL_BORROWER borrower1_
on applicant0_.APP_BORROWER=borrower1_.BORROWER_ID
left outer join ORMREL_CHECKOUT checkouts2_
on borrower1_.BORROWER_ID=checkouts2_.CHECKOUT_BID
inner join ORMREL_PERSON person3_
on applicant0_.APP_PERSON=person3_.PERSON_ID
where applicant0_.APP_BORROWER=?
-found borrower: john smith
Child objects fetched with parent
Access to child collection occurs after all children fetched
Automatically cause persistence commands to be repeated on related objects
Figure 3.7.
@Entity
@Table(name="RELATIONEX_LICAPP")
public class LicenseApplication {
@Id @GeneratedValue
private int id;
@Temporal(TemporalType.TIMESTAMP)
private Date updated;
@OneToOne(optional=false, fetch=FetchType.EAGER,
cascade={
CascadeType.PERSIST,
CascadeType.DETACH,
CascadeType.REMOVE,
CascadeType.REFRESH,
CascadeType.MERGE
})
private License license;
PERSISTRelated entities persisted when this entity is passed to em.persist()
DETACHRelated entities detached from persistence unit when this entity passed to em.detach()
REMOVERelated entities deleted from database when this entity passed to em.remove()
REFRESHRelated entities refreshed with state of database when this entity passed to em.refresh()
MERGERelated entities update state of database when this entity is passed to em.merge()
Automatic removal of an object who's sole purpose is to support a related object that may dereference it
Related to cascade=REMOVE but the triggering object is not being deleted
Supported in the following relationships
@OneToOne
@OneToMany
Figure 3.8. OrphanRemoval Example Declaration
@Entity
@Table(name="RELATIONEX_ATTENDEE")
public class Attendee {
@Id @GeneratedValue
private int id;
//orphanRemoval will take care of dereference and DELETE from dependent Attendee
@OneToOne(cascade=CascadeType.PERSIST, orphanRemoval=true)
private Residence residence;
Figure 3.10. OrphanRemoval Database Interaction
Hibernate:
update RELATIONEX_ATTENDEE
set name=?, residence_id=?
where id=?
Hibernate:
delete from RELATIONEX_RESIDENCE
where id=?Attendee.residence_id set to null
Orphaned residence deleted
Use fetch=EAGER when always accessing related objects together
Use fetch=LAZY when commonly access one object without accessing related objects
Use JPA-QL when encountering corner cases that violate default mapping
Cascades can be used to automate persistence actions on an entire object graph
OrphanRemoval can used to automatically delete dereferenced objects that have no use outside the scope of its owning relation
hashCode()Returns an int value to be used by hashtables to help store and locate
Must remain constant *unless* state that is used to derive value changes
Two objects resulting in equals(obj)=true must return same hashCode
Two objects resulting in same hashCode may result in equals(obj)=false - but may lead to inneficient access
equals()Returns the boolean result of whether two objects are "equal"
Reflexive - this should always equal this
Symmetric - if x==y, then y==x
Transitive - if x==y and y==z, then x==z
Consistent - if x==y, then x will always equal y (unless state used changes)
Uses object instance to determine identity
Two instances with identical state will not be equal
Default implementation
Works for cases where you will only have a single instance representing a single object
Figure 4.1. Using Default Identity Methods
public class java.lang.Object {
...
public native int hashCode();
public boolean equals(java.lang.Object);
...
}
Use assigned database primary key as identity
Positives: unique value within table or possibly database
Negatives: automatically generated values not available until object persisted
Only an issue for auto-generated primary keys
Figure 4.2. Using Database Primary Key For Identity
@Entity
public abstract class Ship {
@Id
@GeneratedValue
protected int id;
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
try {
if (obj == null) { return false; }
if (this == obj) { return true; }
return id==((Ship)obj).id;
} catch (Exception ex) { return false; }
}
Using instanceId until database primary key assigned
Positives: able to compare objects in same persistent stage
Negatives:
Persisting object will break consistency rule (except that state really did change)
Cannot compare transient instances to persisted instances
Identity-based collections can get confused by inconsistent behavior
Figure 4.3. Switching Identity Schemes
@Override
public int hashCode() {
return id==0 ? super.hashCode() : id;
}
@Override
public boolean equals(Object obj) {
try {
if (obj == null) return false;
if (this == obj) { return true; }
return (id==0) ? super.equals(obj) : id==((Ship)obj).id;
} catch (Exception ex) { return false; }
}
Use unique business values to determine identity -- independent of database primary key
Positives: consistent state through persistence lifecycle of object
Negatives: may have trouble identifying unique properties within each object type
Figure 4.4. Switching Identity Schemes
@Override
public int hashCode() {
return (name==null ? 0 : name.hashCode()) +
(created==null ? 0 : (int)created.getTime());
}
@Override
public boolean equals(Object obj) {
try {
if (obj == null) { return false; }
if (this == obj) { return true; }
return name.equals(((ShipByBusinessId)obj).name) &&
created.getTime() == (((ShipByBusinessId)obj).created.getTime());
} catch (Exception ex) { return false; }
}
CollectionBag, no specific collection ordering. Duplicates allowed.
ListDuplicates allowed. Order maintained by database using @SortKey("property ASC/DESC")
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn
@OrderBy("number ASC")
private List<Segment> segments;SetUnique value (object identity comes into play here). No specific order.
MapEach object registered in collection with a property key using @MapKey("property")
@OneToMany @MapKey(name="position") @JoinColumn(name="LINEUP_ID") private Map<String, Position> positions;
Special case of one-to-one mapping
Uses primary key as the foreign key
No additional column for foreign key
Primary keys must match
Figure 5.2. Primary Key Join Example Database Schema
create table ORMREL_BORROWER (
+----- BORROWER_ID bigint not null,
| endDate date,
| startDate date,
| primary key (BORROWER_ID)
| )
| 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)
)
alter table ORMREL_BORROWER
add constraint FKA0973E32F113D9BA
foreign key (BORROWER_ID)
references ORMREL_PERSON
Figure 5.3. Primary Key Join Example Java Mapping
@Entity @Table(name="ORMREL_BORROWER")
public class Borrower {
@Id @Column(name="BORROWER_ID")
+----- private long id;
|
| @OneToOne(fetch=FetchType.LAZY, optional=false,
| cascade={CascadeType.PERSIST,
| CascadeType.REFRESH,
| CascadeType.MERGE})
| @PrimaryKeyJoinColumn //the two tables will be joined by PKs
+----- private Person identity;
|
| @Entity
| @Table(name="ORMREL_PERSON")
| public class Person {
| @Id @GeneratedValue @Column(name="PERSON_ID")
+----> private long id;
public Borrower(Person identity) {
this.id = identity.getId();
this.identity = identity;
}
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;
}Figure 5.4. Primary Key Join Example Usage
//create the person we'll use in the relationship
ejava.examples.orm.rel.annotated.Person person = new Person();
...
//create the Borrower, who requires a Person for its identity
ejava.examples.orm.rel.annotated.Borrower borrower = new Borrower(person);
borrower.setStartDate(new Date());
//persist the borrower, creating the relationship to person
em.persist(borrower);
log.info("created borrower:" + borrower);
assertEquals(person.getId(), borrower.getId()); //ctor copies PK
Signals primary key column used as foreign key -- no separate foreign key column
name (default=primary key column of this entity)Names column in this entity's table this property maps to when using composite keys
referencedColumnName (default=primary key column of joined entity)Names column in joined entity table this property maps to when using composite keys
columnDefinitionCustom DDL definition for column when generating database schema
Child object on many side, using a composite primary key may reference parent with a property from that key
Primary keys cannot be changed
If primary key used -- database manipulation for foreign keys reasons must be turned off
Primary key used to represent foreign key
Primary key must be known -- cannot be derived from generated value
Figure 5.5. Composite @IdClass Property Reused for Foreign Key
@Entity @Table(name="ORMREL_ROOM")
@IdClass(RoomPK.class)
@AttributeOverrides({
@AttributeOverride(name = "houseId", column=@Column(name="HOUSE_ID")),
@AttributeOverride(name = "roomId", column=@Column(name="ROOM_ID"))
})
public class Room {
@Id
private int houseId;
@Id
private int roomId;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
//assign join column to primary key value and turn off inserts/updates here
@JoinColumn(name="HOUSE_ID", insertable=false, updatable=false)
private House house;
public Room() {}
public Room(House house, int roomId) {
this.houseId=house.getId();
this.house=house;
this.roomId=roomId;
}
Setting primary key prior to persisting
Foreign key used twice -- once for relation and once for primary key
Figure 5.6. Referenced Parent Object -- Source of Foreign Key/Primary Key Value
@Entity @Table(name="ORMREL_HOUSE")
public class House {
@Id @GeneratedValue
private int id;
@OneToMany(cascade={CascadeType.PERSIST, CascadeType.REMOVE},
fetch=FetchType.LAZY, mappedBy="house")
private Collection<Room> rooms=new ArrayList<Room>();
Figure 5.7. Composite @IdClass
public class RoomPK implements Serializable {
@Column(name="PK_HOUSE_ID") //overridden
private int houseId;
@Column(name="PK_ROOM_ID") //overridden
private int roomId;
Figure 5.8. Database Schema
create table ORMREL_HOUSE (
id integer generated by default as identity,
primary key (id)
)
create table ORMREL_ROOM (
HOUSE_ID integer not null,
ROOM_ID integer not null,
primary key (HOUSE_ID, ROOM_ID)
)
alter table ORMREL_ROOM
add constraint FKD9EEA1ABC0069D7C
foreign key (HOUSE_ID)
references ORMREL_HOUSEHOUSE_ID is both primary and foreign key for ROOM
Figure 5.9. Example Use
House house = new House();
em.persist(house); //generate a PK for parent
house.getRooms().add(new Room(house,0));
house.getRooms().add(new Room(house,1));
house.getRooms().add(new Room(house,2));
em.persist(house); //cascade persists to children
//get a new copy of house
em.flush(); em.clear();
House house2 = em.find(House.class, house.getId());
Figure 5.10. Example Output
Hibernate:
insert into ORMREL_HOUSE (id)
values (null)
Hibernate:
insert into ORMREL_ROOM (HOUSE_ID, ROOM_ID)
values (?, ?)
...
Hibernate:
select house0_.id as id1_18_0_
from ORMREL_HOUSE house0_
where house0_.id=?
Hibernate:
select
rooms0_.HOUSE_ID as HOUSE1_18_1_,
rooms0_.HOUSE_ID as HOUSE1_30_1_,
rooms0_.ROOM_ID as ROOM2_30_1_,
rooms0_.HOUSE_ID as HOUSE1_30_0_,
rooms0_.ROOM_ID as ROOM2_30_0_
from ORMREL_ROOM rooms0_
where rooms0_.HOUSE_ID=?Primary key used to represent foreign key
Primary key must be known -- cannot be derived from generated value
Figure 5.11. Composite @EmbeddedId Property Reused for Foreign Key
@Entity @Table(name="ORMREL_DOOR")
public class Door {
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name="houseId", column=@Column(name="HOUSE_ID")),
@AttributeOverride(name="doorId", column=@Column(name="DOOR_ID"))
})
private DoorPK pk;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
//assign join column to primary key value and turn off inserts/updates here
@JoinColumn(name="HOUSE_ID", insertable=false, updatable=false)
private House house;
public Door() {}
public Door(House house, int doorId) {
pk=new DoorPK(house.getId(), doorId);
this.house=house;
}
Figure 5.12. Composite @Embeddable Class
@Embeddable
public class DoorPK implements Serializable {
@Column(name="PK_HOUSE_ID") //overridden
private int houseId;
@Column(name="PK_DOOR_ID") //overridden
private int doorId;
Figure 5.13. Database Schema
create table ORMREL_DOOR (
DOOR_ID integer not null,
HOUSE_ID integer not null,
primary key (DOOR_ID, HOUSE_ID)
)
alter table ORMREL_DOOR
add constraint FKD9E8447EC0069D7C
foreign key (HOUSE_ID)
references ORMREL_HOUSEFigure 5.14. Example Use
House house = new House();
em.persist(house); //generate a PK for parent
house.getDoors().add(new Door(house,0));
house.getDoors().add(new Door(house,1));
house.getDoors().add(new Door(house,2));
em.persist(house); //cascade persists to children
//get a new copy of house
em.flush(); em.clear();
House house2 = em.find(House.class, house.getId());
Figure 5.15. Example Output
Hibernate:
insert into ORMREL_HOUSE (id)
values (null)
Hibernate:
insert into ORMREL_DOOR (DOOR_ID, HOUSE_ID)
values (?, ?)
...
Hibernate:
select house0_.id as id1_18_0_
from ORMREL_HOUSE house0_
where house0_.id=?
Hibernate:
select
doors0_.HOUSE_ID as HOUSE2_18_1_,
doors0_.DOOR_ID as DOOR1_17_1_,
doors0_.HOUSE_ID as HOUSE2_17_1_,
doors0_.DOOR_ID as DOOR1_17_0_,
doors0_.HOUSE_ID as HOUSE2_17_0_
from ORMREL_DOOR doors0_
where doors0_.HOUSE_ID=?Primary key value is derived from foreign key value
Value can be dynamically generated
Figure 5.16. Composite @IdClass Property Derived from Foreign Key
@Entity @Table(name="ORMREL_RESIDENT")
@IdClass(ResidentPK.class)
@AttributeOverrides({
@AttributeOverride(name = "residentId", column=@Column(name="RESIDENT_ID"))
})
public class Resident {
@Id
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="HOUSE_ID", nullable=false)
private House house;
@Id
private int residentId;
public Resident() {}
public Resident(House house, int residentId) {
this.house=house;
this.residentId=residentId;
}
No longer modeling separate primary key - derived from foreign key
Figure 5.17. Composite @IdClass Class
public class ResidentPK implements Serializable {
private int house;
private int residentId;
Figure 5.18. Database Schema
create table ORMREL_RESIDENT (
HOUSE_ID integer not null,
RESIDENT_ID integer not null,
primary key (HOUSE_ID, RESIDENT_ID)
)
alter table ORMREL_RESIDENT
add constraint FKEDC7E20C0069D7C
foreign key (HOUSE_ID)
references ORMREL_HOUSEFigure 5.19. Example Use
House house = new House();
em.persist(house); //generate a PK for parent
house.getResidents().add(new Resident(house,0));
house.getResidents().add(new Resident(house,1));
house.getResidents().add(new Resident(house,2));
em.persist(house); //cascade persists to children
//get a new copy of house
em.flush(); em.clear();
House house2 = em.find(House.class, house.getId());
Figure 5.20. Example Output
Hibernate:
insert into ORMREL_HOUSE (id)
values (null)
Hibernate:
insert into ORMREL_RESIDENT (HOUSE_ID, RESIDENT_ID)
values (?, ?)
...
Hibernate:
select house0_.id as id1_18_0_
from ORMREL_HOUSE house0_
where house0_.id=?
Hibernate:
select
residents0_.HOUSE_ID as HOUSE1_18_1_,
residents0_.HOUSE_ID as HOUSE1_29_1_,
residents0_.RESIDENT_ID as RESIDENT2_29_1_,
residents0_.HOUSE_ID as HOUSE1_29_0_,
residents0_.RESIDENT_ID as RESIDENT2_29_0_
from ORMREL_RESIDENT residents0_
where residents0_.HOUSE_ID=?Figure 5.21. Composite @EmbeddedId Property Derived from Foreign Key
@Entity @Table(name="ORMREL_MORTGAGE")
public class Mortgage {
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name="mortgageId", column=@Column(name="MORTGAGE_ID"))
})
private MortgagePK pk;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="HOUSE_ID", nullable=false)
@MapsId("houseId")
private House house;
public Mortgage() {}
public Mortgage(House house, int mortgageId) {
pk=new MortgagePK(house.getId(), mortgageId);
this.house=house;
}
Figure 5.22. Composite @Embeddable Class
@Embeddable
public class MortgagePK implements Serializable {
@Column(name="PK_HOUSE_ID") //overridden
private int houseId;
@Column(name="PK_MORTGAGE_ID") //overridden
private int mortgageId;
Figure 5.23. Database Schema
create table ORMREL_MORTGAGE (
HOUSE_ID integer not null,
MORTGAGE_ID integer not null,
primary key (HOUSE_ID, MORTGAGE_ID)
)
alter table ORMREL_MORTGAGE
add constraint FK175C656CC0069D7C
foreign key (HOUSE_ID)
references ORMREL_HOUSEFigure 5.24. Example Use
House house = new House();
em.persist(house); //generate a PK for parent
house.getMortgages().add(new Mortgage(house,0));
house.getMortgages().add(new Mortgage(house,1));
house.getMortgages().add(new Mortgage(house,2));
em.persist(house); //cascade persists to children
//get a new copy of house
em.flush(); em.clear();
House house2 = em.find(House.class, house.getId());
Figure 5.25. Example Output
Hibernate:
insert into ORMREL_HOUSE (id)
values (null)
Hibernate:
insert into ORMREL_MORTGAGE (HOUSE_ID, MORTGAGE_ID)
values (?, ?)
...
Hibernate:
select house0_.id as id1_18_0_
from ORMREL_HOUSE house0_
where house0_.id=?
Hibernate:
select
mortgages0_.HOUSE_ID as HOUSE1_18_1_,
mortgages0_.HOUSE_ID as HOUSE1_26_1_,
mortgages0_.MORTGAGE_ID as MORTGAGE2_26_1_,
mortgages0_.HOUSE_ID as HOUSE1_26_0_,
mortgages0_.MORTGAGE_ID as MORTGAGE2_26_0_
from ORMREL_MORTGAGE mortgages0_
where mortgages0_.HOUSE_ID=?Separate table created to hold foreign keys
Can be used for all relationship enumatations and directions
Figure 5.27. Join/Link Table Database Table Schema
create table ORMREL_INVENTORY (
/----> id bigint generated by default as identity,
| name varchar(255),
| primary key (id)
| )
| create table ORMREL_INVENTORY_MEDIA (
`----- ORMREL_INVENTORY_id bigint not null,
/----- media_MEDIA_ID bigint not null
| )
| create table ORMREL_MEDIA (
`----> MEDIA_ID bigint generated by default as identity,
title varchar(255),
primary key (MEDIA_ID)
)
Join table name either derived from associated tables or explicitly named
Join table columns either derived from referenced table column or explicitly named
Figure 5.28. Join/Link Table Database Constraint Schema
alter table ORMREL_INVENTORY_MEDIA
add constraint UK_F6FA5C31B7DAA951 unique (media_MEDIA_ID)
alter table ORMREL_INVENTORY_MEDIA
add constraint FKF6FA5C31A70D4E48
foreign key (media_MEDIA_ID)
references ORMREL_MEDIA
alter table ORMREL_INVENTORY_MEDIA
add constraint FKF6FA5C317DD5E49D
foreign key (ORMREL_INVENTORY_id)
references ORMREL_INVENTORYUnique constraint enforces the (1)-to-Many aspect of relationship
Figure 5.29. Join/Link Table Database Java Mapping
@Entity @Table(name="ORMREL_INVENTORY")
public class Inventory {
@Id @GeneratedValue
private long id;
private String name;
@OneToMany(cascade={CascadeType.ALL})
@JoinTable(name="ORMREL_INVENTORY_MEDIA")
/----- private Collection<Media> media = new ArrayList<Media>();
|
| @Entity @Table(name="ORMREL_MEDIA")
| public class Media {
| @Id @GeneratedValue @Column(name="MEDIA_ID")
`----> private long id;
nameDatabase table name for join table
catalogDatabase catalog for join table
schemaDatabase schema for join table
joinColumnsList of @JoinColumn definitions from join table back to owning entity class table
inverseJoinColumnsList of @JoinColumn definitions from join table to inverse entity class table
uniqueConstraintsUniqueness constraints to be added to join table
Uni-directionalOnly one side ("owner") knows of the relationship
Uses the @ManyToOne annotation
Defines mapping to database
Uses either @JoinColumn or @JoinTable
Bi-directionalSame as One-to-Many Bi-directional
Example uses composite key and derives primary key from foreign key
Foreign key auto-generated
Figure 6.2. Many-to-One Uni-directional Database Schema
create table ORMREL_MEDIA (
+--------> MEDIA_ID bigint generated by default as identity,
| title varchar(255),
| primary key (MEDIA_ID)
| )
| create table ORMREL_MEDIACOPY (
| COPY_NO integer not null,
`--------- MEDIACOPY_MID bigint not null,
primary key (COPY_NO, MEDIACOPY_MID)
)
alter table ORMREL_MEDIACOPY
add constraint FKCDB47669F152B359
foreign key (MEDIACOPY_MID)
references ORMREL_MEDIAFigure 6.3. Many-to-One Uni-directional Database Java Mapping
@Entity @Table(name="ORMREL_MEDIACOPY2")
@IdClass(MediaCopyPK2.class)
@AttributeOverrides({
@AttributeOverride(name="copyNo", column=@Column(name="COPY_NO"))
})
public class MediaCopy2 {
@Id //mapped to COPY_NO by attribute override
private int copyNo;
@Id
@ManyToOne
@JoinColumn(name="MEDIACOPY_MID")
+----- private Media media;
|
| private MediaCopy2() {}
| public MediaCopy2(Media media, int copyNo) {
| this.media=media;
| this.copyNo=copyNo;
| }
| ...
| @Entity @Table(name="ORMREL_MEDIA")
| public class Media {
`----> @Id @GeneratedValue @Column(name="MEDIA_ID")
private long id;
Figure 6.4. Many-to-One Uni-directional Database Usage
ejava.examples.orm.rel.annotated.Media media = new Media();
media.setTitle("EJB31");
//add media to DB
assertTrue(media.getId() == 0);
em.persist(media);
log.info("created media:" + media);
//create some copies
for(int i=0; i<5; i++) {
ejava.examples.orm.rel.annotated.MediaCopy2 mc =
new MediaCopy2(media, i);
assertNotNull(mc.getMedia());
assertEquals(i, mc.getCopyNo());
em.persist(mc);
log.info("created copy:" + mc);
}
Figure 6.5. Many-to-One Uni-directional Database Output
Hibernate:
insert into ORMREL_MEDIA (MEDIA_ID, title)
values (null, ?)
-created media:Media@51942b40, id=1, title=EJB31, authors(0)={}
-created copy:MediaCopy2@3787f275, mediaId=1, copyNo=0, media=Media@51942b40, id=1, title=EJB31, authors(0)={}
...
-created copy:MediaCopy2@7a7fdbb0, mediaId=1, copyNo=4, media=Media@51942b40, id=1, title=EJB31, authors(0)={}
Hibernate:
insert into ORMREL_MEDIACOPY2 (COPY_NO, MEDIACOPY_MID)
values (?, ?)
...
Hibernate:
insert into ORMREL_MEDIACOPY2 (COPY_NO, MEDIACOPY_MID)
values (?, ?)Uni-directionalOnly one side ("owner") knows of the relationship
Uses the @ManyToMany annotation
Defines mapping to database
Must use @JoinTable
Bi-directionalBoth 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
Figure 7.3. Many-to-Many Uni-directional Database Schema
create table ORMREL_WANTED (
+----> id bigint generated by default as identity,
| primary key (id)
| )
| create table ORMREL_WANTED_MEDIA (
`----- ORMREL_WANTED_id bigint not null,
+----- media_MEDIA_ID bigint not null
| )
| create table ORMREL_MEDIA (
`----> MEDIA_ID bigint generated by default as identity,
title varchar(255),
primary key (MEDIA_ID)
)
alter table ORMREL_WANTED_MEDIA
add constraint FKEE528304A70D4E48
foreign key (media_MEDIA_ID)
references ORMREL_MEDIA
alter table ORMREL_WANTED_MEDIA
add constraint FKEE52830486AFC6B6
foreign key (ORMREL_WANTED_id)
references ORMREL_WANTED
Figure 7.4. Many-to-Many Uni-directional Database Java Mapping
@Entity @Table(name="ORMREL_WANTED")
public class WantList {
@Id @GeneratedValue
private long id;
@ManyToMany
@JoinTable(name="ORMREL_WANTED_MEDIA") //define table, let columns use default names
+----- private Collection<Media> media = new ArrayList<Media>();
| ...
| @Entity @Table(name="ORMREL_MEDIA")
| public class Media {
| @Id @GeneratedValue @Column(name="MEDIA_ID")
`----> private long id;
private String title;
Figure 7.5. Many-to-Many Uni-directional Example Usage
for(WantList w: wantLists) {
for(Media m: media) {
//we can only navigate this in one direction
w.getMedia().add(m);
log.info("added media(" + m.getId() +
") to want list (" + w.getId() +")");
}
}
Figure 7.6. Many-to-Many Uni-directional Database Output
-added media(1) to want list (1)
-added media(2) to want list (1)
-added media(1) to want list (2)
-added media(2) to want list (2)
-added media(1) to want list (3)
-added media(2) to want list (3)
Hibernate:
insert into ORMREL_WANTED_MEDIA (ORMREL_WANTED_id, media_MEDIA_ID)
values (?, ?)
...Rows added to join table rather than updating entity class tables with foreign key
Figure 7.7. Many-to-Many Bi-directional Database Schema
create table ORMREL_AUTHOR (
+-----> id bigint generated by default as identity,
| name varchar(255),
| primary key (id)
| )
| create table ORMREL_AUTHOR_MEDIA (
`----- LINK_AUTHOR_ID bigint not null,
+----- LINK_MEDIA_ID bigint not null
| )
| create table ORMREL_MEDIA (
`----> MEDIA_ID bigint generated by default as identity,
title varchar(255),
primary key (MEDIA_ID)
)
alter table ORMREL_AUTHOR_MEDIA
add constraint FKA0D2B4E09B01C6F2
foreign key (LINK_MEDIA_ID)
references ORMREL_MEDIA
alter table ORMREL_AUTHOR_MEDIA
add constraint FKA0D2B4E089FFE922
foreign key (LINK_AUTHOR_ID)
references ORMREL_AUTHOR
Figure 7.8. Many-to-Many Bi-directional Database Java Mapping
@Entity @Table(name="ORMREL_AUTHOR")
public class Author {
@Id @GeneratedValue
private long id;
private String name;
@ManyToMany
@JoinTable(name="ORMREL_AUTHOR_MEDIA", //defines the link table
//defines the column in the link table for author FK
joinColumns={@JoinColumn(name="LINK_AUTHOR_ID")},
//defines the column in the link table for the media FK
inverseJoinColumns={@JoinColumn(name="LINK_MEDIA_ID")})
+--> @OrderBy("title DESC") //order the list returned from database
+----- private List<Media> media = new ArrayList<Media>();
| |
| | @Entity @Table(name="ORMREL_MEDIA")
| | public class Media {
| | @Id @GeneratedValue @Column(name="MEDIA_ID")
| | private long id;
| | private String title;
| `---- @ManyToMany(mappedBy="media") //names property in Author that points to us
`-----> private Collection<Author> authors = new ArrayList<Author>();
Figure 7.9. Many-to-Many Bi-directional Example Usage
for(Author a: authors) {
for(Media m: media) {
a.getMedia().add(m);
log.info("added media(" + m.getId() +
") to author (" + a.getId() +")");
m.getAuthors().add(a);
log.info("added author(" + a.getId() +
") to media (" + m.getId() +")");
}
}
Owning and inverse sides now both need to be set
Figure 7.10. Many-to-Many Bi-directional Database Output
-added media(3) to author (1)
-added author(1) to media (3)
...
-added media(5) to author (5)
-added author(5) to media (5)
Hibernate:
insert into ORMREL_AUTHOR_MEDIA (LINK_AUTHOR_ID, LINK_MEDIA_ID)
values (?, ?)
...
Hibernate:
insert into ORMREL_AUTHOR_MEDIA (LINK_AUTHOR_ID, LINK_MEDIA_ID)
values (?, ?)