Ref
Mapping persistent classes
Understanding entities and value types
- You can retrieve an instance of entity type using its persistent identity: for example, a
User,Item, orCategoryinstance. A reference to an entity instance (a pointer in the JVM) is persisted as a reference in the database (a foreign key–constrained value). An entity instance has its own life cycle; it may exist independently of any other entity. You map selected classes of your domain model as entity types. - An instance of value type has no persistent identifier property; it belongs to an entity instance. Its lifespan is bound to the owning entity instance. A value type instance doesn’t support shared references. The most obvious value types are all JDK-defined classes such as
String,Integer, and even primitives. You can also map your own domain model classes as value types: for example,AddressandMonetaryAmount.
Distinguishing entities and value types
Shared references—Avoid shared references to value type instances when you write your POJO classes. For example, make sure only oneUsercan reference anAddress. You can makeAddressimmutable with no publicsetUser()method and enforce the relationship with a public constructor that has aUserargument. Of course, you still need a no-argument, probably protected constructor, as we discussed in the previous chapter, so Hibernate can also create an instance.Life cycle dependencies—If aUseris deleted, itsAddressdependency has to be deleted as well. Persistence metadata will include the cascading rules for all such dependencies, so Hibernate (or the database) can take care of removing the obsoleteAddress. You must design your application procedures and user interface to respect and expect such dependencies—write your domain model POJOs accordingly.Identity—Entity classes need an identifier property in almost all cases. Value type classes (and of course JDK classes such asStringandInteger) don’t have an identifier property, because instances are identified through the owning entity.
Mapping entities with identity
With object/relational persistence, a persistent instance is an in-memory representation of a particular row (or rows) of a database table (or tables). Along with Java identity and equality, we define database identity. You now have three methods for distinguishing references:
- Objects are identical if they occupy the same memory location in the JVM. This can be checked with the
a == boperator. This concept is known as object identity. - Objects are equal if they have the same state, as defined by the
a.equals(Object b)method. Classes that don’t explicitly override this method inherit the implementation defined byjava.lang.Object, which compares object identity with==. This concept is known as object equality. * Objects stored in a relational database are identical if they share the same table and primary key value. This concept, mapped into the Java space, is known as database identity.
Configuring key generators
@GeneratedValue(strategy = ...)
GenerationType.AUTO—Hibernate picks an appropriate strategy, asking the SQL dialect of your configured database what is best. This is equivalent to@GeneratedValue()without any settings.GenerationType.SEQUENCE—Hibernate expects (and creates, if you use the tools) a sequence namedHIBERNATE_SEQUENCEin your database. The sequence will be called separately before everyINSERT, producing sequential numeric values.GenerationType.IDENTITY—Hibernate expects (and creates in table DDL) a special auto-incremented primary key column that automatically generates a numeric value onINSERT, in the database.GenerationType.TABLE—Hibernate will use an extra table in your database schema that holds the next numeric primary key value, one row for each entity class. This table will be read and updated accordingly, beforeINSERTs. The default table name isHIBERNATE_SEQUENCESwith columnsSEQUENCE_NAMEandSEQUENCE_NEXT_HI_VALUE.
Entity-mapping options
@Entity
@Table(name = "USERS")
public class User implements Serializable {
// ...
}
Dynamic SQL generation
@Entity
@org.hibernate.annotations.DynamicInsert
@org.hibernate.annotations.DynamicUpdate
public class Item {
// ...
}
By enabling dynamic insertion and updates, you tell Hibernate to produce the SQL strings when needed, not up front. The UPDATE will only contain columns with updated values, and the INSERT will only contain non-nullable columns
Making an entity immutable
@Entity
@org.hibernate.annotations.Immutable
public class Bid {
// ...
}
A POJO is immutable if no public setter methods for any properties of the class are exposed—all values are set in the constructor. Hibernate should access the fields directly when loading and storing instances.
Mapping an entity to a subselct
Using a Hibernate annotation, you can create an application-level view, a read-only entity class mapped to an SQL SELECT:
ItemBidSummary itemBidSummary = em.find(ItemBidSummary.class, ITEM_ID);
// select * from (
// select i.ID as ITEMID, i.ITEM_NAME as NAME, ...
// ) where ITEMID = ?
You should list all table names referenced in your SELECT in the @org.hibernate .annotations.Synchronize annotation.
Mapping value types
Mapping basic properties
The default JPA rules for properties of persistent classes are these:
- If the property is a primitive or a primitive wrapper, or of type
String,BigInteger,BigDecimal,java.util.Date,java.util.Calendar,java.sql.Date,java.sql .Time,java.sql.Timestamp,byte[],Byte[],char[], orCharacter[], it’s automatically persistent. Hibernate loads and stores the value of the property in a column with an appropriate SQL type and the same name as the property. - Otherwise, if you annotate the class of the property as
@Embeddable, or you map the property itself as@Embedded, Hibernate maps the property as an embedded component of the owning class. We discuss embedding of components later in this chapter, with theAddressandMonetaryAmountembeddable classes ofCaveatEmptor. - Otherwise, if the type of the property is
java.io.Serializable, its value is stored in its serialized form. This typically isn’t what you want, and you should always map Java classes instead of storing a heap of bytes in the database. Imagine maintaining a database with this binary information when the application is gone in a few years. - Otherwise, Hibernate will throw an exception on startup, complaining that it doesn’t understand the type of the property.
Overriding basic property defaults
To exclude a property, mark the field or the getter method of the property with the @javax.persistence.Transient annotation or use the Java transient keyword.
@Basic(optional = false)
BigDecimal initialPrice;
@Column(nullable = false)
BigDecimal initialPrice;
@Column(name = "START_PRICE", nullable = false)
BigDecimal initialPrice;
Customizing property access
The JPA specification offers the @Access annotation for overriding the default behavior, with the parameters AccessType.FIELD and AccessType.PROPERTY .
If you set @Access on the class/entity level, Hibernate accesses all properties of the class according to the selected strategy. You then set any other mapping annotations, including the @Id, on either fields or getter methods, respectively.
@Access(AccessType.FIELD) on a getter method would tell Hibernate to access the field directly.
Using derived properties
@org.hibernate.annotations.Formula(
"substr(DESCRIPTION, 1, 12) || '...'"
)
protected String shortDescription;
@org.hibernate.annotations.Formula(
"(select avg(b.AMOUNT) from BID b where b.ITEM_ID = ID)"
)
protected BigDecimal averageBidAmount;
The given SQL formulas are evaluated every time the Item entity is retrieved from the database and not at any other time, so the result may be outdated if other properties are modified. The properties never appear in an SQL INSERT or UPDATE, only in SELECTs.
Evaluation occurs in the database; Hibernate embeds the SQL formula in the SELECT clause when loading the instance.
Transforming column values
@Column(name = "IMPERIALWEIGHT")
@org.hibernate.annotations.ColumnTransformer(
read = "IMPERIALWEIGHT / 2.20462",
write = "? * 2.20462"
)
protected double metricWeight;
When reading a row from the ITEM table, Hibernate embeds the expression IMPERIALWEIGHT / 2.20462, so the calculation occurs in the database and Hibernate returns the metric value in the result to the application layer. For writing to the column, Hibernate sets the metric value on the mandatory, single placeholder (the question mark), and your SQL expression calculates the actual value to be inserted or updated.
List<Item> result =
em.createQuery("select i from Item i where i.metricWeight = :w")
.setParameter("w", 2.0)
.getResultList();
The actual SQL executed by Hibernate for this query contains the following restriction in the WHERE clause:
// ...
where
i.IMPERIALWEIGHT / 2.20462=?
Generated and default property values
Essentially, whenever Hibernate issues an SQL INSERT or UPDATE for an entity that has declared generated properties, it does a SELECT immediately afterward to retrieve the generated values
You mark generated properties with the @org.hibernate.annotations.Generated annotation.
@Temporal(TemporalType.TIMESTAMP)
@Column(insertable = false, updatable = false)
@org.hibernate.annotations.Generated(
org.hibernate.annotations.GenerationTime.ALWAYS
)
protected Date lastModified;
@Column(insertable = false)
@org.hibernate.annotations.ColumnDefault("1.00")
@org.hibernate.annotations.Generated(
org.hibernate.annotations.GenerationTime.INSERT
)
protected BigDecimal initialPrice;
Available settings for GenerationTime are ALWAYS and INSERT .
ALWAYS: Hibernate refreshes the entity instance after every SQLUPDATEorINSERT. The example assumes that a database trigger will keep thelastModifiedproperty current. The property should also be marked read-only, with the updatable and insertable parameters of@Column. If both are set to false, the property’s column(s) never appear in theINSERTorUPDATEstatements, and you let the database generate the value.INSERT: refreshing only occurs after an SQLINSERT, to retrieve the default value provided by the database. Hibernate also maps the property as not insertable. The@ColumnDefaultHibernate annotation sets the default value of the column when Hibernate exports and generates the SQL schema DDL.
Temporal properties
The JPA specification requires that you annotate temporal properties with @Temporal to declare the accuracy of the SQL data type of the mapped column. The Java temporal types are java.util.Date, java.util.Calendar , java.sql.Date, java.sql.Time, and java.sql.Timestamp.
Mapping enumerations
public enum AuctionType {
HIGHEST_BID,
LOWEST_BID,
FIXED_PRICE
}
Without the @Enumerated annotation, Hibernate would store the ORDINAL position of the value. That is, it would store 1 for HIGHEST_BID, 2 for LOWEST_BID, and 3 for FIXED_PRICE.
Mapping embeddable components
Having “more classes than tables” is how Hibernate supports fine-grained domain models.
The rules are as follows:
- If the owning
@Entityof an embedded component is mapped with field access, either implicitly with@Idon a field or explicitly with@Access(AccessType .FIELD)on the class, all mapping annotations of the embedded component class are expected on fields of the component class. Hibernate expects annotations on the fields of theAddressclass and reads/writes the fields directly at runtime. Getter and setter methods onAddressare optional. - If the owning
@Entityof an embedded component is mapped with property access, either implicitly with@Idon a getter method or explicitly with@Access(AccessType.PROPERTY)on the class, all mapping annotations of the embedded component class are expected on getter methods of the component class. Hibernate then reads and writes values by calling getter and setter methods on the embeddable component class. - If the embedded property of the owning entity class—
User#homeAddressin the last example—is marked with@Access(AccessType.FIELD), Hibernate expects annotations on the fields of theAddressclass and access fields at runtime. - If the embedded property of the owning entity class—
User#homeAddressin the last example—is marked with@Access(AccessType.PROPERTY), Hibernate expects annotations on getter methods of theAddresclass and access getter and setter methods at runtime. - If
@Accessannotates the embeddable class itself, Hibernate will use the selected strategy for reading mapping annotations on the embeddable class and runtime access.
Overriding embedded attributes
Mapping nested embedded components
@Embeddable
public class Address {
@NotNull
@Column(nullable = false)
protected String street;
@NotNull
@AttributeOverrides(
@AttributeOverride(
name = "name",
column = @Column(name = "CITY", nullable = false)
)
)
protected City city;
// ...
}
Mapping Java and SQL types with converters
Built-in types
Java primitive types that map to SQL standard types
| Name | Java type | ANSI SQL type |
|---|---|---|
| integer | int, java.lang.Integer | INTEGER |
| long | long, java.lang.Long | BIGINT |
| short | short, java.lang.Short | SMALLINT |
| float | float, java.lang.Float | FLOAT |
| double | double, java.lang.Double | DOUBLE |
| byte | byte, java.lang.Byte | TINYINT |
| boolean | boolean, java.lang.Boolean | BOOLEAN |
| big_decimal | java.math.BigDecimal | NUMERIC |
| big_integer | java.math.BigInteger | NUMERIC |
Adapters for character and string values
| Name | Java type | ANSI SQL type |
|---|---|---|
| string | java.lang.String | VARCHAR |
| character | char[], Character[], java.lang.String | CHAR |
| yes_no | boolean, java.lang.Boolean | CHAR(1), 'Y' or 'N' |
| true_false | boolean, java.lang.Boolean | CHAR(1), 'T' or 'F' |
| class | java.lang.Class | VARCHAR |
| locale | java.util.Locale | VARCHAR |
| timezone | java.util.TimeZone | VARCHAR |
| currency | java.util.Currency | VARCHAR |
Date and time types
| Name | Java type | ANSI SQL type |
|---|---|---|
| date | java.util.Date, java.sql.Date | DATE |
| time | java.util.Date, java.sql.Time | TIME |
| timestamp | java.util.Date, java.sql.Timestamp | TIMESTAMP |
| calendar | java.util.Calendar | TIMESTAMP |
| calendar_date | java.util.Calendar | DATE |
| duration | java.time.Duration | BIGINT |
| instant | java.time.Instant | TIMESTAMP |
| localdatetime | java.time.LocalDateTime | TIMESTAMP |
| localdate | java.time.LocalDate | DATE |
| localtime | java.time.LocalTime | TIME |
| offsetdatetime | java.time.OffsetDateTime | TIMESTAMP |
| offsettime | java.time.OffsetTime | TIME |
| zoneddatetime | java.time.ZonedDateTime | TIMESTAMP |
Binary and large value types
| Name | Java type | ANSI SQL type |
|---|---|---|
| binary | byte[], java.lang.Byte[] | VARBINARY |
| text | java.lang.String | CLOB |
| clob | java.sql.Clob | CLOB |
| blob | java.sql.Blob | BLOB |
| serializable | java.io.Serializable | VARBINARY |
Hibernate initializes the property value right away, when the entity instance that holds the property variable is loaded. This is inconvenient when you have to deal with potentially large values, so you usually want to override this default mapping. The JPA specification has a convenient shortcut annotation for this purpose, @Lob:
@Entity
public class Item {
@Lob
protected byte[] image;
@Lob
protected String description;
// ...
}
This maps the byte[] to an SQL BLOB data type and the String to a CLOB. Unfortunately, you still don’t get lazy loading with this design. Hibernate would have to intercept field access and, for example, load the bytes of the image when you call someItem.getImage().
Alternatively, you can switch the type of property in your Java class. JDBC supports locator objects (LOBs) directly. If your Java property is java.sql.Clob or java .sql.Blob, you get lazy loading without bytecode instrumentation:
@Entity
public class Item {
@Lob
protected java.sql.Blob imageBlob;
@Lob
protected java.sql.Clob description;
// ...
These JDBC classes include behavior to load values on demand. When the owning entity instance is loaded, the property value is a placeholder, and the real value isn’t immediately materialized. Once you access the property, within the same transaction, the value is materialized or even streamed directly (to the client) without consuming temporary memory:
@Entity
public class Item {
@org.hibernate.annotations.Type(type = "yes_no")
protected boolean verified = false;
}
Creating custom JPA converters
CONVERTING BASIC PROPERTY VALUES
CONVERTING PROPERTIES OF COMPONENTS
abstract public class Zipcode {
protected String value;
public Zipcode(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Zipcode zipcode = (Zipcode) o;
return value.equals(zipcode.value);
}
@Override
public int hashCode() {
return value.hashCode();
}
}
public class GermanZipcode extends Zipcode {
public GermanZipcode(String value) {
super(value);
}
}
Mapping inheritance
There are four different strategies for representing an inheritance hierarchy:
- Use one table per concrete class and default runtime polymorphic behavior.
- Use one table per concrete class but discard polymorphism and inheritance relationships completely from the SQL schema. Use SQL
UNIONqueries for runtime polymorphic behavior. - Use one table per class hierarchy: enable polymorphism by denormalizing the SQL schema and relying on row-based discrimination to determine super/ subtypes.
- Use one table per subclass: represent is a (inheritance) relationships as has a (foreign key) relationships, and use SQL
JOINoperations.
Table per concrete class with implicit polymorphism
@MappedSuperclass
public abstract class BillingDetails {
@NotNull
protected String owner;
// ...
}
@Entity
@AttributeOverride(
name = "owner",
column = @Column(name = "CC_OWNER", nullable = false))
public class CreditCard extends BillingDetails {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
@NotNull
protected String cardNumber;
@NotNull
protected String expMonth;
@NotNull
protected String expYear;
// ...
}
The JPA query select bd from BillingDetails bd requires two SQL statements:
select
ID, OWNER, ACCOUNT, BANKNAME, SWIFT
from
BANKACCOUNT
select
ID, CC_OWNER, CARDNUMBER, EXPMONTH, EXPYEAR
from
CREDITCARD
Hibernate uses a separate SQL query for each concrete subclass. On the other hand, queries against the concrete classes are trivial and perform well—Hibernate uses only one of the statements.
Table per concrete class with unions
TABLE_PER_CLASS
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BillingDetails {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
@NotNull
protected String owner;
// ...
}
@Entity
public class CreditCard extends BillingDetails {
@NotNull
protected String cardNumber;
@NotNull
protected String expMonth;
@NotNull
protected String expYear;
// ...
}
For example, the query select bd from BillingDetails bd generates the following SQL statement:
select
ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER,
ACCOUNT, BANKNAME, SWIFT, CLAZZ_
from
( select
ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER,
null as ACCOUNT,
null as BANKNAME,
null as SWIFT,
1 as CLAZZ_
from
CREDITCARD
union all
select
id, OWNER,
null as EXPMONTH,
null as EXPYEAR,
null as CARDNUMBER,
ACCOUNT, BANKNAME, SWIFT,
2 as CLAZZ_
from
BANKACCOUNT
) as BILLINGDETAILS
- This
SELECTuses aFROM-clause subquery to retrieve all instances ofBillingDetailsfrom all concrete class tables. - A
unionrequires that the queries that are combined project over the same columns; hence, you have to pad and fill nonexistent columns withNULL. - Hibernate can use a
UNIONquery to simulate a single table as the target of the association mapping.
Table per class hierarchy
You can map an entire class hierarchy to a single table. This table includes columns for all properties of all classes in the hierarchy. The value of an extra type discrimina- tor column or formula identifies the concrete subclass represented by a particular row.
- The root class
BillingDetailsof the inheritance hierarchy is mapped to the tableBILLINGDETAILSautomatically. - Shared properties of the superclass can be
NOT NULLin the schema; every subclass instance must have a value.
SQL for select bd from BillingDetails bd:
select
ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER,
ACCOUNT, BANKNAME, SWIFT, BD_TYPE
from
BILLINGDETAILS
To query the CreditCard subclass, Hibernate adds a restriction on the discriminator column:
select
ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER
from
BILLINGDETAILS
where
BD_TYPE='CC'
Formulas for discrimination aren’t part of the JPA specification, but Hibernate has an extension annotation, @DiscriminatorFormula.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@org.hibernate.annotations.DiscriminatorFormula(
"case when CARDNUMBER is not null then 'CC' else 'BA' end"
)
public abstract class BillingDetails {
// ...
}
mapping relies on an SQL CASE/WHEN expression to determine whether a particular row represents a credit card or a bank account
The disadvantages of the table-per-class hierarchy strategy may be too serious for your design—considering denormalized schemas can become a major burden in the long term.
Table per subclass with joins
Every class/subclass that declares persistent properties—including abstract classes and even interfaces—has its own table.
The table of a concrete @Entity here contains columns only for each non-inherited property, declared by the subclass itself, along with a primary key that is also a foreign key of the superclass table.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class BillingDetails {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
@NotNull
protected String owner;
// ...
}
@Entity
public class BankAccount extends BillingDetails {
@NotNull
protected String account;
@NotNull
protected String bankname;
@NotNull
protected String swift;
// ...
}
@Entity
@PrimaryKeyJoinColumn(name = "CREDITCARD_ID")
public class CreditCard extends BillingDetails {
@NotNull
protected String cardNumber;
@NotNull
protected String expMonth;
@NotNull
protected String expYear;
// ...
}
- In subclasses, you don’t need to specify the join column if the primary key column of the subclass table has (or is supposed to have) the same name as the primary key column of the superclass table.
- Hibernate knows how to join the tables if you want to retrieve instances of
BankAccount. You can specify the column name explicitly by@PrimaryKeyJoinColumn. - The primary key columns of the
BANKACCOUNTandCREDITCARDtables each also have a foreign key constraint referencing the primary key of theBILLINGDETAILStable.
Hibernate relies on an SQL outer join for select bd from BillingDetails bd:
select
BD.ID, BD.OWNER,
CC.EXPMONTH, CC.EXPYEAR, CC.CARDNUMBER,
BA.ACCOUNT, BA.BANKNAME, BA.SWIFT,
case
when CC.CREDITCARD_ID is not null then 1
when BA.ID is not null then 2
when BD.ID is not null then 0
end
from
BILLINGDETAILS BD
left outer join CREDITCARD CC on BD.ID=CC.CREDITCARD_ID
left outer join BANKACCOUNT BA on BD.ID=BA.ID
The SQL CASE ... WHEN clause detects the existence (or absence) of rows in the subclass tables CREDITCARD and BANKACCOUNT , so Hibernate can determine the concrete subclass for a particular row of the BILLINGDETAILS table.
For a narrow subclass query like select cc from CreditCard cc, Hibernate uses an inner join:
select
CREDITCARD_ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER
from
CREDITCARD
inner join BILLINGDETAILS on CREDITCARD_ID=ID
Queries always require a join across many tables, or many sequential reads.
Mixing inheritance strategies
The @SecondaryTable and @Column annotations group some properties and tell Hibernate to get them from a secondary table.
Remember that InheritanceType.SINGLE_TABLE enforces all columns of subclasses to be nullable.
At runtime, Hibernate executes an outer join to fetch BillingDetails and all subclass instances polymorphically:
select
ID, OWNER, ACCOUNT, BANKNAME, SWIFT,
EXPMONTH, EXPYEAR, CARDNUMBER,
BD_TYPE
from
BILLINGDETAILS
left outer join CREDITCARD on ID=CREDITCARD_ID
Inheritance of embeddable classes
@MappedSuperclass
public abstract class Measurement {
@NotNull
protected String name;
@NotNull
protected String symbol;
// ...
}
@Embeddable
@AttributeOverrides({
@AttributeOverride(name = "name",
column = @Column(name = "DIMENSIONS_NAME")),
@AttributeOverride(name = "symbol",
column = @Column(name = "DIMENSIONS_SYMBOL"))
})
public class Dimensions extends Measurement {
@NotNull
protected BigDecimal depth;
@NotNull
protected BigDecimal height;
@NotNull
protected BigDecimal width;
// ...
}
@Embeddable
@AttributeOverrides({
@AttributeOverride(name = "name",
column = @Column(name = "WEIGHT_NAME")),
@AttributeOverride(name = "symbol",
column = @Column(name = "WEIGHT_SYMBOL"))
})
public class Weight extends Measurement {
<enter/>
@NotNull
@Column(name = "WEIGHT")
protected BigDecimal value;
// ...
}
@Entity
public class Item {
protected Dimensions dimensions;
protected Weight weight;
// ...
}
Choosing a strategy
Here are some rules of thumb:
- If you don’t require polymorphic associations or queries, lean toward
table-per-concreteclass—in other words, if you never or rarelyselect bd from BillingDetails bdand you have no class that has an association toBillingDetails. An explicitUNION-based mapping withInheritanceType.TABLE_PER_CLASSshould be preferred, because (optimized) polymorphic queries and associations will then be possible later. - If you do require polymorphic associations (an association to a superclass, hence to all classes in the hierarchy with dynamic resolution of the concrete class at runtime) or queries, and subclasses declare relatively few properties (particularly if the main difference between subclasses is in their behavior), lean toward
InheritanceType.SINGLE_TABLE. Your goal is to minimize the number of nullable columns and to convince yourself (and your DBA) that a denormalized schema won’t create problems in the long run. - If you do require polymorphic associations or queries, and subclasses declare many (non-optional) properties (subclasses differ mainly by the data they hold), lean toward
InheritanceType.JOINED. Alternatively, depending on the width and depth of your inheritance hierarchy and the possible cost of joins versus unions, useInheritanceType.TABLE_PER_CLASS. This decision might require evaluation of SQL execution plans with real data.
Polymorphic associations
Polymorphic many-to-one associations
@Entity
@Table(name = "USERS")
public class User {
@ManyToOne(fetch = FetchType.LAZY)
protected BillingDetails defaultBilling;
// ...
}
CreditCard cc = new CreditCard(
"John Doe", "1234123412341234", "06", "2015"
);
User johndoe = new User("johndoe");
johndoe.setDefaultBilling(cc);
em.persist(cc);
em.persist(johndoe);
Polymorphic collections
@Entity
@Table(name = "USERS")
public class User {
@OneToMany(mappedBy = "user")
protected Set<BillingDetails> billingDetails = new HashSet<>();
// ...
}
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BillingDetails {
@ManyToOne(fetch = FetchType.LAZY)
protected User user;
// ...
}
The BillingDetails class hierarchy can be mapped with TABLE_PER_CLASS, SINGLE_TABLE, or a JOINED inheritance type. Hibernate is smart enough to use the right SQL queries, with either JOIN or UNION operators, when loading the collection elements.
Mapping collections and entity associations
You create and map this collection property to do the following:
- Execute the SQL query
SELECT * from IMAGE where ITEM_ID = ?automatically when you callsomeItem.getImages(). You don’t have to manually write and execute a query with theEntityManagerto load data. - Avoid saving each Image with
entityManager.persist(). If you have a mapped collection, adding the Image to the collection withsomeItem.getImages() .add()will make it persistent automatically when theItemis saved. This cascading persistence is convenient because you can save instances without callingEntityManager. - Have a dependent life cycle of
Images. When you delete anItem, Hibernate deletes all attachedImageswith an extra SQLDELETE. You don’t have to worry about the life cycle of images and cleaning up orphans (assuming your database foreign key constraint doesn’t ONDELETE CASCADE). The JPA provider handles the composition life cycle.
Without extending Hibernate, you can choose from the following collections:
- A
java.util.Setproperty, initialized with ajava.util.HashSet. The order of elements isn’t preserved, and duplicate elements aren’t allowed. All JPA providers support this type. - A
java.util.SortedSetproperty, initialized with ajava.util.TreeSet. This collection supports stable order of elements: sorting occurs in-memory, after Hibernate loads the data. This is a Hibernate-only extension; other JPA providers may ignore the “sorted” aspect of the set. - A
java.util.Listproperty, initialized with ajava.util.ArrayList. Hibernate preserves the position of each element with an additional index column in the database table. All JPA providers support this type. - A
java.util.Collectionproperty, initialized with ajava.util.ArrayList. This collection has bag semantics; duplicates are possible, but the order of elements isn’t preserved. All JPA providers support this type. - A
java.util.Mapproperty, initialized with ajava.util.HashMap. The key and value pairs of a map can be preserved in the database. All JPA providers support this type. - A
java.util.SortedMapproperty, initialized with ajava.util.TreeMap. It sup- ports stable order of elements: sorting occurs in-memory, after Hibernate loads the data. This is a Hibernate-only extension; other JPA providers may ignore the “sorted” aspect of the map. - Hibernate supports persistent arrays, but JPA doesn’t. They’re rarely used, and we won’t show them in this book: Hibernate can’t wrap array properties, so many benefits of collections, such as on-demand lazy loading, won’t work. Only use persistent arrays in your domain model if you’re sure you won’t need lazy loading.
Mapping a set
Mapping an identifier bag
Mapping a list
Mapping a map
Sorted and ordered collections
@Entity
public class Item {
@ElementCollection
@CollectionTable(name = "IMAGE")
@MapKeyColumn(name = "FILENAME")
@Column(name = "IMAGENAME")
@org.hibernate.annotations.SortComparator(ReverseStringComparator.class)
protected SortedMap<String, String> images =
new TreeMap<String, String>();
// ...
}
@Entity
public class Item {
@ElementCollection
@CollectionTable(name = "IMAGE")
@Column(name = "FILENAME")
@org.hibernate.annotations.SortNatural
protected SortedSet<String> images = new TreeSet<String>();
// ...
}
@Entity
public class Item {
@ElementCollection
@CollectionTable(name = "IMAGE")
@MapKeyColumn(name = "FILENAME")
@Column(name = "IMAGENAME")
@org.hibernate.annotations.OrderBy(clause = "FILENAME desc")
protected Map<String, String> images = new LinkedHashMap<String, String>();
// ...
}
Collections of components
Mapping entity associations
Advanced entity association mapping
One-to-one associations
Sharing a primary key
@Entity
public class Address {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
@NotNull
protected String street;
@NotNull
protected String zipcode;
@NotNull
protected String city;
// ...
}
After persisting the Address, you take its generated identifier value and set it on the User before saving it, too.
The last line of this example is optional: your code now expects a value when calling someUser.getShippingAddress(), so you should set it. Hibernate won’t give you an error if you forget this last step.
There are three problems with the mapping and code:
- You have to remember that the
Addressmust be saved first and then get its identifier value after the call topersist(). This is only possible if theAddressentity has an identifier generator that produces values onpersist()before theINSERT, as we discussed in section 4.2.5. Otherwise,someAddress.getId()returnsnull, and you can’t manually set the identifier value of theUser. - Lazy loading with proxies only works if the association is non-optional. This is often a surprise for developers new to JPA. The default for
@OneToOneisFetchType.EAGER: when Hibernate loads aUser, it loads theshippingAddressright away. Conceptually, lazy loading with proxies only makes sense if Hibernate knows that there is a linkedshippingAddress. If the property were nullable, Hibernate would have to check in the database whether the property value isNULL, by querying theADDRESStable. If you have to check the database, you might as well load the value right away, because there would be no benefit in using a proxy. - The one-to-one association is unidirectional; sometimes you need bidirectional navigation.
The foreign primary key generator
@Entity
@Table(name = "USERS")
public class User {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
@OneToOne(
mappedBy = "user",
cascade = CascadeType.PERSIST
)
protected Address shippingAddress;
// ...
}
Using a foreign key join column
Using a join table
Shipment someShipment = new Shipment();
em.persist(someShipment);
Item someItem = new Item("Some Item");
em.persist(someItem);
Shipment auctionShipment = new Shipment(someItem);
em.persist(auctionShipment);