The Java Persistence API (JPA) is the Java standard to create, retrieve, update, and delete (CRUD) data from relational databases.
Introducing JPA
Impedance mismatch: differences between the object and relational worlds
| Domain model (Java) | Relational model (database) |
|---|---|
| Objects, classes | Tables, rows |
| Attributes, properties | Columns |
| Identity | Primary key |
| Relationship/reference to other entity | Foreign key |
| Inheritance/polymorphism | Doesn’t exist |
| Methods | Indirect parallel to SQL logic, stored procedures, triggers |
| Code is portable | Not necessarily portable, depending on vendor |
Releationship between EJB 3 and JPA
When Java EE 5 was released, the EJB container was completely rearchitected, moving to a lightweight POJO, annotation, and convention-over-configuration design. With EE 5 came the first introduction to the Java Persistence API. At the time, JPA was part of the EJB specification.
But as the JPA specification grew and become more feature-rich, it was eventually rolled into its own Java specification request (JSR).
Domain modeling
The domain model is made up of Java objects representing your application’s data and the relationships or associations among the data.
Domain objects as Java classes
Implementing domain objects with JPA
@Entity annotation
@Entity: annotation marks a POJO domain object as an entity that can be managed by JPA.
@Entity
public class Category {
//...
public Category() { /**/ }
public Category(String name) { /**/ }
//...
}
@Entity
public abstract class User {
// ...
String userId;
String username;
String email;
byte[] picture;
// ...
}
@Entity
public class Seller extends User { /**/ }
@Entity
public class Bidder extends User { /**/ }
Specifying the table
Mapping an entity to a single table
@Table specifies the table containing the columns to which the entity is mapped.
@Target(TYPE)
@Retention(RUNTIME)
public @interface Table {
String name() default "";
String catalog() default "";
String schema() default "";
Index[] indexes() default {};
UniqueConstraint[] uniqueConstraints() default {};
}
The @Table annotation is optional. By default, JPA will assume the name of the table is the name of the @Entity class itself.
@Table(name="ITEM_CATEGORY")
public class Category
The catalog and schema elements are there if you need to further specify the location of the table in the database.
@Table(name="ITEM_CATEGORY", schema="ACTIONBAZAAR"))
public class Category
The uniqueConstraints element tells the persistence provider which columns on the auto-generated table should have a unique constraint. Again there’s no guarantee the persistence provider will support auto-generation, and if it does, there’s no guarantee the uniqueConstraints element will be applied.
Default data source
java:comp/DefaultDataSource
@Resource(lookup="java:comp/DefaultDataSource")
DataSource defaultDs;
Mapping an entity to multiple tables
The @SecondaryTables (plural) and @SecondaryTable (singular) annotations allow JPA to handle the cases where an entity’s data must come from two or more tables.
@Target({ TYPE })
@Retention(RUNTIME)
public @interface SecondaryTable {
String name();
String catalog() default "";
String schema() default "";
PrimaryKeyJoinColumn[] pkJoinColumns() default {};
UniqueConstraint[] uniqueConstraints() default {};
}
@Entity
@Table(name="USERS")
@SecondaryTable(name="USER_PICTURES", pkJoinColumns=@PrimaryKeyJoinColumn(name="USER_ID"))
public class User
Mapping the columns
@Column annotation
By convention, JPA saves all Java object properties that have JavaBeans-style public or protected setters and getters and by default assumes the column name is the same as the property name.
Because the name of the email property is email, by default JPA assumes an EMAIL column exists in the USERS table.
@Columnattributes
@Target ({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface Column {
String name() default "";
boolean unique() default false;
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
String table() default "";
int length() default 255;
int precision() default 0;
int scale() default 0;
}
Field versus property-based persistence
The other choice you have for the placement of your annotations is on the getter methods of the class (annotations on setter methods are ignored). This is known as property-based access. Here’s an example:
@Column(name="USER_ID")
public Long getId() { return id; }
public Long setId(Long id) {
this.id = (id <= 0) ? 0 : id;
}
In this example, notice that the annotation is on the getter method and that the setter method has some simple business logic. When you use property-based access, JPA will get the value by using the getter method and set the value by using the setter method.
@Entity
@Access(FIELD)
public class Seller extends User {
//...the rest of seller omitted for brevity
@Transient
private double creditWorth;
@Column(name="CREDIT_WORTH")
@Access(AccessType.PROPERTY)
public double getCreditWorth() { return creditWorth; }
public void setCreditWorth(double cw) {
creditWorth = (cw <= 0) ? 50.0 : cw;
}
}
Define a transient field
public class User {
@Column(name="DATE_OF_BIRTH")
Date birthday;
@Transient
int age;
//...Other getters and setters ommitted for brevity
public void setBirthday(Date birthday) {
this.birthday = birthday;
// calculate age...
}
}
Temporal types
Temporal types are all about dates and times. Most databases support a few different temporal data types with different granularity levels corresponding to DATE (storing day, month, and year), TIME (storing just time and not day, month, or year), and TIMESTAMP (storing time, day, month, and year). The @Temporal annotation specifies which of these data types you want.
@Temporal(TemporalType.DATE)
protected java.util.Date creationDate;
@Temporal(TemporalType.DATE)
protected java.util.Calendar creationDate;
// No annotation
protected javax.sql.Date creationDate;
Enumerated types
The @Enumerated annotation may be used in either of the following ways:
public enum UserType { SELLER, BIDDER, CSR, ADMIN }
@Enumerated(EnumType.STRING)
protected UserType userType1;
@Enumerated(EnumType.ORDINAL)
protected UserType userType2;
The difference between the two is that one example uses EnumType.STRING and the other uses EnumType.ORDINAL. EnumType controls what data type is stored in the database for the enumeration.
When @Enumerated(EnumType.STRING) is used, JPA will persist this String representation to the database.
User-Type.SELLER is represented by SELLER, UserType.BIDDER by BIDDER, and so on.
UserType.SELLER is index 0 and may be retrieved from the enumeration by this index value by User-Type.values()[0];.
The JPA default is to save by ordinal value. So by default JPA will store User-Type.ADMIN as the number 3, not the String "ADMIN".
Collections
@ElementCollectionannotation can work with embeddable objects as well.
@Embeddable
public class Address {
@Column(name="HOME_STREET")
private String street;
@Column(name="HOME_CITY")
private String city;
@Column(name="HOME_STATE")
private String state;
@Column(name="HOME_ZIP")
private String zipCode;
}
@Entity
@Table(name="USERS")
public class User {
@ElementCollection
@CollectionTable(name="HOMES")
private Set<Address> shippingAddresses;
}
Specifying entity identify
@Id annotation
- The
@Idproperty can support primitives (int,long,double, and the like), and in these cases JPA performs a direct equality comparison. - The
@Idproperty also supportsSerializabletypes (String,Date, and so on), and in these cases JPA will use theequals()method. - It’s important to remember that the
@Idannotation will work only if the primary key is a single column of the table.
Most modern projects will add a column to the table for this very purpose. But legacy projects may not have a single-column primary key but instead rely on multiple columns to uniquely identify a row in the table. JPA has two ways of handling multiple column primary keys. We’ll look at these next.
#IdClass annotation
The first way JPA handles multiple-column primary keys is with the @IdClass annotation.
You now have the name property and the createdDate property c both annotated with @Id, and this tells JPA that the two together uniquely identify the category. But what JPA doesn’t know yet is how to use these two properties when comparing two Category objects for equality. To do this, you introduce a new class, CategoryKey , as shown in the next listing.
The last thing you need to do is let Category know to use CategoryKey:
@Entity
@IdClass(CategoryKey.class)
public class Category {
// ...
}
- When JPA retrieves data from the
CATEGORYtable and creates aCategoryobject, thenameandcreatedDateproperties are set with data from their corresponding columns in the table. - When the time comes for JPA to determine if two
Categoryobjects are the same, JPA will create a new instance ofCategoryKeyfor each of theCategoryobjects it’s comparing. JPA will copy thenameandcreatedDateproperty values from theCategoryobjects into theCategoryKeyobjects. - Finally, JPA will use the
equals()method onCategoryKeyto see if the twoCategoryKeyobjects are the same. If they’re equal, JPA then assumes the twoCategoryobjects are equal.
@EmbeddedId annotation
Using the @EmbeddedId is very similar to using @IdClass, but @EmbeddedId takes advantage of the @Embeddable annotation and turns your domain model object into a composite.
Generating primary keys
AutoIdentitySequenceTableCode
Auto
Auto frees the developer from any special database work and leaves the generation of the primary key to whatever defaults are configured for the database.
Identity
The identity strategy makes use of a special database column type that maintains its own auto-incrementing number to uniquely identify rows in the table.
- Microsoft SQL Server,
IDENTITY; - MySQL,
AUTO_INCREMENT; - PostgreSQL,
SERIAL; - Oracle does have support for this column type
When data is inserted into the table, the identity field will automatically increment and store that value as the row’s primary key.
Sequence
The sequence strategy uses a database sequence to get auto-incrementing unique numbers.
- First create the sequence
CREATE SEQUENCE USER_SEQUENCE START WITH 1 INCREMENT BY 10;
- Next use
@SequenceGeneratorto configure a connection to the sequence; - Then configure
@GeneratedValueto use this sequence generator.
A @SequenceGenerator is sharable across the entire persistence unit. This means a @SequenceGenerator doesn’t need to be defined in the class it’s used. Any @SequenceGenerator is shared among all entities, so keep that in mind when assigning internal JPA names to ensure they’re unique.
Table
The table strategy uses a small database table to get auto-incrementing unique numbers. Typically the table has two columns in it.
- The first column is a unique name for a sequence.
- The second column is the sequence value.
To configure JPA to use the table strategy,:
- First need a table
CREATE TABLE SEQUENCE_GENERATOR_TABLE (
SEQUENCE_NAME VARCHAR2(80) NOT NULL,
SEQUENCE_VALUE NUMBER(15) NOT NULL,
PRIMARY KEY (SEQUENCE_NAME));
- Manually insert a sequence and its initial value
- Next, use
@TableGeneratorto configure a connection to the table, and then configure@GeneratedValueto use this sequence generator.
Remember that @TableGenerator is sharable across the entire persistence unit. This means a @TableGenerator doesn’t need to be defined in the class it’s used. Any @TableGenerator is shared among all entities, so keep that in mind when assigning internal JPA names to ensure they’re unique.
Code
The code strategy is for situations when you don’t want the database to auto-generate a primary key. Instead, you want to generate the primary key yourself in your application’s code.
Use the @PrePersist lifecycle annotation to set the value of the primary-key property before the data is inserted into the database.
Entity releationships
One-to-one relationships
@OneToOne
Unidirectional One-To-One
Attributes of the @OneToOne annotation
| Attribute | Description |
|---|---|
targetEntity | Class of the object to use in the relationship. Useful if the relationships are defined by interfaces and there are multiple implementing classes. |
cascade | Database relationship changes that must be cascaded down to the related data. |
fetch | Controls when the related data is populated. |
optional | Specifies if the relationship is optional (see listing 9.18). |
mappedBy | Specifies the entity that owns the relationship. This element is only specified on the non-owning side of the relationship (see listing 9.18). |
Bidrectional One-To-One
The optional= "false" configuration tells JPA that BillingInfo can’t exist without a User.
One-to-many and many-toone relationships
Many-to-many relationships
Summary of JPA entity relationship annotations
| Attribute | @OneToOne | @OneToMany | @ManyToOne | @ManyToMany |
|---|---|---|---|---|
targetEntity | Yes | Yes | Yes | Yes |
cascade | Yes | Yes | Yes | Yes |
fetch | Yes | Yes | Yes | Yes |
optional | Yes | No | Yes | Yes |
mappedBy | Yes | Yes | No | Yes |
Mapping inheritance
Single-table startegy
Joined-tables strategy
Table-per-class strategy
Summary of OO hierarchy inheritance mapping strategies
| Feature | Single-table | Joined-tables | Table-per-class |
|---|---|---|---|
| Table support | One table for all classes in the entity hierarchy:-Mandatory columns may be nullable. -The table grows when more subclasses are added. | One for the parent class, and each subclass has a separate table to store polymorphic properties. Mapped tables are normalized. | One table for each concrete class in the entity hierarchy. |
| Uses discriminator column? | Yes | Yes | No |
| SQL generated for retrieval of entity hierarchy | Simple SELECT. | SELECT clause joining multiple tables. | One SELECT for each subclass or UNION of SELECT. |
SQL for insert and update | Single INSERT or UPDATE for all entities in the hierarchy. | Multiple INSERT, UPDATE: one for the root class and one for each involved subclass. | One INSERT or UPDATE for every subclass. |
| Polymorphic relationship | Good | Good | Poor |
| Polymorphic queries | Good | Good | Poor |