We began this chapter with the goal of mapping the following object model:
![]() |
That goal has now been met. In the course of explaining JPA's object-relational mapping metadata, we slowly built the requisite schema and mappings for the complete model. First, the database schema:
![]() |
And finally, the complete entity mappings. We have trimmed the mappings to take advantage of JPA defaults where possible.
Example 13.17. Full Entity Mappings
package org.mag;
@Entity
@IdClass(Magazine.MagazineId.class)
@Table(name="MAG")
@DiscriminatorValue("Mag")
public class Magazine {
@Column(length=9)
@Id private String isbn;
@Id private String title;
@Column(name="VERS")
@Version private int version;
private String name;
private double price;
@Column(name="COPIES")
private int copiesSold;
@OneToOne(fetch=FetchType.LAZY,
cascade={CascadeType.PERSIST,CascadeType.REMOVE})
@JoinColumn(name="COVER_ID")
private Article coverArticle;
@OneToMany(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
@OrderBy
@JoinTable(name="MAG_ARTS",
joinColumns={
@JoinColumn(name="MAG_ISBN", referencedColumnName="ISBN"),
@JoinColumn(name="MAG_TITLE", referencedColumnName="TITLE")
},
inverseJoinColumns=@JoinColumn(name="ART_ID"))
private Collection<Article> articles;
@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
@JoinColumn(name="PUB_ID")
private Company publisher;
@Transient private byte[] data;
...
public static class MagazineId {
...
}
}
@Entity
@Table(name="ART", uniqueConstraints=@Unique(columnNames="TITLE"))
@SequenceGenerator(name="ArticleSeq", sequenceName="ART_SEQ")
public class Article {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ArticleSeq")
private long id;
@Column(name="VERS")
@Version private int version;
private String title;
private byte[] content;
@ManyToMany(cascade=CascadeType.PERSIST)
@OrderBy("lastName, firstName")
@JoinTable(name="ART_AUTHS",
joinColumns=@JoinColumn(name="ART_ID"),
inverseJoinColumns=@JoinColumn(name="AUTH_ID"))
private Collection<Author> authors;
...
}
package org.mag.pub;
@Entity
@Table(name="COMP")
public class Company {
@Column(name="CID")
@Id private long id;
@Column(name="VERS")
@Version private int version;
private String name;
@Column(name="REV")
private double revenue;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="street", column=@Column(name="STRT")),
@AttributeOverride(name="city", column=@Column(name="ACITY"))
})
private Address address;
@OneToMany(mappedBy="publisher", cascade=CascadeType.PERSIST)
private Collection<Magazine> mags;
@OneToMany(cascade=CascadeType.PERSIST,CascadeType.REMOVE)
@JoinTable(name="COMP_SUBS",
joinColumns=@JoinColumn(name="COMP_ID"),
inverseJoinColumns=@JoinColumn(name="SUB_ID"))
private Collection<Subscription> subscriptions;
...
}
@Entity
@Table(name="AUTH")
public class Author {
@Id
@GeneratedValue(strategy=GenerationType.TABLE, generator="AuthorGen")
@TableGenerator(name="AuthorGen", tableName="AUTH_GEN", pkColumnName="PK",
valueColumnName="AID")
@Column(name="AID", columnDefinition="INTEGER64")
private long id;
@Column(name="VERS")
@Version private int version;
@Column(name="FNAME")
private String firstName;
@Column(name="LNAME")
private String lastName;
private Address address;
@ManyToMany(mappedBy="authors", cascade=CascadeType.PERSIST)
private Collection<Article> arts;
...
}
@Embeddable
public class Address {
private String street;
private String city;
@Column(columnDefinition="CHAR(2)")
private String state;
private String zip;
}
package org.mag.subscribe;
@MappedSuperclass
public abstract class Document {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@Column(name="VERS")
@Version private int version;
...
}
@Entity
@Table(schema="CNTRCT")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="CTYPE")
public class Contract
extends Document {
@Lob
private String terms;
...
}
@Entity
@Table(name="SUB", schema="CNTRCT")
@DiscriminatorColumn(name="KIND", discriminatorType=DiscriminatorType.INTEGER)
@DiscriminatorValue("1")
public class Subscription {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@Column(name="VERS")
@Version private int version;
@Column(name="START")
private Date startDate;
@Column(name="PAY")
private double payment;
@OneToMany(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
@MapKey(name="num")
@JoinTable(name="SUB_ITEMS", schema="CNTRCT",
joinColumns=@JoinColumn(name="SUB_ID"),
inverseJoinColumns=@JoinColumn(name="ITEM_ID"))
private Map<Long,LineItem> items;
...
@Entity
@Table(name="LINE_ITEM", schema="CNTRCT")
public static class LineItem
extends Contract {
@Column(name="COMM")
private String comments;
private double price;
private long num;
@ManyToOne
@JoinColumns({
@JoinColumn(name="MAG_ISBN", referencedColumnName="ISBN"),
@JoinColumn(name="MAG_TITLE", referencedColumnName="TITLE")
})
private Magazine magazine;
...
}
}
@Entity(name="Lifetime")
@DiscriminatorValue("2")
public class LifetimeSubscription
extends Subscription {
@Basic(fetch=FetchType.LAZY)
@Column(name="ELITE")
private boolean getEliteClub() { ... }
public void setEliteClub(boolean elite) { ... }
...
}
@Entity(name="Trial")
@DiscriminatorValue("3")
public class TrialSubscription
extends Subscription {
@Column(name="END")
public Date getEndDate() { ... }
public void setEndDate(Date end) { ... }
...
}
The same metadata expressed in XML form:
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd"
version="1.0">
<mapped-superclass class="org.mag.subscribe.Document">
<attributes>
<id name="id">
<generated-value strategy="IDENTITY"/>
</id>
<version name="version">
<column name="VERS"/>
</version>
</attributes>
</mapped-superclass>
<entity class="org.mag.Magazine">
<table name="MAG"/>
<id-class="org.mag.Magazine.MagazineId"/>
<discriminator-value>Mag</discriminator-value>
<attributes>
<id name="isbn">
<column length="9"/>
</id>
<id name="title"/>
<basic name="name"/>
<basic name="price"/>
<basic name="copiesSold">
<column name="COPIES"/>
</basic>
<version name="version">
<column name="VERS"/>
</version>
<many-to-one name="publisher" fetch="LAZY">
<join-column name="PUB_ID"/>
<cascade>
<cascade-persist/>
</cascade>
</many-to-one>
<one-to-many name="articles">
<order-by/>
<join-table name="MAG_ARTS">
<join-column name="MAG_ISBN" referenced-column-name="ISBN"/>
<join-column name="MAG_TITLE" referenced-column-name="TITLE"/>
<inverse-join-column name="ART_ID"/>
</join-table>
<cascade>
<cascade-persist/>
<cascade-remove/>
</cascade>
</one-to-many>
<one-to-one name="coverArticle" fetch="LAZY">
<join-column name="COVER_ID"/>
<cascade>
<cascade-persist/>
<cascade-remove/>
</cascade>
</one-to-one>
<transient name="data"/>
</attributes>
</entity>
<entity class="org.mag.Article">
<table name="ART">
<unique-constraint>
<column-name>TITLE</column-name>
</unique-constraint>
</table>
<sequence-generator name="ArticleSeq", sequenceName="ART_SEQ"/>
<attributes>
<id name="id">
<generated-value strategy="SEQUENCE" generator="ArticleSeq"/>
</id>
<basic name="title"/>
<basic name="content"/>
<version name="version">
<column name="VERS"/>
</version>
<many-to-many name="articles">
<order-by>lastName, firstName</order-by>
<join-table name="ART_AUTHS">
<join-column name="ART_ID" referenced-column-name="ID"/>
<inverse-join-column name="AUTH_ID" referenced-column-name="AID"/>
</join-table>
</many-to-many>
</attributes>
</entity>
<entity class="org.mag.pub.Company">
<table name="COMP"/>
<attributes>
<id name="id">
<column name="CID"/>
</id>
<basic name="name"/>
<basic name="revenue">
<column name="REV"/>
</basic>
<version name="version">
<column name="VERS"/>
</version>
<one-to-many name="mags" mapped-by="publisher">
<cascade>
<cascade-persist/>
</cascade>
</one-to-many>
<one-to-many name="subscriptions">
<join-table name="COMP_SUBS">
<join-column name="COMP_ID"/>
<inverse-join-column name="SUB_ID"/>
</join-table>
<cascade>
<cascade-persist/>
<cascade-remove/>
</cascade>
</one-to-many>
<embedded name="address">
<attribute-override name="street">
<column name="STRT"/>
</attribute-override>
<attribute-override name="city">
<column name="ACITY"/>
</attribute-override>
</embedded>
</attributes>
</entity>
<entity class="org.mag.pub.Author">
<table name="AUTH"/>
<attributes>
<id name="id">
<column name="AID" column-definition="INTEGER64"/>
<generated-value strategy="TABLE" generator="AuthorGen"/>
<table-generator name="AuthorGen" table="AUTH_GEN"
pk-column-name="PK" value-column-name="AID"/>
</id>
<basic name="firstName">
<column name="FNAME"/>
</basic>
<basic name="lastName">
<column name="LNAME"/>
</basic>
<version name="version">
<column name="VERS"/>
</version>
<many-to-many name="arts" mapped-by="authors">
<cascade>
<cascade-persist/>
</cascade>
</many-to-many>
<embedded name="address"/>
</attributes>
</entity>
<entity class="org.mag.subcribe.Contract">
<table schema="CNTRCT"/>
<inheritance strategy="JOINED"/>
<discriminator-column name="CTYPE"/>
<attributes>
<basic name="terms">
<lob/>
</basic>
</attributes>
</entity>
<entity class="org.mag.subcribe.Subscription">
<table name="SUB" schema="CNTRCT"/>
<inheritance strategy="SINGLE_TABLE"/>
<discriminator-value>1</discriminator-value>
<discriminator-column name="KIND" discriminator-type="INTEGER"/>
<attributes>
<id name="id">
<generated-value strategy="IDENTITY"/>
</id>
<basic name="payment">
<column name="PAY"/>
</basic>
<basic name="startDate">
<column name="START"/>
</basic>
<version name="version">
<column name="VERS"/>
</version>
<one-to-many name="items">
<map-key name="num">
<join-table name="SUB_ITEMS" schema="CNTRCT">
<join-column name="SUB_ID"/>
<inverse-join-column name="ITEM_ID"/>
</join-table>
<cascade>
<cascade-persist/>
<cascade-remove/>
</cascade>
</one-to-many>
</attributes>
</entity>
<entity class="org.mag.subscribe.Subscription.LineItem">
<table name="LINE_ITEM" schema="CNTRCT"/>
<attributes>
<basic name="comments">
<column name="COMM"/>
</basic>
<basic name="price"/>
<basic name="num"/>
<many-to-one name="magazine">
<join-column name="MAG_ISBN" referenced-column-name="ISBN"/>
<join-column name="MAG_TITLE" referenced-column-name="TITLE"/>
</many-to-one>
</attributes>
</entity>
<entity class="org.mag.subscribe.LifetimeSubscription" name="Lifetime">
<discriminator-value>2</discriminator-value>
<attributes>
<basic name="eliteClub" fetch="LAZY">
<column name="ELITE"/>
</basic>
</attributes>
</entity>
<entity class="org.mag.subscribe.TrialSubscription" name="Trial">
<discriminator-value>3</discriminator-value>
<attributes>
<basic name="endDate">
<column name="END"/>
</basic>
</attributes>
</entity>
<embeddable class="org.mag.pub.Address">
<attributes>
<basic name="street"/>
<basic name="city"/>
<basic name="state">
<column column-definition="CHAR(2)"/>
</basic>
<basic name="zip"/>
</attributes>
</embeddable>
</entity-mappings>