EJB 3 In Action - Using EJB with JPA and CDI - JPA entities

239 阅读6分钟

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, classesTables, rows
Attributes, propertiesColumns
IdentityPrimary key
Relationship/reference to other entityForeign key
Inheritance/polymorphismDoesn’t exist
MethodsIndirect parallel to SQL logic, stored procedures, triggers
Code is portableNot 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.

image.png

image.png

Domain objects as Java classes

image.png

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) { /**/ }
    //...
}

image.png

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

image.png

@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

image.png

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.

  • @Column attributes
@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

image.png

  • @ElementCollection annotation 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 @Id property can support primitives (int, long, double, and the like), and in these cases JPA performs a direct equality comparison.
  • The @Id property also supports Serializable types (String, Date, and so on), and in these cases JPA will use the equals() method.
  • It’s important to remember that the @Id annotation 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.

image.png

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.

image.png

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 CATEGORY table and creates a Category object, the name and createdDate properties are set with data from their corresponding columns in the table.
  • When the time comes for JPA to determine if two Category objects are the same, JPA will create a new instance of CategoryKey for each of the Category objects it’s comparing. JPA will copy the name and createdDate property values from the Category objects into the CategoryKey objects.
  • Finally, JPA will use the equals() method on CategoryKey to see if the two CategoryKey objects are the same. If they’re equal, JPA then assumes the two Category objects 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.

image.png

image.png

Generating primary keys

  • Auto
  • Identity
  • Sequence
  • Table
  • Code

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.

image.png

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.

image.png

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 @SequenceGenerator to configure a connection to the sequence;
  • Then configure @GeneratedValue to use this sequence generator.

image.png

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

image.png

  • Next, use @TableGenerator to configure a connection to the table, and then configure @GeneratedValue to use this sequence generator.

image.png

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.

image.png

Entity releationships

One-to-one relationships

  • @OneToOne

Unidirectional One-To-One

image.png

Attributes of the @OneToOne annotation
AttributeDescription
targetEntityClass of the object to use in the relationship. Useful if the relationships are defined by interfaces and there are multiple implementing classes.
cascadeDatabase relationship changes that must be cascaded down to the related data.
fetchControls when the related data is populated.
optionalSpecifies if the relationship is optional (see listing 9.18).
mappedBySpecifies 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

image.png

The optional= "false" configuration tells JPA that BillingInfo can’t exist without a User.

One-to-many and many-toone relationships

image.png

Many-to-many relationships

image.png

Summary of JPA entity relationship annotations
Attribute@OneToOne@OneToMany@ManyToOne@ManyToMany
targetEntityYesYesYesYes
cascadeYesYesYesYes
fetchYesYesYesYes
optionalYesNoYesYes
mappedByYesYesNoYes

Mapping inheritance

Single-table startegy

image.png

image.png

Joined-tables strategy

image.png

image.png

Table-per-class strategy

image.png

image.png

Summary of OO hierarchy inheritance mapping strategies
FeatureSingle-tableJoined-tablesTable-per-class
Table supportOne 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?YesYesNo
SQL generated for retrieval of entity hierarchySimple SELECT.SELECT clause joining multiple tables.One SELECT for each subclass or UNION of SELECT.
SQL for insert and updateSingle 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 relationshipGoodGoodPoor
Polymorphic queriesGoodGoodPoor