JPA With Hibernate - Mapping strategies

191 阅读15分钟

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, or Category instance. 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, Address and MonetaryAmount.

Distinguishing entities and value types

image.png

  • Shared references —Avoid shared references to value type instances when you write your POJO classes. For example, make sure only one User can reference an Address. You can make Address immutable with no public setUser() method and enforce the relationship with a public constructor that has a User argument. 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 a User is deleted, its Address dependency 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 obsolete Address. 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 as String and Integer) 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 == b operator. 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 by java.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 named HIBERNATE_SEQUENCE in your database. The sequence will be called separately before every INSERT , 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 on INSERT , 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, before INSERTs. The default table name is HIBERNATE_SEQUENCES with columns SEQUENCE_NAME and SEQUENCE_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:

image.png

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.

image.png

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[], or Character[], 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 the Address and MonetaryAmount embeddable classes of CaveatEmptor.
  • 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.

image.png

@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 SQL UPDATE or INSERT . The example assumes that a database trigger will keep the lastModified property 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 the INSERT or UPDATE statements, and you let the database generate the value.
  • INSERT: refreshing only occurs after an SQL INSERT , to retrieve the default value provided by the database. Hibernate also maps the property as not insertable. The @ColumnDefault Hibernate 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.

image.png

Mapping enumerations

public enum AuctionType {
    HIGHEST_BID,
    LOWEST_BID,
    FIXED_PRICE
}

image.png

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

image.png

image.png

Having “more classes than tables” is how Hibernate supports fine-grained domain models.

image.png

image.png

image.png

The rules are as follows:

  • If the owning @Entity of an embedded component is mapped with field access, either implicitly with @Id on 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 the Address class and reads/writes the fields directly at runtime. Getter and setter methods on Address are optional.
  • If the owning @Entity of an embedded component is mapped with property access, either implicitly with @Id on 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#homeAddress in the last example—is marked with @Access(AccessType.FIELD), Hibernate expects annotations on the fields of the Address class and access fields at runtime.
  • If the embedded property of the owning entity class—User#homeAddress in the last example—is marked with @Access(AccessType.PROPERTY), Hibernate expects annotations on getter methods of the Addres class and access getter and setter methods at runtime.
  • If @Access annotates the embeddable class itself, Hibernate will use the selected strategy for reading mapping annotations on the embeddable class and runtime access.

Overriding embedded attributes

image.png

Mapping nested embedded components

image.png

@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;

    // ...
}

image.png

Mapping Java and SQL types with converters

Built-in types

Java primitive types that map to SQL standard types
NameJava typeANSI SQL type
integerint, java.lang.IntegerINTEGER
longlong, java.lang.LongBIGINT
shortshort, java.lang.ShortSMALLINT
floatfloat, java.lang.FloatFLOAT
doubledouble, java.lang.DoubleDOUBLE
bytebyte, java.lang.ByteTINYINT
booleanboolean, java.lang.BooleanBOOLEAN
big_decimaljava.math.BigDecimalNUMERIC
big_integerjava.math.BigIntegerNUMERIC
Adapters for character and string values
NameJava typeANSI SQL type
stringjava.lang.StringVARCHAR
characterchar[], Character[], java.lang.StringCHAR
yes_noboolean, java.lang.BooleanCHAR(1), 'Y' or 'N'
true_falseboolean, java.lang.BooleanCHAR(1), 'T' or 'F'
classjava.lang.ClassVARCHAR
localejava.util.LocaleVARCHAR
timezonejava.util.TimeZoneVARCHAR
currencyjava.util.CurrencyVARCHAR
Date and time types
NameJava typeANSI SQL type
datejava.util.Date, java.sql.DateDATE
timejava.util.Date, java.sql.TimeTIME
timestampjava.util.Date, java.sql.TimestampTIMESTAMP
calendarjava.util.CalendarTIMESTAMP
calendar_datejava.util.CalendarDATE
durationjava.time.DurationBIGINT
instantjava.time.InstantTIMESTAMP
localdatetimejava.time.LocalDateTimeTIMESTAMP
localdatejava.time.LocalDateDATE
localtimejava.time.LocalTimeTIME
offsetdatetimejava.time.OffsetDateTimeTIMESTAMP
offsettimejava.time.OffsetTimeTIME
zoneddatetimejava.time.ZonedDateTimeTIMESTAMP
 Binary and large value types
NameJava typeANSI SQL type
binarybyte[], java.lang.Byte[]VARBINARY
textjava.lang.StringCLOB
clobjava.sql.ClobCLOB
blobjava.sql.BlobBLOB
serializablejava.io.SerializableVARBINARY

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:

image.png

image.png

@Entity
public class Item {

    @org.hibernate.annotations.Type(type = "yes_no")
    protected boolean verified = false;
}

Creating custom JPA converters

image.png

image.png

image.png

CONVERTING BASIC PROPERTY VALUES

image.png

image.png

CONVERTING PROPERTIES OF COMPONENTS

image.png

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);
    }
}

image.png

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 UNION queries 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 JOIN operations.

Table per concrete class with implicit polymorphism

image.png

@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 SELECT uses a FROM-clause subquery to retrieve all instances of BillingDetails from all concrete class tables.
  • A union requires that the queries that are combined project over the same columns; hence, you have to pad and fill nonexistent columns with NULL.
  • Hibernate can use a UNION query 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.

image.png

image.png

image.png

  • The root class BillingDetails of the inheritance hierarchy is mapped to the table BILLINGDETAILS automatically.
  • Shared properties of the superclass can be NOT NULL in 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.

image.png

@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 BANKACCOUNT and CREDITCARD tables each also have a foreign key constraint referencing the primary key of the BILLINGDETAILS table.

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

image.png

image.png

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

image.png

@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-concrete class—in other words, if you never or rarely select bd from BillingDetails bd and you have no class that has an association to BillingDetails. An explicit UNION-based mapping with InheritanceType.TABLE_PER_CLASS should 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, use InheritanceType.TABLE_PER_CLASS. This decision might require evaluation of SQL execution plans with real data.

Polymorphic associations

image.png

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);

image.png

image.png

image.png

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

image.png

You create and map this collection property to do the following:

  • Execute the SQL query SELECT * from IMAGE where ITEM_ID = ? automatically when you call someItem.getImages(). You don’t have to manually write and execute a query with the EntityManager to load data.
  • Avoid saving each Image with entityManager.persist(). If you have a mapped collection, adding the Image to the collection with someItem.getImages() .add() will make it persistent automatically when the Item is saved. This cascading persistence is convenient because you can save instances without calling EntityManager .
  • Have a dependent life cycle of Images. When you delete an Item, Hibernate deletes all attached Images with an extra SQL DELETE. You don’t have to worry about the life cycle of images and cleaning up orphans (assuming your database foreign key constraint doesn’t ON DELETE CASCADE). The JPA provider handles the composition life cycle.

Without extending Hibernate, you can choose from the following collections:

  • A java.util.Set property, initialized with a java.util.HashSet. The order of elements isn’t preserved, and duplicate elements aren’t allowed. All JPA providers support this type.
  • A java.util.SortedSet property, initialized with a java.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.List property, initialized with a java.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.Collection property, initialized with a java.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.Map property, initialized with a java.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.SortedMap property, initialized with a java.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

image.png

image.png

Mapping an identifier bag

image.png

image.png

Mapping a list

image.png

image.png

Mapping a map

image.png

image.png

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>();

    // ...
}


image.png

image.png

@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

image.png

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;

    // ...
}

image.png

image.png

image.png

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 Address must be saved first and then get its identifier value after the call to persist(). This is only possible if the Address entity has an identifier generator that produces values on persist() before the INSERT, as we discussed in section 4.2.5. Otherwise, someAddress.getId() returns null, and you can’t manually set the identifier value of the User.
  • 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 @OneToOne is FetchType.EAGER: when Hibernate loads a User, it loads the shippingAddress right away. Conceptually, lazy loading with proxies only makes sense if Hibernate knows that there is a linked shippingAddress. If the property were nullable, Hibernate would have to check in the database whether the property value is NULL, by querying the ADDRESS table. 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;
    // ...
}

image.png

image.png

image.png

Using a foreign key join column

image.png

image.png

image.png

Using a join table

image.png

image.png

Shipment someShipment = new Shipment();
em.persist(someShipment);

Item someItem = new Item("Some Item");
em.persist(someItem);

Shipment auctionShipment = new Shipment(someItem);
em.persist(auctionShipment);