700 likes | 731 Views
Java Persistence: Object Relationship Mapping. Goals. Be able to map a relationships between classes to the database using class annotations coverage of ORM descriptors can be found in text or JPA Spec
E N D
Java Persistence: Relationships Java Persistence:Object RelationshipMapping
Java Persistence: Relationships Goals • Be able to map a relationships between classes to the database using class annotations • coverage of ORM descriptors can be found in text or JPA Spec • Be able to navigate relationships between objects persisted to the database using get/set API calls instead of SQL
Java Persistence: Relationships Objectives • Relationship Sides (owning and inverse) • Relationship Types • OneToOne • ManyToOne • OneToMany • ManyToMany • Relationship Directions • uni-directional • bi-directional • Collection Types • FetchType • CascadeType
Java Persistence: Relationships OneToOne • Uni-directional • Bi-Directional
Java Persistence: Relationships OneToOne • Uni-directional • only one class (“owner”) knows of the relationship • uses @OneToOne annotation • this class defines the mapping to the database • may use @JoinColumn or @JoinTable annotation • Bi-Directional • both classes know of the relationship • use @OneToOne annotation • one class (“owner”) defines the mapping to the database • may use @JoinColumn or @JoinTable annotation • the other class (“inverse side”) names the owner's property that defines the relationship • uses @OneToOne(mappedBy=”remote property”)
Java Persistence: Relationships OneToOne Uni-directional: Example DB Schema create table ORMREL_PHOTO (PHOTO_ID bigint generated by default as identity (..., image longvarbinary, primary key (PHOTO_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_PERSON add constraint FK14D7C425DCCB1C0D foreign key (PERSON_PHOTO) references ORMREL_PHOTO
Java Persistence: Relationships OneToOne Uni-directional: Example Java Mapping @Entity @Table(name="ORMREL_PHOTO") public class Photo { @Id @GeneratedValue @Column(name="PHOTO_ID") private long id; @Lob private byte[] image; @Entity @Table(name="ORMREL_PERSON") public class Person { @Id @GeneratedValue @Column(name="PERSON_ID") private long id; private String firstName; private String lastName; private String phone; @OneToOne(cascade={CascadeType.ALL}, fetch=FetchType.LAZY) @JoinColumn(name="PERSON_PHOTO") private Photo photo; * There is no inverse relationship definition for uni-directional relationships * Owning side uses a JoinColumn to name the foreign key column for the relationship
Java Persistence: Relationships @OneToOne Annotation public interface OneToOne extends ... { • Class targetEntity() default void.class • entity class related to • can usually be determined by property type • CascadeType[] cascade() default {} • defines which persistence operations to cascade to related object • FetchType fetch() default EAGER • hint to provider as to whether related object necessary • boolean optional() default true • states whether value of relationship can be null • String mappedBy() default “” • only used in bi-directional relationships on the inverse side • names remote property in owning class that defines mapping to DB
Java Persistence: Relationships FetchType (*hint) • EAGER • related object is always instantiated from the database when this object is instantiated • LAZY • related object not instantiated from database when this object is instantiated • proxy is acting as a place-holder • undefined implementation by spec • byte code manipulation of entity class* required option of Java EE environment • post-compilation step* option for Java SE environment • dynamic proxy classes • object is fetched and instantiated when proxy accessed and object is still attached to the persistence context • exception thrown (undefined by spec) if proxy accessed after object is detached from persistence context
Java Persistence: Relationships @JoinColumn Annotation public interface JoinColumn extends ...{ • very similar to @Column annotation • String name() default “” • name of database column for foreign key • String referencedColumnName() default “” (=PK column of related object) • used to reference a non-primary key field in related object • must be unique value in OneToOne relationship • boolean unique() default false • is foreign key unique within table • boolean nullable() default true • is foreign key allowed to be null • boolean insertable() default true • is foreign key present on create • boolean updatable() default true • is foreign key included in updates • String columnDefinition() default “” • exact DDL for column type • String table() default “” • used for multi-table mappings
Java Persistence: Relationships @JoinColumns Annotation public interface JoinColumns extends ... { • public abstract JoinColumn[] value(); • defines array of foreign key columns for composite keys Example Usage: @OneToOne @JoinColumns({ @JoinColumn(...), @JoinColumn(...) })
Java Persistence: Relationships OneToOne Uni-directional: Example Usage //create 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 anytime person.setPhoto(photo); //will set the FK in person //write person to DB; persist configured to cascade to photo em.persist(person); //verify what we can get from DB em.flush(); //FK physically written at this point em.clear(); Person person2 = em.find(Person.class, person.getId()); assertNotNull(person2); assertNotNull(person2.getPhoto());
Java Persistence: Relationships Primary Key Join: Example DB Schema create table ORMREL_BORROWER (BORROWER_ID bigint not null, startDate date, endDate 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;
Java Persistence: Relationships Primary Key Join public interface PrimaryKeyJoinColumn extends ...{ • used instead of JoinColumn to define a primary key join between this and referenced object • String name() default “” • used when this class defines a composite primary key • names primary key column in this class' table that references related object • String referencedColumnName() default “” • used to specify column foreign key is referencing • will default to PK field for related object's table • String columnDefinition() default “” • specific DDL for column definition
Java Persistence: Relationships 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 private Person identity; @Entity @Table(name="ORMREL_PERSON") public class Person { @Id @GeneratedValue @Column(name="PERSON_ID") private long id;
Java Persistence: Relationships OneToOne Bi-directional: Example DB Schema create table ORMREL_BORROWER (BORROWER_ID bigint not null, startDate date, endDate date, primary key (BORROWER_ID)) create table ORMREL_APPLICANT (id bigint generated by default as identity, APP_PERSON bigint not null, APP_BORROWER bigint, primary key (id), unique (APP_PERSON)) alter table ORMREL_APPLICANT add constraint FKD186081242E2AFD5 foreign key (APP_PERSON) references ORMREL_PERSON * RDBMSes are insensitive to direction. Therefore once a foreign key is defined on one side or the other, it can be used from either direction
Java Persistence: Relationships OneToOne Bi-directional: Example Java Mapping @Entity @Table(name="ORMREL_BORROWER") public class Borrower { private static Log log = LogFactory.getLog(Borrower.class); @Id @Column(name="BORROWER_ID") private long id; @OneToOne(fetch=FetchType.LAZY, optional=true, mappedBy="borrower") private Applicant application; @Entity @Table(name="ORMREL_APPLICANT") public class Applicant { @Id @GeneratedValue private long id; @OneToOne(optional=true) @JoinColumn(name="APP_BORROWER") private Borrower borrower; * Inverse-side of relationship defines remote property that defines the database mapping * Owning side uses a JoinColumn to name the foreign key column for the relationship
Java Persistence: Relationships OneToOne Bi-directional: Example Usage Applicant applicant2 = em.find(Applicant.class, applicantId); Borrower borrower2 = em.find(Borrower.class, borrowerId); //form relationship borrower2.setApplication(applicant2); //set inverse side applicant2.setBorrower(borrower2); //set owning side em.flush(); em.clear(); //locate them from DB Applicant applicant3 = em.find(Applicant.class, applicantId); Borrower borrower3 = em.find(Borrower.class, borrowerId); assertEquals(applicant3.getId(), borrower3.getApplication().getId()); assertEquals(borrower3.getId(), applicant3.getBorrower().getId()); * Must manually set both sides of a bi-directional relationship
Java Persistence: Relationships Bi-directional Relationships and Ownership • Only changes made to owning side impact database • persist • update • remove • Actions taken on owning side not automatically propagated to inverse side • must either • manually update inverse side • design classes to handle propogation • refresh object if proper state in database
Java Persistence: Relationships Bi-directional Relationships and Ownership Example 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()); Not setting owning side will cause no later change to DB Set on either side does NOT get automatically propagated to other side
Java Persistence: Relationships Bi-directional Relationships and Ownership Example (cont.) 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 change to be lost on next refresh
Java Persistence: Relationships Bi-directional Relationships and Ownership Example 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()); assertNotNull("applicant does not have borrower:" + applicant.getBorrower(), applicant.getBorrower()); Not setting inverse side – no impact to DB Set on owning side will take care of updating DB
Java Persistence: Relationships Bi-directional Relationships and Ownership Example (cont.) 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 written to DB assertNotNull("borrower did not have applicant:" + borrower.getApplication(), borrower.getApplication()); assertNotNull("applicant did not have borrower:" + applicant.getBorrower(), applicant.getBorrower()); Changes now correctly reflected in DB
Java Persistence: Relationships ManyToOne • Uni-directional • Many side is only class (“owner”) that knows of the relationship • uses @ManyToOne annotation • this class defines the mapping to the database • may use @JoinColumn or @JoinTable annotation • Bi-directional • Many side required to own relationship • One side (“inverse side”) names the owner's property that defines the relationship • may use @OneToMany(mappedBy=”remote property”)
Java Persistence: Relationships ManyToOne • Uni-directional • Bi-Directional
Java Persistence: Relationships ManyToOne Uni-directional: Example DB 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_MEDIA * Many side defines foreign key to one side * In this case, foreign key is part of primary key
Java Persistence: Relationships ManyToOne Uni-directional: Example Java Mapping * There is no inverse relationship definition for uni-directional relationships @Entity @Table(name="ORMREL_MEDIA") public class Media { @Id @GeneratedValue @Column(name="MEDIA_ID") private long id; @Entity @Table(name="ORMREL_MEDIACOPY") @IdClass(MediaCopyPK.class) public class MediaCopy { //use m2o to have media automatically associated @ManyToOne(optional=false) @JoinColumn( name="MEDIACOPY_MID", nullable=false, updatable=false) public Media getMedia() { return media; } private void setMedia(Media media) { this.media = media; * Owning side uses a JoinColumn to name the foreign key column for the relationship
Java Persistence: Relationships @ManyToOne Annotation public interface ManyToOne extends ...{ • Class targetEntity() default void.class • entity type to map target of relation • can be determined by provider from property type • CascadeType[] cascade() default “” • defines which persistence operations will also apply to related object • FetchType fetch() default EAGER • hint to provider whether related object is necessary • boolean optional() default true • is foreign key allowed to be null
Java Persistence: Relationships ManyToOne Uni-directional: Example Usage //create one (inverse side) ejava.examples.orm.rel.annotated.Media media = new Media(); media.setTitle("EJB3"); em.persist(media); assertTrue(media.getId() != 0); //create some copies (owning side) for(int i=0; i<5; i++) { ejava.examples.orm.rel.annotated.MediaCopy mc = new MediaCopy(media, i); assertNotNull(mc.getMedia()); assertEquals(i, mc.getCopyNo()); //must manually persist since no cascade defined from Media em.persist(mc); log.info("created copy:" + mc); } * Owning side property set in ctor()
Java Persistence: Relationships ManyToOne Uni-directional: Example Usage (cont.) em.flush(); em.clear(); //verify media copy from database provides media object for(int i=0; i<5; i++) { //media copy uses a composite primary key ejava.examples.orm.rel.MediaCopyPK pk = new MediaCopyPK(media.getId(), i); MediaCopy mc = em.find(MediaCopy.class, pk); assertNotNull("media copy not found:" + pk, mc); assertEquals("unexpected mediaId:" + mc.getMediaId(), mc.getMediaId(), media.getId()); assertEquals("unexpected copyNo:" + mc.getCopyNo(), mc.getCopyNo(), i); assertNotNull("no media resolved", mc.getMedia()); }
Java Persistence: Relationships Issues Mapping Composite Key Column public class MediaCopyPK implements Serializable { @Column(name="COPY_NO") private int copyNo; @Column(name="MEDIACOPY_MID") private long mediaId; @Entity @Table(name="ORMREL_MEDIACOPY") @IdClass(MediaCopyPK.class) public class MediaCopy { @Id private int copyNo; @Id private long mediaId; * Provider requires a separate property to map entire foreign key. • create table ORMREL_MEDIACOPY (COPY_NO integer not null, MEDIACOPY_MID bigint not null, primary key (COPY_NO, MEDIACOPY_MID))
Java Persistence: Relationships Issues Mapping Composite Key Column (JPA 1.0) @ManyToOne @JoinColumn(name="MEDIACOPY_MID") private Media media; javax.persistence.PersistenceException: org.hibernate.MappingException: Repeated column in mapping for entity: ejava.examples.orm.rel.annotated.MediaCopy column: MEDIACOPY_MID (should be mapped with insert="false" update="false") @ManyToOne @JoinColumn(name="MEDIACOPY_MID", insertable=false,updatable=false) private Media media; * Foreign key for the relationship is mapped to same column as primary key • alter table ORMREL_MEDIACOPY • add constraint FKCDB47669F152B359 • foreign key (MEDIACOPY_MID) • references ORMREL_MEDIA; * Now updates to the media property do not attempt to update the FK column, which is also a PK column.
Java Persistence: Relationships JPA 2.0 Adds @MapsId public class MediaCopy2 implements Serializable { @Id //mapped to COPY_NO by IdClass private intcopyNo; @Id //mapped to MEDIACOPY_MID by IdClass private long mediaId; @ManyToOne @MapsId("mediaId") //maps mediaId property //to relationship column @JoinColumn(name="MEDIACOPY_MID") private Media media;
Java Persistence: Relationships ManyToOne Bi-directional: Example DB Schema create table ORMREL_BORROWER (BORROWER_ID bigint not null, startDate date, endDate 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_BORROWER * Many side defines foreign key to one side
Java Persistence: Relationships ManyToOne Bi-directional: Example Java Mapping @Entity @Table(name="ORMREL_BORROWER") public class Borrower { @Id @Column(name="BORROWER_ID") private long id; @OneToMany(mappedBy="borrower", fetch=FetchType.LAZY) private Collection<Checkout> checkouts = new ArrayList<Checkout>(); @Entity @Table(name="ORMREL_CHECKOUT") public class Checkout { private static long CHECKOUT_DAYS = 1000*60*60*24*14; @Id @GeneratedValue @Column(name="CHECKOUT_ID") private long id; @ManyToOne(optional=false) @JoinColumn(name="CHECKOUT_BID") private Borrower borrower; * Inverse-side of relationship defines remote property that defines the database mapping * Owning side uses a JoinColumn to name the foreign key column for the relationship
Java Persistence: Relationships @OneToMany Annotation public interface OneToMany extends ...{ • Class targetEntity() default void.class • defines mapped type of related object • provider can determine value from property type • CascadeType[] cascade() default “” • defines which persistence operations propagate to related object • FetchType fetch() default EAGER • hint to provider whether related object is always necessary • String mappedBy() default “” • names property in related object that defines mapping to database
Java Persistence: Relationships ManyToOne Bi-directional: Example Usage //get a borrower ejava.examples.orm.rel.annotated.Borrower borrower = em.find(Borrower.class, borrowerId); assertNotNull(borrower); assertTrue(borrower.getCheckouts().size() == 0); //create 1st checkout ejava.examples.orm.rel.annotated.Checkout checkout = new Checkout(new Date()); checkout.setBorrower(borrower); //set owning side borrower.addCheckout(checkout); //set inverse side //above is a wrapper around // borrower.getCheckouts().add(checkout) em.persist(checkout); //persist owning side of the relation
Java Persistence: Relationships ManyToOne Bi-directional: Example Usage (cont.) //create a couple more for(int i=0; i<5; i++) { Checkout co = new Checkout(new Date()); co.setBorrower(borrower); //set owning side borrower.addCheckout(co); //set inverse side //above is a wrapper around // borrower.getCheckouts().add(checkout) em.persist(co); //persist owning side of the relation } log.info("done populating borrower"); em.flush(); em.clear(); Borrower borrower2 = em.find(Borrower.class, borrower.getId()); assertEquals(6, borrower2.getCheckouts().size());
Java Persistence: Relationships Changing a Collection • Wrong! • don't replace the collection from the managed object with a transient one Collection<Checkout> newCheckouts = ... borrower.setCheckouts(newCheckouts); //WRONG! • Correct! • update the existing collection within the managed object Collection<Checkout> newCheckouts = ... borrower.getCheckouts().addAll(newCheckouts);
Java Persistence: Relationships OneToMany • Uni-directional
Java Persistence: Relationships OneToMany • Uni-directional • One side (“owner”) is only class that knows of relationship • uses @OneToMany annotation • This class defines mapping to database using a Join (or “link”) table • must use a @JoinTable • Bi-directional • same as ManyToOne bi-directional
Java Persistence: Relationships Link/Join Table
Java Persistence: Relationships OneToMany Uni-directional: Example DB 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, unique (media_MEDIA_ID)) create table ORMREL_MEDIA (MEDIA_ID bigint generated by default as identity, title varchar(255), primary key (MEDIA_ID)) alter table ORMREL_INVENTORY_MEDIA add constraint FKF6FA5C317DD5E49D foreign key (ORMREL_INVENTORY_id) references ORMREL_INVENTORY alter table ORMREL_INVENTORY_MEDIA add constraint FKF6FA5C31A70D4E48 foreign key (media_MEDIA_ID) references ORMREL_MEDIA * Link table declares foreign keys to form relationships * Unique media ID guards against a single media being mapped to multiple inventories
Java Persistence: Relationships OneToMany Uni-directional: Example Java Mapping * Inverse-side of relationship defines nothing for uni-directional relationships @Entity @Table(name="ORMREL_MEDIA") public class Media { @Id @GeneratedValue @Column(name="MEDIA_ID") private long id; @Entity @Table(name="ORMREL_INVENTORY") public class Inventory { private long id; private String name; private Collection<Media> media = new ArrayList<Media>(); @OneToMany(cascade={CascadeType.ALL}) @JoinTable(name="ORMREL_INVENTORY_MEDIA") public Collection<Media> getMedia() { return media; } public void setMedia(Collection<Media> media) { this.media = media; } * Owning side of OneToManyuni-directional must define a link table to hold relationship
Java Persistence: Relationships @JoinTable Annotation public interface JoinTable extends ... { • very similar to @Table annotation • String name() default “” • table name for join table • String catalog() default “” • catalog name for join table • String schema() default “” • schema name for join table • JoinColumn[] joinColumns() default {} • array of columns defining foreign key to this object • JoinColumn[] inverseJoinColumns() default {} • array of columns defining foreign key to related object • UniqueConstraint[] uniqueConstraints()
Java Persistence: Relationships OneToMany Uni-directional: Example Usage //create owning side ejava.examples.orm.rel.annotated.Inventory inventory1 = new Inventory(); inventory1.setName("testLinkFind"); em.persist(inventory1); //create and relate inverse side for(int i=0; i<5; i++) { ejava.examples.orm.rel.annotated.Media media = new Media(); em.persist(media); log.info("created media:" + media); //relation defined to cascade perists inventory1.getMedia().add(media); }
Java Persistence: Relationships OneToMany Uni-directional: Example Usage (cont.) em.getTransaction().commit(); em.clear(); em.getTransaction().begin(); //get new copy of inventory from DB assertFalse("inventory still managed",em.contains(inventory1)); Inventory inventory2 = em.find(Inventory.class, inventory1.getId()); assertNotNull("inventory not found", inventory2); assertNotSame(inventory1, inventory2); assertNotNull("media null", inventory2.getMedia()); assertEquals("unexpected media count:" + inventory2.getMedia().size(), inventory1.getMedia().size(), inventory2.getMedia().size());
Java Persistence: Relationships Using FK in uni-directional OneToMany create table ORMO2M_CHILD ( CHILDID bigint generated by default as identity (start with 1), name varchar(255), PARENT_ID bigint, primary key (CHILDID) ); create table ORMO2M_PARENT ( PARENTID bigint generated by default as identity (start with 1), name varchar(255), primary key (PARENTID) ); alter table ORMO2M_CHILD add constraint FK257187DDF37CA975 foreign key (PARENT_ID) references ORMO2M_PARENT;
Java Persistence: Relationships Using FK in uni-directional OneToMany @Entity(name="O2MChild") @Table(name="ORMO2M_CHILD") public class OneManyChild { @Id @GeneratedValue @Column(name="CHILDID") public long getId() { return id; } @Entity(name="O2MOwningParent") @Table(name="ORMO2M_PARENT") public class OneManyOwningParent { @Id @GeneratedValue @Column(name="PARENTID") public long getId() { return id; } @OneToMany @JoinColumn(name="PARENT_ID") //@JoinTable( // joinColumns=@JoinColumn(name="PARENT_ID")) public Collection<OneManyChild> getChildren() { return children; }
Java Persistence: Relationships OneToMany bi-directional • The same as ManyToOne bi-directional • Spec requires @ManyToOne side of a bi-directional relationship be the owner