Part Six explores the Java Persistence API.
The Java Persistence API provides an object/relational mapping facility to Java developers for managing relational data in Java applications. Java Persistence consists of four areas:
The Java Persistence API
The query language
The Java Persistence Criteria API
Object/relational mapping metadata
An entity is a lightweight persistence domain object. Typically an entity represents a table in a relational database, and each entity instance corresponds to a row in that table. The primary programming artifact of an entity is the entity class, although entities can use helper classes.
The persistent state of an entity is represented either through persistent fields or persistent properties. These fields or properties use object/relational mapping annotations to map the entities and entity relationships to the relational data in the underlying data store.
An entity class must follow these requirements:
The class must be annotated with the javax.persistence.Entity annotation.
The class must have a public or protected, no-argument constructor. The class may have other constructors.
The class must not be declared final. No methods or persistent instance variables must be declared final.
If an entity instance is passed by value as a detached object, such as through a session bean’s remote business interface, the class must implement the Serializable interface.
Entities may extend both entity and non-entity classes, and non-entity classes may extend entity classes.
Persistent instance variables must be declared private, protected, or package-private, and can only be accessed directly by the entity class’s methods. Clients must access the entity’s state through accessor or business methods.
The persistent state of an entity can be accessed either through the entity’s instance variables or through JavaBeans-style properties. The fields or properties must be of the following Java language types:
Java primitive types
java.lang.String
Other serializable types including:
Wrappers of Java primitive types
java.math.BigInteger
java.math.BigDecimal
java.util.Date
java.util.Calendar
java.sql.Date
java.sql.Time
java.sql.TimeStamp
User-defined serializable types
byte[]
Byte[]
char[]
Character[]
Enumerated types
Other entities and/or collections of entities
Embeddable classes
Entities may use persistent fields, persistent properties, or a combination of both. If the mapping annotations are applied to the entity’s instance variables, the entity uses persistent fields. If the mapping annotations are applied to the entity’s getter methods for JavaBeans-style properties, the entity uses persistent properties.
If the entity class uses persistent fields, the Persistence runtime accesses entity class instance variables directly. All fields not annotated javax.persistence.Transient or not marked as Java transient will be persisted to the data store. The object/relational mapping annotations must be applied to the instance variables.
If the entity uses persistent properties, the entity must follow the method conventions of JavaBeans components. JavaBeans-style properties use getter and setter methods that are typically named after the entity class’s instance variable names. For every persistent property property of type Type of the entity, there is a getter method getProperty and setter method setProperty. If the property is a boolean, you may use isProperty instead of getProperty. For example, if a Customer entity uses persistent properties, and has a private instance variable called firstName, the class defines a getFirstName and setFirstName method for retrieving and setting the state of the firstName instance variable.
The method signature for single-valued persistent properties are as follows:
Type getProperty() void setProperty(Type type)
The object/relational mapping annotations for persistent properties must be applied to the getter methods. Mapping annotations cannot be applied to fields or properties annotated @Transient or marked transient.
Collection-valued persistent fields and properties must use the supported Java collection interfaces regardless of whether the entity uses persistent fields or properties. The following collection interfaces may be used:
java.util.Collection
java.util.Set
java.util.List
java.util.Map
If the entity class uses persistent fields, the type in the above method signatures must be one of these collection types. Generic variants of these collection types may also be used. For example, if the Customer entity has a persistent property that contains a set of phone numbers, it would have the following methods:
Set<PhoneNumber> getPhoneNumbers() { ... } void setPhoneNumbers(Set<PhoneNumber>) { ... }
If a field or property of an entity consists of a collection of basic types or embeddable classes, use the javax.persistence.ElementCollection annotation on the field or property.
@ElementCollection has two attributes: targetClass and fetch. The targetClass attribute specifies the class name of the basic or embeddable class, and is optional if the field or property is defined using Java programming language generics. The optional fetch attribute is used to specify whether the collection should be retrieved lazily or eagerly, using the javax.persistence.FetchType constants of either LAZY or EAGER, respectively. By default, the collection will be fetched lazily.
The following entity, Person, has a persistent field nicknames that is a collection of String classes that will be fetched eagerly. The targetClass element is not required because it uses generics to define the field.
@Entity public class Person { ... @ElementCollection(fetch=EAGER) protected Set<String> nickname = new HashSet(); ... }
Collections of entity elements and relationships may be represented by java.util.Map collections. A Map consists of a key and value.
When using Map elements or relationships, the following rules apply:
The Map key or value may be a basic Java programming language type, an embeddable class, or an entity.
When the Map value is an embeddable class or basic type, use the @ElementCollection annotation.
When the Map value is an entity use the @OneToMany or @ManyToMany annotation.
Only use the Map type on one side of a bidirectional relationship.
If the key type of a Map is a Java programming language basic type, use the javax.persistence.MapKeyColumn annotation to set the column mapping for the key. By default, the name attribute of @MapKeyColumn is of the form RELATIONSHIP FIELD/PROPERTY NAME_KEY. For example, if the referencing relationship field name is image, the default name attribute is IMAGE_KEY.
If the key type of a Map is an entity, use the javax.persistence.MapKeyJoinColumn annotation. If the multiple columns are needed to set the mapping, use the javax.persistence.MapKeyJoinColumns annotation to include multiple @MapKeyJoinColumn annotations. If no @MapKeyJoinColumn is present, the mapping column name is by default set to RELATIONSHIP FIELD/PROPERTY NAME_KEY. For example, if the relationship field name is employee, the default name attribute is EMPLOYEE_KEY.
If Java programming language generic types are not used in the relationship field or property, the key class must be explicitly set using the javax.persistence.MapKeyClass annotation.
If the Map key is the primary key, or a persistent field or property of the entity that is the Map value, use the javax.persistence.MapKey annotation. The @MapKeyClass and @MapKey annotations cannot be used on the same field or property.
If Map value is a Java programming language basic type or an embeddable class, it will be mapped as a collection table in the underlying database. If generic types are not used, the @ElementCollection annotation's targetClass attribute must be set to the type of the Map value.
If the Map value is an entity, and part of a many-to-many or one-to-many unidirectional relationship, it will be mapped as a join table in the underlying database. A unidirectional one-to-many relationship that uses a Map may also be mapped using the @JoinColumn annotation.
If the entity is part of a one-to-many/many-to-one bidirectional relationship, it will be mapped in the table of the entity that represents the value of the Map. If generic types are not used, the targetEntity attribute of the @OneToMany and @ManyToMany annotations must be set to the type of the Map value.
Each entity has a unique object identifier. A customer entity, for example, might be identified by a customer number. The unique identifier, or primary key, enables clients to locate a particular entity instance. Every entity must have a primary key. An entity may have either a simple or a composite primary key.
Simple primary keys use the javax.persistence.Id annotation to denote the primary key property or field.
Composite primary keys are used when a primary key consists of more than one attribute, which corresponds to a set of single persistent properties or fields. Composite primary keys must be defined in a primary key class. Composite primary keys are denoted using the javax.persistence.EmbeddedId and javax.persistence.IdClass annotations.
The primary key, or the property or field of a composite primary key, must be one of the following Java language types:
Java primitive types
Java primitive wrapper types
java.lang.String
java.util.Date (the temporal type should be DATE)
java.sql.Date
java.math.BigDecimal
java.math.BigInteger
Floating point types should never be used in primary keys. If you use a generated primary key, only integral types will be portable.
A primary key class must meet these requirements:
The access control modifier of the class must be public.
The properties of the primary key class must be public or protected if property-based access is used.
The class must have a public default constructor.
The class must implement the hashCode() and equals(Object other) methods.
The class must be serializable.
A composite primary key must be represented and mapped to multiple fields or properties of the entity class, or must be represented and mapped as an embeddable class.
If the class is mapped to multiple fields or properties of the entity class, the names and types of the primary key fields or properties in the primary key class must match those of the entity class.
The following primary key class is a composite key, the orderId and itemId fields together uniquely identify an entity.
public final class LineItemKey implements Serializable { public Integer orderId; public int itemId; public LineItemKey() {} public LineItemKey(Integer orderId, int itemId) { this.orderId = orderId; this.itemId = itemId; } public boolean equals(Object otherOb) { if (this == otherOb) { return true; } if (!(otherOb instanceof LineItemKey)) { return false; } LineItemKey other = (LineItemKey) otherOb; return ( (orderId==null?other.orderId==null:orderId.equals (other.orderId) ) && (itemId == other.itemId) ); } public int hashCode() { return ( (orderId==null?0:orderId.hashCode()) ^ ((int) itemId) ); } public String toString() { return "" + orderId + "-" + itemId; } }
There are four types of multiplicities: one-to-one, one-to-many, many-to-one, and many-to-many.
One-to-one: Each entity instance is related to a single instance of another entity. For example, to model a physical warehouse in which each storage bin contains a single widget, StorageBin and Widget would have a one-to-one relationship. One-to-one relationships use the javax.persistence.OneToOne annotation on the corresponding persistent property or field.
One-to-many: An entity instance can be related to multiple instances of the other entities. A sales order, for example, can have multiple line items. In the order application, Order would have a one-to-many relationship with LineItem. One-to-many relationships use the javax.persistence.OneToMany annotation on the corresponding persistent property or field.
Many-to-one: Multiple instances of an entity can be related to a single instance of the other entity. This multiplicity is the opposite of a one-to-many relationship. In the example just mentioned, from the perspective of LineItem the relationship to Order is many-to-one. Many-to-one relationships use the javax.persistence.ManyToOne annotation on the corresponding persistent property or field.
Many-to-many: The entity instances can be related to multiple instances of each other. For example, in college each course has many students, and every student may take several courses. Therefore, in an enrollment application, Course and Student would have a many-to-many relationship. Many-to-many relationships use the javax.persistence.ManyToMany annotation on the corresponding persistent property or field.
The direction of a relationship can be either bidirectional or unidirectional. A bidirectional relationship has both an owning side and an inverse side. A unidirectional relationship has only an owning side. The owning side of a relationship determines how the Persistence runtime makes updates to the relationship in the database.
In a bidirectional relationship, each entity has a relationship field or property that refers to the other entity. Through the relationship field or property, an entity class’s code can access its related object. If an entity has a related field, then the entity is said to “know” about its related object. For example, if Order knows what LineItem instances it has and if LineItem knows what Order it belongs to, then they have a bidirectional relationship.
Bidirectional relationships must follow these rules:
The inverse side of a bidirectional relationship must refer to its owning side by using the mappedBy element of the @OneToOne, @OneToMany, or @ManyToMany annotation. The mappedBy element designates the property or field in the entity that is the owner of the relationship.
The many side of many-to-one bidirectional relationships must not define the mappedBy element. The many side is always the owning side of the relationship.
For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.
For many-to-many bidirectional relationships either side may be the owning side.
In a unidirectional relationship, only one entity has a relationship field or property that refers to the other. For example, LineItem would have a relationship field that identifies Product, but Product would not have a relationship field or property for LineItem. In other words, LineItem knows about Product, but Product doesn’t know which LineItem instances refer to it.
Java Persistence query language and Criteria API queries often navigate across relationships. The direction of a relationship determines whether a query can navigate from one entity to another. For example, a query can navigate from LineItem to Product but cannot navigate in the opposite direction. For Order and LineItem, a query could navigate in both directions, because these two entities have a bidirectional relationship.
Entities that use relationships often have dependencies on the existence of the other entity in the relationship. For example, a line item is part of an order, and if the order is deleted, then the line item should also be deleted. This is called a cascade delete relationship.
The javax.persistence.CascadeType enumerated type defines the cascade operations that are applied in the cascade element of the relationship annotations.
Table 19–1 Cascade Operations For Entities
Cascade Operation |
Description |
---|---|
ALL |
All cascade operations will be applied to the parent entity's related entity. All is equivalent to specifying cascade={DETACH, MERGE, PERSIST, REFRESH, REMOVE} |
DETACH |
If the parent entity is detached from the persistence context, the related entity will also be detached. |
MERGE |
If the parent entity is merged into the persistence context, the related entity will also be merged. |
PERSIST |
If the parent entity is persisted into the persistence context, the related entity will also be persisted. |
REFRESH |
If the parent entity is refreshed in the current persistence context, the related entity will also be refreshed. |
REMOVE |
If the parent entity is removed from the current persistence context, the related entity will also be removed. |
Cascade delete relationships are specified using the cascade=REMOVE element specification for @OneToOne and @OneToMany relationships. For example:
@OneToMany(cascade=REMOVE, mappedBy="customer") public Set<Order> getOrders() { return orders; }
When a target entity in one-to-one or one-to-many relationship is removed from the relationship, it is often desirable to cascade the remove operation to the target entity. Such target entities are considered “orphans,” and the orphanRemoval attribute can be used to specify that orphaned entities should be removed. For example, if an order has many line items, and one of the line items is removed from the order, the removed line item is considered an orphan. If orphanRemoval is set to true, the line item entity will be deleted when the line item is removed from the order.
The orphanRemoval attribute in @OneToMany and @oneToOne takes a boolean value, and is by default false.
The following example will cascade the remove operation to the orphaned customer entity when it is removed from the relationship.
@OneToMany(mappedBy="customer", orphanRemoval="true") public List<Order> getOrders() { ... }
Embeddable classes are used to represent the state of an entity, but don't have a persistent identity of their own, unlike entity classes. Instances of an embeddable class share the identity of the entity that owns it. Embeddable classes only exist as the state of another entity. An entity may have single-valued or collection-valued embeddable class attributes.
Embeddable classes have the same rules as entity classes, except that they are annotated with the javax.persistence.Embeddable annotation instead of @Entity.
The following embeddable class, ZipCode has two fields, zip and plusFour.
@Embeddable public class ZipCode { String zip; String plusFour; ... }
This embeddable class is used by the Address entity.
@Entity public class Address { @Id protected long id String street1; String street2; String city; String province; @Embedded ZipCode zipCode; String country; ... }
Entities that own embeddable classes as part of their persistent state may annotate the field or property with the javax.persitence.Embedded annotation, but are not required to do so.
Embeddable classes may themselves use other embeddable classes to represent their state. They may also contain collections of basic Java programming language types, or other embeddable classes. Embeddable classes may also contain relationships to other entities or collections of entities. If the embeddable class has such a relationship, the relationship is from the target entity or collection of entities to the entity that owns the embeddable class.
Entities support class inheritance, polymorphic associations, and polymorphic queries. They can extend non-entity classes, and non-entity classes can extend entity classes. Entity classes can be both abstract and concrete.
The roster example application demonstrates entity inheritance, and is described in Entity Inheritance in the roster Application.
An abstract class may be declared an entity by decorating the class with @Entity. Abstract entities differ from concrete entities only in that they cannot be instantiated.
Abstract entities can be queried just like concrete entities. If an abstract entity is the target of a query, the query operates on all the concrete subclasses of the abstract entity.
@Entity public abstract class Employee { @Id protected Integer employeeId; ... } @Entity public class FullTimeEmployee extends Employee { protected Integer salary; ... } @Entity public class PartTimeEmployee extends Employee { protected Float hourlyWage; }
Entities may inherit from superclasses that contain persistent state and mapping information, but are not entities. That is, the superclass is not decorated with the @Entity annotation, and is not mapped as an entity by the Java Persistence provider. These superclasses are most often used when you have state and mapping information common to multiple entity classes.
Mapped superclasses are specified by decorating the class with the javax.persistence.MappedSuperclass annotation.
@MappedSuperclass public class Employee { @Id protected Integer employeeId; ... } @Entity public class FullTimeEmployee extends Employee { protected Integer salary; ... } @Entity public class PartTimeEmployee extends Employee { protected Float hourlyWage; ... }
Mapped superclasses cannot be queried, and can’t be used in EntityManager or Query operations. You must use entity subclasses of the mapped superclass in EntityManager or Query operations. Mapped superclasses can’t be targets of entity relationships. Mapped superclasses can be abstract or concrete.
Mapped superclasses do not have any corresponding tables in the underlying datastore. Entities that inherit from the mapped superclass define the table mappings. For instance, in the code sample above the underlying tables would be FULLTIMEEMPLOYEE and PARTTIMEEMPLOYEE, but there is no EMPLOYEE table.
Entities may have non-entity superclasses, and these superclasses can be either abstract or concrete. The state of non-entity superclasses is non-persistent, and any state inherited from the non-entity superclass by an entity class is non-persistent. Non-entity superclasses may not be used in EntityManager or Query operations. Any mapping or relationship annotations in non-entity superclasses are ignored.
You can configure how the Java Persistence provider maps inherited entities to the underlying datastore by decorating the root class of the hierarchy with the javax.persistence.Inheritance annotation. There are three mapping strategies that are used to map the entity data to the underlying database:
A single table per class hierarchy
A table per concrete entity class
A “join” strategy, where fields or properties that are specific to a subclass are mapped to a different table than the fields or properties that are common to the parent class
The strategy is configured by setting the strategy element of @Inheritance to one of the options defined in the javax.persistence.InheritanceType enumerated type:
public enum InheritanceType { SINGLE_TABLE, JOINED, TABLE_PER_CLASS };
The default strategy is InheritanceType.SINGLE_TABLE, and is used if the @Inheritance annotation is not specified on the root class of the entity hierarchy.
With this strategy, which corresponds to the default InheritanceType.SINGLE_TABLE, all classes in the hierarchy are mapped to a single table in the database. This table has a discriminator column, a column that contains a value that identifies the subclass to which the instance represented by the row belongs.
The discriminator column can be specified by using the javax.persistence.DiscriminatorColumn annotation on the root of the entity class hierarchy.
Table 19–2 @DiscriminatorColumn Elements
Type |
Name |
Description |
---|---|---|
String |
name |
The name of the column in the table to be used as the discriminator column. The default is DTYPE. This element is optional. |
DiscriminatorType |
discriminatorType |
The type of the column to be used as a discriminator column. The default is DiscriminatorType.STRING. This element is optional. |
String |
columnDefinition |
The SQL fragment to use when creating the discriminator column. The default is generated by the Persistence provider, and is implementation-specific. This element is optional. |
String |
length |
The column length for String-based discriminator types. This element is ignored for non-String discriminator types. The default is 31. This element is optional. |
The javax.persistence.DiscriminatorType enumerated type is used to set the type of the discriminator column in the database by setting the discriminatorType element of @DiscriminatorColumn to one of the defined types. DiscriminatorType is defined as:
public enum DiscriminatorType { STRING, CHAR, INTEGER };
If @DiscriminatorColumn is not specified on the root of the entity hierarchy and a discriminator column is required, the Persistence provider assumes a default column name of DTYPE, and column type of DiscriminatorType.STRING.
The javax.persistence.DiscriminatorValue annotation may be used to set the value entered into the discriminator column for each entity in a class hierarchy. You may only decorate concrete entity classes with @DiscriminatorValue.
If @DiscriminatorValue is not specified on an entity in a class hierarchy that uses a discriminator column, the Persistence provider will provide a default, implementation-specific value. If the discriminatorType element of @DiscriminatorColumn is DiscriminatorType.STRING, the default value is the name of the entity.
This strategy provides good support for polymorphic relationships between entities and queries that cover the entire entity class hierarchy. However, it requires the columns that contain the state of subclasses to be nullable.
In this strategy, which corresponds to InheritanceType.TABLE_PER_CLASS, each concrete class is mapped to a separate table in the database. All fields or properties in the class, including inherited fields or properties, are mapped to columns in the class’s table in the database.
This strategy provides poor support for polymorphic relationships, and usually requires either SQL UNION queries or separate SQL queries for each subclass for queries that cover the entire entity class hierarchy.
Support for this strategy is optional, and may not be supported by all Java Persistence API providers. The default Java Persistence API provider in the Enterprise Server does not support this strategy.
In this strategy, which corresponds to InheritanceType.JOINED, the root of the class hierarchy is represented by a single table, and each subclass has a separate table that only contains those fields specific to that subclass. That is, the subclass table does not contain columns for inherited fields or properties. The subclass table also has a column or columns that represent its primary key, which is a foreign key to the primary key of the superclass table.
This strategy provides good support for polymorphic relationships, but requires one or more join operations to be performed when instantiating entity subclasses. This may result in poor performance for extensive class hierarchies. Similarly, queries that cover the entire class hierarchy require join operations between the subclass tables, resulting in decreased performance.
Some Java Persistence API providers, including the default provider in the Enterprise Server, require a discriminator column in the table that corresponds to the root entity when using the joined subclass strategy. If you are not using automatic table creation in your application, make sure the database table is set up correctly for the discriminator column defaults, or use the @DiscriminatorColumn annotation to match your database schema. For information on discriminator columns, see The Single Table per Class Hierarchy Strategy.
Entities are managed by the entity manager. The entity manager is represented by javax.persistence.EntityManager instances. Each EntityManager instance is associated with a persistence context. A persistence context defines the scope under which particular entity instances are created, persisted, and removed.
A persistence context is a set of managed entity instances that exist in a particular data store. The EntityManager interface defines the methods that are used to interact with the persistence context.
The EntityManager API creates and removes persistent entity instances, finds entities by the entity’s primary key, and allows queries to be run on entities.
With a container-managed entity manager, an EntityManager instance’s persistence context is automatically propagated by the container to all application components that use the EntityManager instance within a single Java Transaction Architecture (JTA) transaction.
JTA transactions usually involve calls across application components. To complete a JTA transaction, these components usually need access to a single persistence context. This occurs when an EntityManager is injected into the application components by means of the javax.persistence.PersistenceContext annotation. The persistence context is automatically propagated with the current JTA transaction, and EntityManager references that are mapped to the same persistence unit provide access to the persistence context within that transaction. By automatically propagating the persistence context, application components don’t need to pass references to EntityManager instances to each other in order to make changes within a single transaction. The Java EE container manages the life cycle of container-managed entity managers.
To obtain an EntityManager instance, inject the entity manager into the application component:
@PersistenceContext EntityManager em;
With application-managed entity managers, on the other hand, the persistence context is not propagated to application components, and the life cycle of EntityManager instances is managed by the application.
Application-managed entity managers are used when applications need to access a persistence context that is not propagated with the JTA transaction across EntityManager instances in a particular persistence unit. In this case, each EntityManager creates a new, isolated persistence context. The EntityManager, and its associated persistence context, is created and destroyed explicitly by the application. They are also used when directly injecting EntityManager instances can't be done because EntityManager instances are not thread-safe. EntityManagerFactory instances are thread-safe.
Applications create EntityManager instances in this case by using the createEntityManager method of javax.persistence.EntityManagerFactory.
To obtain an EntityManager instance, you first must obtain an EntityManagerFactory instance by injecting it into the application component by means of the javax.persistence.PersistenceUnit annotation:
@PersistenceUnit EntityManagerFactory emf;
Then, obtain an EntityManager from the EntityManagerFactory instance:
EntityManager em = emf.createEntityManager();
Application-managed entity managers don't automatically propagate the JTA transaction context. Such applications need to manually gain access to the JTA transaction manager and add transaction demarcation information when performing entity operations. The javax.transaction.UserTransaction interface defines methods to begin, commit, and rollback transactions. Inject an instance of UserTransaction by creating an instance variable annotated with @Resource.
@Resource UserTransaction utx;
To begin a transaction, call the UserTransaction.begin method. When all the entity operations are complete, call the UserTransaction.commit method to commit the transaction. The UserTransaction.rollback method is used to roll back the current transaction.
The following example shows how to manage transactions in an application that uses an application-managed entity manager.
@PersistenceContext EntityManagerFactory emf; EntityManager em; @Resource UserTransaction utx; ... em = emf.createEntityManager(); try { utx.begin(); em.persist(SomeEntity); em.merge(AnotherEntity); em.remove(ThirdEntity); utx.commit(); } catch (Exception e) { utx.rollback(); }
The EntityManager.find method is used to look up entities in the data store by the entity’s primary key.
@PersistenceContext EntityManager em; public void enterOrder(int custID, Order newOrder) { Customer cust = em.find(Customer.class, custID); cust.getOrders().add(newOrder); newOrder.setCustomer(cust); }
You manage entity instances by invoking operations on the entity by means of an EntityManager instance. Entity instances are in one of four states: new, managed, detached, or removed.
New entity instances have no persistent identity and are not yet associated with a persistence context.
Managed entity instances have a persistent identity and are associated with a persistence context.
Detached entity instances have a persistent identify and are not currently associated with a persistence context.
Removed entity instances have a persistent identity, are associated with a persistent context, and are scheduled for removal from the data store.
New entity instances become managed and persistent either by invoking the persist method, or by a cascading persist operation invoked from related entities that have the cascade=PERSIST or cascade=ALL elements set in the relationship annotation. This means the entity’s data is stored to the database when the transaction associated with the persist operation is completed. If the entity is already managed, the persist operation is ignored, although the persist operation will cascade to related entities that have the cascade element set to PERSIST or ALL in the relationship annotation. If persist is called on a removed entity instance, it becomes managed. If the entity is detached, persist will throw an IllegalArgumentException, or the transaction commit will fail.
@PersistenceContext EntityManager em; ... public LineItem createLineItem(Order order, Product product, int quantity) { LineItem li = new LineItem(order, product, quantity); order.getLineItems().add(li); em.persist(li); return li; }
The persist operation is propagated to all entities related to the calling entity that have the cascade element set to ALL or PERSIST in the relationship annotation.
@OneToMany(cascade=ALL, mappedBy="order") public Collection<LineItem> getLineItems() { return lineItems; }
Managed entity instances are removed by invoking the remove method, or by a cascading remove operation invoked from related entities that have the cascade=REMOVE or cascade=ALL elements set in the relationship annotation. If the remove method is invoked on a new entity, the remove operation is ignored, although remove will cascade to related entities that have the cascade element set to REMOVE or ALL in the relationship annotation. If remove is invoked on a detached entity it will throw an IllegalArgumentException, or the transaction commit will fail. If remove is invoked on an already removed entity, it will be ignored. The entity’s data will be removed from the data store when the transaction is completed, or as a result of the flush operation.
public void removeOrder(Integer orderId) { try { Order order = em.find(Order.class, orderId); em.remove(order); }...
In this example, all LineItem entities associated with the order are also removed, as Order.getLineItems has cascade=ALL set in the relationship annotation.
The state of persistent entities is synchronized to the database when the transaction with which the entity is associated commits. If a managed entity is in a bidirectional relationship with another managed entity, the data will be persisted based on the owning side of the relationship.
To force synchronization of the managed entity to the data store, invoke the flush method of the EntityManager instance. If the entity is related to another entity, and the relationship annotation has the cascade element set to PERSIST or ALL, the related entity’s data will be synchronized with the data store when flush is called.
If the entity is removed, calling flush will remove the entity data from the data store.
A persistence unit defines a set of all entity classes that are managed by EntityManager instances in an application. This set of entity classes represents the data contained within a single data store.
Persistence units are defined by the persistence.xml configuration file. The JAR file or directory whose META-INF directory contains persistence.xml is called the root of the persistence unit. The scope of the persistence unit is determined by the persistence unit’s root.
Each persistence unit must be identified with a name that is unique to the persistence unit’s scope.
Persistent units can be packaged as part of a WAR or EJB JAR file, or can be packaged as a JAR file that can then be included in an WAR or EAR file.
If you package the persistent unit as a set of classes in an EJB JAR file, persistence.xml should be put in the EJB JAR’s META-INF directory.
If you package the persistence unit as a set of classes in a WAR file, persistence.xml should be located in the WAR file’s WEB-INF/classes/META-INF directory.
If you package the persistence unit in a JAR file that will be included in a WAR or EAR file, the JAR file should be located:
In the WEB-INF/lib directory of a WAR.
In the EAR file’s library directory.
In the Java Persistence API 1.0, JAR files could be located at the root of an EAR file as the root of the persistence unit. This is no longer supported. Portable applications should use the EAR file's library directory as the root of the persistence unit.
persistence.xml defines one or more persistence units. The following is an example persistence.xml file.
<persistence> <persistence-unit name="OrderManagement"> <description>This unit manages orders and customers. It does not rely on any vendor-specific features and can therefore be deployed to any persistence provider. </description> <jta-data-source>jdbc/MyOrderDB</jta-data-source> <jar-file>MyOrderApp.jar</jar-file> <class>com.widgets.Order</class> <class>com.widgets.Customer</class> </persistence-unit> </persistence>
This file defines a persistence unit named OrderManagement, which uses a JTA-aware data source jdbc/MyOrderDB. The jar-file and class elements specify managed persistence classes: entity classes, embeddable classes, and mapped superclasses. The jar-file element specifies JAR files that are visible to the packaged persistence unit that contain managed persistence classes, while the class element explicitly names managed persistence classes.
The jta-data-source (for JTA-aware data sources) and non-jta-data-source (non-JTA-aware data sources) elements specify the global JNDI name of the data source to be used by the container.
There are two methods of querying entities using the Java Persistence API. The Java Persistence query language (JPQL) is a simple, string-based language similar to SQL used to query entities and their relationships. See Chapter 21, The Java Persistence Query Language for more information on the Java Persistence query language. The Criteria API is used to create type-safe queries using Java programming language APIs to query for entities and their relationships. See Chapter 22, Creating Queries Using the Criteria API for more information on the Criteria API.
Each approach, JPQL and the Criteria API, has advantages and disadvantages.
JPQL queries are typically more concise compared to Criteria queries, just a few lines long. They also tend to be more readable compared to Criteria queries, and developers familiar with SQL will find it easy to learn the syntax of JPQL. JPQL named queries can be defined in the entity class using a Java programming language annotation, or in the application's deployment descriptor. JPQL queries are not type-safe, however, and require a cast when retrieving the query result from the entity manager. This means that type casting errors may not be caught at compile-time. JPQL queries don't support open-ended parameters.
Criteria queries allow you to define the query in the business tier of the application. While this is also possible using JPQL dynamic queries, Criteria queries provide better performance because JPQL dynamic queries must be parsed each time they are called. Criteria queries are type-safe, and therefore don't require casting like JPQL queries. The Criteria API is just another Java programming language API, and doesn't require developers to learn the syntax of another query language. Criteria queries are typically more verbose compared to JPQL queries, and require the developer to create several objects and perform operations on those objects before submitting the query to the entity manager.
This chapter describes how to use the Java Persistence API in different example applications. The material here focuses on the source code and settings of three examples. The first example called order is an application that uses a stateful session bean to manage entities related to an ordering system. The second example is roster, an application that manages a community sports system. This chapter assumes that you are familiar with the concepts detailed in Chapter 19, Introduction to the Java Persistence API.
The order application is a simple inventory and ordering application for maintaining a catalog of parts and placing an itemized order of those parts. It has entities that represent parts, vendors, orders, and line items. These entities are accessed using a stateful session bean that holds the business logic of the application. A simple singleton session bean creates the initial entities on application deployment. A Facelets web application manipulates the data, and displays data from the catalog.
The information contained in an order can be divided into different elements. What is the order number? What parts are included in the order? What parts make up that part? Who makes the part? What are the specifications for the part? Are there any schematics for the part? order is a simplified version of an ordering system that has all these elements.
The order application consists of a single WAR module that includes the enterprise bean classes, the entities, the support classes, and the Facelets XHTML and class files.
The order application demonstrates several types of entity relationships: one-to-many, many-to-one, one-to-one, unidirectional, and self-referential relationships.
A self-referential relationship is a relationship between relationship fields in the same entity. Part has a field bomPart that has a one-to-many relationship with the field parts, which is also in Part. That is, a part can be made up of many parts, and each of those parts has exactly one bill-of-material part.
The primary key for Part is a compound primary key, a combination of the partNumber and revision fields. It is mapped to the PARTNUMBER and REVISION columns in the EJB_ORDER_PART table.
... @ManyToOne @JoinColumns({ @JoinColumn(name="BOMPARTNUMBER", referencedColumnName="PARTNUMBER"), @JoinColumn(name="BOMREVISION", referencedColumnName="REVISION") }) public Part getBomPart() { return bomPart; } ... @OneToMany(mappedBy="bomPart") public Collection<Part> getParts() { return parts; } ...
Part has a field, vendorPart, that has a one-to-one relationship with VendorPart’s part field. That is, each part has exactly one vendor part, and vice versa.
Here is the relationship mapping in Part:
@OneToOne(mappedBy="part") public VendorPart getVendorPart() { return vendorPart; }
Here is the relationship mapping in VendorPart:
@OneToOne @JoinColumns({ @JoinColumn(name="PARTNUMBER", referencedColumnName="PARTNUMBER"), @JoinColumn(name="PARTREVISION", referencedColumnName="REVISION") }) public Part getPart() { return part; }
Note that, because Part uses a compound primary key, the @JoinColumns annotation is used to map the columns in the PERSISTENCE_ORDER_VENDOR_PART table to the columns in PERSISTENCE_ORDER_PART. PERSISTENCE_ORDER_VENDOR_PART’s PARTREVISION column refers to PERSISTENCE_ORDER_PART’s REVISION column.
Order has a field, lineItems, that has a one-to-many relationship with LineItem’s field order. That is, each order has one or more line item.
LineItem uses a compound primary key that is made up of the orderId and itemId fields. This compound primary key maps to the ORDERID and ITEMID columns in the PERSISTENCE_ORDER_LINEITEM database table. ORDERID is a foreign key to the ORDERID column in the PERSISTENCE_ORDER_ORDER table. This means that the ORDERID column is mapped twice: once as a primary key field, orderId; and again as a relationship field, order.
Here’s the relationship mapping in Order:
@OneToMany(cascade=ALL, mappedBy="order") public Collection<LineItem> getLineItems() { return lineItems; }
Here is the relationship mapping in LineItem:
@ManyToOne public Order getOrder() { return order; }
LineItem has a field, vendorPart, that has a unidirectional many-to-one relationship with VendorPart. That is, there is no field in the target entity in this relationship.
@ManyToOne public VendorPart getVendorPart() { return vendorPart; }
The order application uses several types of primary keys: single-valued primary keys, compound primary keys, and generated primary keys.
VendorPart uses a generated primary key value. That is, the application does not assign primary key values for the entities, but instead relies on the persistence provider to generate the primary key values. The @GeneratedValue annotation is used to specify that an entity will use a generated primary key.
In VendorPart, the following code specifies the settings for generating primary key values:
@TableGenerator( name="vendorPartGen", table="PERSISTENCE_ORDER_SEQUENCE_GENERATOR", pkColumnName="GEN_KEY", valueColumnName="GEN_VALUE", pkColumnValue="VENDOR_PART_ID", allocationSize=10) @Id @GeneratedValue(strategy=GenerationType.TABLE, generator="vendorPartGen") public Long getVendorPartNumber() { return vendorPartNumber; }
The @TableGenerator annotation is used in conjunction with @GeneratedValue’s strategy=TABLE element. That is, the strategy used to generate the primary keys is use a table in the database. @TableGenerator is used to configure the settings for the generator table. The name element sets the name of the generator, which is vendorPartGen in VendorPart.
The EJB_ORDER_SEQUENCE_GENERATOR table, which has two columns GEN_KEY and GEN_VALUE, will store the generated primary key values. This table could be used to generate other entity’s primary keys, so the pkColumnValue element is set to VENDOR_PART_ID to distinguish this entity’s generated primary keys from other entity’s generated primary keys. The allocationSize element specifies the amount to increment when allocating primary key values In this case, each VendorPart’s primary key will increment by 10.
The primary key field vendorPartNumber is of type Long, as the generated primary key’s field must be an integral type.
A compound primary key is made up of multiple fields and follows the requirements described in Primary Key Classes. To use a compound primary key, you must create a wrapper class.
In order, two entities use compound primary keys: Part and LineItem.
Part uses the PartKey wrapper class. Part’s primary key is a combination of the part number and the revision number. PartKey encapsulates this primary key.
LineItem uses the LineItemKey class. LineItem’s primary key is a combination of the order number and the item number. LineItemKey encapsulates this primary key. This is the LineItemKey compound primary key wrapper class:
package order.entity; public final class LineItemKey implements java.io.Serializable { private Integer orderId; private int itemId; public int hashCode() { return ((this.getOrderId()==null ?0:this.getOrderId().hashCode()) ^ ((int) this.getItemId())); } public boolean equals(Object otherOb) { if (this == otherOb) { return true; } if (!(otherOb instanceof LineItemKey)) { return false; } LineItemKey other = (LineItemKey) otherOb; return ((this.getOrderId()==null ?other.orderId==null:this.getOrderId().equals (other.orderId)) && (this.getItemId == other.itemId)); } public String toString() { return "" + orderId + "-" + itemId; } }
The @IdClass annotation is used to specify the primary key class in the entity class. In LineItem, @IdClass is used as follows:
@IdClass(order.entity.LineItemKey.class) @Entity ... public class LineItem { ... }
The two fields in LineItem are tagged with the @Id annotation to mark those fields as part of the compound primary key:
@Id public int getItemId() { return itemId; } ... @Id @Column(name="ORDERID", nullable=false, insertable=false, updatable=false) public Integer getOrderId() { return orderId; }
For orderId, you also use the @Column annotation to specify the column name in the table, and that this column should not be inserted or updated, as it is an overlapping foreign key pointing at the PERSISTENCE_ORDER_ORDER table’s ORDERID column (see One-to-Many Relationship Mapped to Overlapping Primary and Foreign Keys). That is, orderId will be set by the Order entity.
In LineItem’s constructor, the line item number (LineItem.itemId) is set using the Order.getNextId method.
public LineItem(Order order, int quantity, VendorPart vendorPart) { this.order = order; this.itemId = order.getNextId(); this.orderId = order.getOrderId(); this.quantity = quantity; this.vendorPart = vendorPart; }
Order.getNextId counts the number of current line items, adds one, and returns that number.
public int getNextId() { return this.lineItems.size() + 1; }
Part doesn’t require the @Column annotation on the two fields that comprise Part’s compound primary key. This is because Part’s compound primary key is not an overlapping primary key/foreign key.
@IdClass(order.entity.PartKey.class) @Entity ... public class Part { ... @Id public String getPartNumber() { return partNumber; } ... @Id public int getRevision() { return revision; } ... }
Part’s fields map to more than one database table: PERSISTENCE_ORDER_PART and PERSISTENCE_ORDER_PART_DETAIL. The PERSISTENCE_ORDER_PART_DETAIL table holds the specification and schematics for the part. The @SecondaryTable annotation is used to specify the secondary table.
... @Entity @Table(name="PERSISTENCE_ORDER_PART") @SecondaryTable(name="PERSISTENCE_ORDER_PART_DETAIL", pkJoinColumns={ @PrimaryKeyJoinColumn(name="PARTNUMBER", referencedColumnName="PARTNUMBER"), @PrimaryKeyJoinColumn(name="REVISION", referencedColumnName="REVISION") }) public class Part { ... }
PERSISTENCE_ORDER_PART_DETAIL shares the same primary key values as PERSISTENCE_ORDER_PART. The pkJoinColumns element of @SecondaryTable is used to specify that PERSISTENCE_ORDER_PART_DETAIL’s primary key columns are foreign keys to PERSISTENCE_ORDER_PART. The @PrimaryKeyJoinColumn annotation sets the primary key column names and specifies which column in the primary table the column refers to. In this case, the primary key column names for both PERSISTENCE_ORDER_PART_DETAIL and PERSISTENCE_ORDER_PART are the same: PARTNUMBER and REVISION, respectively.
Entities that have relationships to other entities often have dependencies on the existence of the other entity in the relationship. For example, a line item is part of an order, and if the order is deleted, then the line item should also be deleted. This is called a cascade delete relationship.
In order, there are two cascade delete dependencies in the entity relationships. If the Order to which a LineItem is related is deleted, then the LineItem should also be deleted. If the Vendor to which a VendorPart is related is deleted, then the VendorPart should also be deleted.
You specify the cascade operations for entity relationships by setting the cascade element in the inverse (non-owning) side of the relationship. The cascade element is set to ALL in the case of Order.lineItems. This means that all persistence operations (deletes, updates, and so on) are cascaded from orders to line items.
Here is the relationship mapping in Order:
@OneToMany(cascade=ALL, mappedBy="order") public Collection<LineItem> getLineItems() { return lineItems; }
Here is the relationship mapping in LineItem:
@ManyToOne public Order getOrder() { return order; }
The PARTDETAIL table in the database has a column, DRAWING, of type BLOB. BLOB stands for binary large objects, which are used for storing binary data such as an image. The DRAWING column is mapped to the field Part. drawing of type java.io.Serializable. The @Lob annotation is used to denote that the field is large object.
@Column(table="PERSISTENCE_ORDER_PART_DETAIL") @Lob public Serializable getDrawing() { return drawing; }
PERSISTENCE_ORDER_PART_DETAIL also has a column, SPECIFICATION, of type CLOB. CLOB stands for character large objects, which are used to store string data too large to be stored in a VARCHAR column. SPECIFICATION is mapped to the field Part.specification of type java.lang.String. The @Lob annotation is also used here to denote that the field is a large object.
@Column(table="PERSISTENCE_ORDER_PART_DETAIL") @Lob public String getSpecification() { return specification; }
Both of these fields use the @Column annotation and set the table element to the secondary table.
The Order.lastUpdate persistent property, which is of type java.util.Date, is mapped to the PERSISTENCE_ORDER_ORDER.LASTUPDATE database field, which is of the SQL type TIMESTAMP. To ensure the proper mapping between these types, you must use the @Temporal annotation with the proper temporal type specified in @Temporal’s element. @Temporal’s elements are of type javax.persistence.TemporalType. The possible values are:
DATE, which maps to java.sql.Date
TIME, which maps to java.sql.Time
TIMESTAMP, which maps to java.sql.Timestamp
Here is the relevant section of Order:
@Temporal(TIMESTAMP) public Date getLastUpdate() { return lastUpdate; }
The RequestBean stateful session bean contains the business logic and manages the entities of order.
RequestBean uses the @PersistenceContext annotation to retrieve an entity manager instance which is used to manage order’s entities in RequestBean’s business methods.
@PersistenceContext private EntityManager em;
This EntityManager instance is a container-managed entity manager, so the container takes care of all the transactions involved in the managing order’s entities.
The RequestBean.createPart business method creates a new Part entity. The EntityManager.persist method is used to persist the newly created entity to the database.
Part part = new Part(partNumber, revision, description, revisionDate, specification, drawing); em.persist(part);
The ConfigBean singleton session bean is used to initialize the data in order. ConfigBean is annotated with @Startup, which indicates that the EJB container should create ConfigBean when order is deployed. The createData method is annotated with @PostConstruct, and it creates the initial entities used by order by calling RequestsBean's business methods.
The RequestBean.getOrderPrice business method returns the price of a given order, based on the orderId. The EntityManager.find method is used to retrieve the entity from the database.
Order order = em.find(Order.class, orderId);
The first argument of EntityManager.find is the entity class, and the second is the primary key.
The RequestBean.createVendorPart business method creates a VendorPart associated with a particular Vendor. The EntityManager.persist method is used to persist the newly created VendorPart entity to the database, and the VendorPart.setVendor and Vendor.setVendorPart methods are used to associate the VendorPart with the Vendor.
PartKey pkey = new PartKey(); pkey.partNumber = partNumber; pkey.revision = revision; Part part = em.find(Part.class, pkey); VendorPart vendorPart = new VendorPart(description, price, part); em.persist(vendorPart); Vendor vendor = em.find(Vendor.class, vendorId); vendor.addVendorPart(vendorPart); vendorPart.setVendor(vendor);
The RequestBean.adjustOrderDiscount business method updates the discount applied to all orders. It uses the findAllOrders named query, defined in Order:
@NamedQuery( name="findAllOrders", query="SELECT o FROM Order o" )
The EntityManager.createNamedQuery method is used to run the query. Because the query returns a List of all the orders, the Query.getResultList method is used.
List orders = em.createNamedQuery( "findAllOrders") .getResultList();
The RequestBean.getTotalPricePerVendor business method returns the total price of all the parts for a particular vendor. It uses a named parameter, id, defined in the named query findTotalVendorPartPricePerVendor defined in VendorPart.
@NamedQuery( name="findTotalVendorPartPricePerVendor", query="SELECT SUM(vp.price) " + "FROM VendorPart vp " + "WHERE vp.vendor.vendorId = :id" )
When running the query, the Query.setParameter method is used to set the named parameter id to the value of vendorId, the parameter to RequestBean.getTotalPricePerVendor.
return (Double) em.createNamedQuery( "findTotalVendorPartPricePerVendor") .setParameter("id", vendorId) .getSingleResult();
The Query.getSingleResult method is used for this query because the query returns a single value.
The RequestBean.removeOrder business method deletes a given order from the database. It uses the EntityManager.remove method to delete the entity from the database.
Order order = em.find(Order.class, orderId); em.remove(order);
This section describes how to build, package, deploy, and run the order application. To do this, you will create the database tables in the JavaDB server, then build, deploy, and run the example.
Follow these instructions to build, package, deploy, and run the order example to your Enterprise Server instance using NetBeans IDE.
In NetBeans IDE, select File->Open Project.
In the Open Project dialog, navigate to tut-install/examples/persistence/.
Select the order folder.
Select the Open as Main Project check box.
Click Open Project.
In the Projects tab, right-click the order project and select Run.
NetBeans will open a web browser to http://localhost:8080/order/.
To build the application components of order, enter the following command:
ant |
This runs the default task, which compiles the source files and packages the application into an WAR file located at tut-install/examples/persistence/order/dist/order.war.
To deploy the WAR, make sure the Enterprise Server is started, then enter the following command:
ant deploy |
Open a web browser to http://localhost:8080/order/ to create and update the order data.
As a convenience, the all task will build, package, deploy, and run the application. To do this, enter the following command:
ant all |
To undeploy order.war, enter the following command:
ant undeploy |
The roster application maintains the team rosters for players in recreational sports leagues. The application has four components: Java Persistence API entities (Player, Team, and League), a stateful session bean (RequestBean), an application client (RosterClient), and three helper classes (PlayerDetails, TeamDetails, and LeagueDetails).
Functionally, roster is similar to the order application described earlier in this chapter with three new features that order does not have: many-to-many relationships, entity inheritance, and automatic table creation at deployment time.
A recreational sports system has the following relationships:
A player can be on many teams.
A team can have many players.
A team is in exactly one league.
A league has many teams.
In roster this is reflected by the following relationships between the Player, Team, and League entities:
There is a many-to-many relationship between Player and Team.
There is a many-to-one relationship between Team and League.
The many-to-many relationship between Player and Team is specified by using the @ManyToMany annotation.
In Team.java, the @ManyToMany annotation decorates the getPlayers method:
@ManyToMany @JoinTable( name="EJB_ROSTER_TEAM_PLAYER", joinColumns= @JoinColumn(name="TEAM_ID", referencedColumnName="ID"), inverseJoinColumns= @JoinColumn(name="PLAYER_ID", referencedColumnName="ID") ) public Collection<Player> getPlayers() { return players; }
The @JoinTable annotation is used to specify a table in the database that will associate player IDs with team IDs. The entity that specifies the @JoinTable is the owner of the relationship, so in this case the Team entity is the owner of the relationship with the Player entity. Because roster uses automatic table creation at deployment time, the container will create a join table in the database named EJB_ROSTER_TEAM_PLAYER.
Player is the inverse, or non-owning side of the relationship with Team. As one-to-one and many-to-one relationships, the non-owning side is marked by the mappedBy element in the relationship annotation. Because the relationship between Player and Team is bidirectional, the choice of which entity is the owner of the relationship is arbitrary.
In Player.java, the @ManyToMany annotation decorates the getTeams method:
@ManyToMany(mappedBy="players") public Collection<Team> getTeams() { return teams; }
The roster application demonstrates how to use entity inheritance, as described in Entity Inheritance.
The League entity in roster is an abstract entity with two concrete subclasses: SummerLeague and WinterLeague. Because League is an abstract class it cannot be instantiated:
... @Entity @Table(name = "EJB_ROSTER_LEAGUE") public abstract class League implements java.io.Serializable { ... }
Instead, SummerLeague or WinterLeague are used by clients when creating a league. SummerLeague and WinterLeague inherit the persistent properties defined in League, and only add a constructor that verifies that the sport parameter matches the type of sport allowed in that seasonal league. For example, here is the SummerLeague entity:
... @Entity public class SummerLeague extends League implements java.io.Serializable { /** Creates a new instance of SummerLeague */ public SummerLeague() { } public SummerLeague(String id, String name, String sport) throws IncorrectSportException { this.id = id; this.name = name; if (sport.equalsIgnoreCase("swimming") || sport.equalsIgnoreCase("soccer") || sport.equalsIgnoreCase("basketball") || sport.equalsIgnoreCase("baseball")) { this.sport = sport; } else { throw new IncorrectSportException( "Sport is not a summer sport."); } } }
The roster application uses the default mapping strategy of InheritanceType.SINGLE_TABLE, so the @Inheritance annotation is not required. If you wanted to use a different mapping strategy, decorate League with @Inheritance and specify the mapping strategy in the strategy element:
@Entity @Inheritance(strategy=JOINED) @Table(name="EJB_ROSTER_LEAGUE") public abstract class League implements java.io.Serializable { ... }
roster uses the default discriminator column name, so the @DiscriminatorColumn annotation is not required. Because you are using automatic table generation in roster the Persistence provider will create a discriminator column in the EJB_ROSTER_LEAGUE table called DTYPE, which will store the name of the inherited entity used to create the league. If you want to use a different name for the discriminator column, decorate League with @DiscriminatorColumn and set the name element:
@Entity @DiscriminatorColumn(name="DISCRIMINATOR") @Table(name="EJB_ROSTER_LEAGUE") public abstract class League implements java.io.Serializable { ... }
The roster application uses Criteria API queries, as opposed to the JPQL queries used in order. Criteria queries are Java programming language, type-safe queries defined in the business tier of roster, in the RequestBean stateless session bean.
Metamodel classes are classes that model an entities attributes, and are used by Criteria queries to navigate to an entities attributes. Each entity class in roster has a corresponding Metamodel class, generated at compile-time, with the same package name as the entity, and appended with an underscore character (_). For example, the roster.entity.Person entity has a corresponding Metamodel class, roster.entity.Person_.
Each persistent field or property in the entity class has a corresponding attribute in the entity's Metamodel class. For the Person entity, the corresponding Metamodel class is:
@StaticMetamodel(Person.class) public class Person_ { public static volatile SingularAttribute<Player, String> id; public static volatile SingularAttribute<Player, String> name; public static volatile SingularAttribute<Player, String> position; public static volatile SingularAttribute<Player, Double> salary; public static volatile CollectionAttribute<Player, Team> teams; }
The CrtiteriaBuilder interface defines methods to create criteria query objects, create expressions for modifying those query objects. RequestBean creates an instance of CriteriaBuilder by using a @PostConstruct method, init.
@PersistenceContext private EntityManager em; private CriteriaBuilder cb; @PostConstruct private void init() { cb = em.getCriteriaBuilder(); }
The EntityManager instance is injected at runtime, and then that EntityManager object is used to create the CriteriaBuilder instance by calling getCriteriaBuilder. The CriteriaBuilder instance is created in a @PostConstruct method to ensure that the EntityManager instance has been injected by the enterprise bean container.
Many of the business methods in RequestBean define Criteria queries. One business method, getPlayersByPosition, returns a list of players that play a particular position on a team.
public List<PlayerDetails> getPlayersByPosition(String position) { logger.info("getPlayersByPosition"); List<Player> players = null; try { CriteriaQuery<Player> cq = cb.createQuery(Player.class); if (cq != null) { Root<Player> player = cq.from(Player.class); // set the where clause cq.where(cb.equal(player.get(Player_.position), position)); cq.select(player); TypedQuery<Player> q = em.createQuery(cq); players = q.getResultList(); } return copyPlayersToDetails(players); } catch (Exception ex) { throw new EJBException(ex); } }
A query is object is created by calling the CriteriaBuilder object's createQuery method, with the type set to Player because the query will return a list of players.
The query root is the base entity from which the query will navigate to find the entity's attributes and related entities, and is created by calling the from method of the query object. This sets the from clause of the query.
The where clause, set by calling the where method on the query object, restricts the results of the query according to the conditions of an expression. The CriteriaBuilder.equal method compares the two expressions. In getPlayersByPosition, the position attribute of the Player_ Metamodel class, accessed by calling the get method of the query root, is compared to the position parameter passed to getPlayersByPosition.
The select clause of the query is set by calling the select method of the query object. The query will return Player entities, so the query root object is passed as a parameter to select.
The query object is prepared for execution by calling EntityManager.createQuery, which returns a TypedQuery<T> object with the type of the query, in this case Player. This typed query object is used to execute the query, which occurs when the getResultList method is called, and a List<Player> collection is returned.
At deployment time the Enterprise Server will automatically drop and create the database tables used by roster. This is done by setting the toplink.ddl-generation property to drop-and-create-tables in persistence.xml.
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="em" transaction-type="JTA"> <jta-data-source>jdbc/__default</jta-data-source> <properties> <property name="toplink.ddl-generation" value="drop-and-create-tables"/> </properties> </persistence-unit> </persistence>
This feature is specific to the Java Persistence API provider used by the Enterprise Server, and is non-portable across Java EE servers. Automatic table creation is useful for development purposes, however, and the toplink.ddl-generation property may be removed from persistence.xml when preparing the application for production use, or when deploying to other Java EE servers.
This section describes how to build, package, deploy, and run the roster application. You can do this using either NetBeans IDE or Ant.
Follow these instructions to build, package, deploy, and run the roster example to your Enterprise Server instance using NetBeans IDE.
In NetBeans IDE, select File->Open Project.
In the Open Project dialog, navigate to tut-install/examples/persistence/.
Select the roster folder.
Select the Open as Main Project and Open Required Projects check boxes.
Click Open Project.
In the Projects tab, right-click the roster project and select Run.
You will see the following partial output from the application client in the Output tab:
List all players in team T2: P6 Ian Carlyle goalkeeper 555.0 P7 Rebecca Struthers midfielder 777.0 P8 Anne Anderson forward 65.0 P9 Jan Wesley defender 100.0 P10 Terry Smithson midfielder 100.0 List all teams in league L1: T1 Honey Bees Visalia T2 Gophers Manteca T5 Crows Orland List all defenders: P2 Alice Smith defender 505.0 P5 Barney Bold defender 100.0 P9 Jan Wesley defender 100.0 P22 Janice Walker defender 857.0 P25 Frank Fletcher defender 399.0 ... |
To build the application components of roster, enter the following command:
ant |
This runs the default task, which compiles the source files and packages the application into an EAR file located at tut-install/examples/persistence/roster/dist/roster.ear.
To deploy the EAR, make sure the Enterprise Server is started, then enter the following command:
ant deploy |
The build system will check to see if the JavaDB database server is running and start it if it is not running, then deploy roster.ear. The Enterprise Server will then drop and create the database tables during deployment, as specified in persistence.xml.
After roster.ear is deployed, a client JAR, rosterClient.jar, is retrieved. This contains the application client.
To run the application client, enter the following command:
ant run |
You will see the output, which begins:
[echo] running application client container. [exec] List all players in team T2: [exec] P6 Ian Carlyle goalkeeper 555.0 [exec] P7 Rebecca Struthers midfielder 777.0 [exec] P8 Anne Anderson forward 65.0 [exec] P9 Jan Wesley defender 100.0 [exec] P10 Terry Smithson midfielder 100.0 [exec] List all teams in league L1: [exec] T1 Honey Bees Visalia [exec] T2 Gophers Manteca [exec] T5 Crows Orland [exec] List all defenders: [exec] P2 Alice Smith defender 505.0 [exec] P5 Barney Bold defender 100.0 [exec] P9 Jan Wesley defender 100.0 [exec] P22 Janice Walker defender 857.0 [exec] P25 Frank Fletcher defender 399.0 ... |
As a convenience, the all task will build, package, deploy, and run the application. To do this, enter the following command:
ant all |
To undeploy roster.ear, enter the following command:
ant undeploy |
The Java Persistence query language defines queries for entities and their persistent state. The query language allows you to write portable queries that work regardless of the underlying data store.
The query language uses the abstract persistence schemas of entities, including their relationships, for its data model, and it defines operators and expressions based on this data model. The scope of a query spans the abstract schemas of related entities that are packaged in the same persistence unit. The query language uses a SQL-like syntax to select objects or values based on entity abstract schema types and relationships among them.
This chapter relies on the material presented in earlier chapters. For conceptual information, see Chapter 19, Introduction to the Java Persistence API. For code examples, see Chapter Chapter 20, Running the Persistence Examples.
The following list defines some of the terms referred to in this chapter.
Abstract schema: The persistent schema abstraction (persistent entities, their state, and their relationships) over which queries operate. The query language translates queries over this persistent schema abstraction into queries that are executed over the database schema to which entities are mapped.
Abstract schema type: All expressions evaluate to a type. The abstract schema type of an entity is derived from the entity class and the metadata information provided by Java language annotations.
Backus-Naur Form (BNF): A notation that describes the syntax of high-level languages. The syntax diagrams in this chapter are in BNF notation.
Navigation: The traversal of relationships in a query language expression. The navigation operator is a period.
Path expression: An expression that navigates to a entity’s state or relationship field.
Relationship field: A persistent relationship field of an entity whose type is the abstract schema type of the related entity.
The EntityManager.createQuery and EntityManager.createNamedQuery methods are used to query the datastore using Java Persistence query language queries.
The createQuery method is used to create dynamic queries, which are queries that are defined directly within an application’s business logic.
public List findWithName(String name) { return em.createQuery( "SELECT c FROM Customer c WHERE c.name LIKE :custName") .setParameter("custName", name) .setMaxResults(10) .getResultList(); }
The createNamedQuery method is used to create static queries, or queries that are defined in metadata using the javax.persistence.NamedQuery annotation. The name element of @NamedQuery specifies the name of the query that will be used with the createNamedQuery method. The query element of @NamedQuery is the query.
@NamedQuery( name="findAllCustomersWithName", query="SELECT c FROM Customer c WHERE c.name LIKE :custName" )
Here’s an example of createNamedQuery, which uses the @NamedQuery defined above.
@PersistenceContext public EntityManager em; ... customers = em.createNamedQuery("findAllCustomersWithName") .setParameter("custName", "Smith") .getResultList();
Named parameters are parameters in a query that are prefixed with a colon (:). Named parameters in a query are bound to an argument by the javax.persistence.Query.setParameter(String name, Object value) method. In the following example, the name argument to the findWithName business method is bound to the :custName named parameter in the query by calling Query.setParameter.
public List findWithName(String name) { return em.createQuery( "SELECT c FROM Customer c WHERE c.name LIKE :custName") .setParameter("custName", name) .getResultList(); }
Named parameters are case-sensitive, and may be used by both dynamic and static queries.
You may alternately use positional parameters in queries, instead of named parameters. Positional parameters are prefixed with a question mark (?) followed the numeric position of the parameter in the query. The Query.setParameter(integer position, Object value) method is used to set the parameter values.
In the following example, the findWithName business method is rewritten to use input parameters:
public List findWithName(String name) { return em.createQuery( “SELECT c FROM Customer c WHERE c.name LIKE ?1”) .setParameter(1, name) .getResultList(); }
Input parameters are numbered starting from 1. Input parameters are case-sensitive, and may be used by both dynamic and static queries.
This section briefly describes the syntax of the query language so that you can quickly move on to the next section, Example Queries. When you are ready to learn about the syntax in more detail, see the section Full Query Language Syntax.
A select query has six clauses: SELECT, FROM, WHERE, GROUP BY, HAVING, and ORDER BY. The SELECT and FROM clauses are required, but the WHERE, GROUP BY, HAVING, and ORDER BY clauses are optional. Here is the high-level BNF syntax of a query language query:
QL_statement ::= select_clause from_clause [where_clause][groupby_clause][having_clause][orderby_clause]
The SELECT clause defines the types of the objects or values returned by the query.
The FROM clause defines the scope of the query by declaring one or more identification variables, which can be referenced in the SELECT and WHERE clauses. An identification variable represents one of the following elements:
An element of a collection relationship
An element of a single-valued relationship
A member of a collection that is the multiple side of a one-to-many relationship
The WHERE clause is a conditional expression that restricts the objects or values retrieved by the query. Although it is optional, most queries have a WHERE clause.
The GROUP BY clause groups query results according to a set of properties.
The HAVING clause is used with the GROUP BY clause to further restrict the query results according to a conditional expression.
The ORDER BY clause sorts the objects or values returned by the query into a specified order.
Update and delete statements provide bulk operations over sets of entities. They have the following syntax:
update_statement :: = update_clause [where_clause] delete_statement :: = delete_clause [where_clause]
The update and delete clauses determine the type of the entities to be updated or deleted. The WHERE clause may be used to restrict the scope of the update or delete operation.
The following queries are from the Player entity of the roster application, which is documented in The roster Application.
If you are unfamiliar with the query language, these simple queries are a good place to start.
SELECT p FROM Player p
Data retrieved: All players.
Description: The FROM clause declares an identification variable named p, omitting the optional keyword AS. If the AS keyword were included, the clause would be written as follows:
FROM Player AS p
The Player element is the abstract schema name of the Player entity.
See also: Identification Variables
SELECT DISTINCT p FROM Player p WHERE p.position = ?1
Data retrieved: The players with the position specified by the query’s parameter.
Description: The DISTINCT keyword eliminates duplicate values.
The WHERE clause restricts the players retrieved by checking their position, a persistent field of the Player entity. The ?1 element denotes the input parameter of the query.
See also: Input Parameters, The DISTINCT Keyword
SELECT DISTINCT p FROM Player p WHERE p.position = :position AND p.name = :name
Data retrieved: The players having the specified positions and names.
Description: The position and name elements are persistent fields of the Player entity. The WHERE clause compares the values of these fields with the named parameters of the query, set using the Query.setNamedParameter method. The query language denotes a named input parameter using colon (:) followed by an identifier. The first input parameter is :position, the second is :name.
In the query language, an expression can traverse (or navigate) to related entities. These expressions are the primary difference between the Java Persistence query language and SQL. Queries navigates to related entities, whereas SQL joins tables.
SELECT DISTINCT p FROM Player p, IN(p.teams) t
Data retrieved: All players who belong to a team.
Description: The FROM clause declares two identification variables: p and t. The p variable represents the Player entity, and the t variable represents the related Team entity. The declaration for t references the previously declared p variable. The IN keyword signifies that teams is a collection of related entities. The p.teams expression navigates from a Player to its related Team. The period in the p.teams expression is the navigation operator.
You may also use the JOIN statement to write the same query:
SELECT DISTINCT p FROM Player p JOIN p.teams t
This query could also be rewritten as:
SELECT DISTINCT p FROM Player p WHERE p.team IS NOT EMPTY
Use the JOIN clause statement to navigate to a single-valued relationship field:
SELECT t FROM Team t JOIN t.league l WHERE l.sport = ’soccer’ OR l.sport =’football’
In this example, the query will return all teams that are in either soccer or football leagues.
SELECT DISTINCT p FROM Player p, IN (p.teams) AS t WHERE t.city = :city
Data retrieved: The players whose teams belong to the specified city.
Description: This query is similar to the previous example, but it adds an input parameter. The AS keyword in the FROM clause is optional. In the WHERE clause, the period preceding the persistent variable city is a delimiter, not a navigation operator. Strictly speaking, expressions can navigate to relationship fields (related entities), but not to persistent fields. To access a persistent field, an expression uses the period as a delimiter.
Expressions cannot navigate beyond (or further qualify) relationship fields that are collections. In the syntax of an expression, a collection-valued field is a terminal symbol. Because the teams field is a collection, the WHERE clause cannot specify p.teams.city (an illegal expression).
See also: Path Expressions
SELECT DISTINCT p FROM Player p, IN (p.teams) t WHERE t.league = :league
Data retrieved: The players that belong to the specified league.
Description: The expressions in this query navigate over two relationships. The p.teams expression navigates the Player-Team relationship, and the t.league expression navigates the Team-League relationship.
In the other examples, the input parameters are String objects, but in this example the parameter is an object whose type is a League. This type matches the league relationship field in the comparison expression of the WHERE clause.
SELECT DISTINCT p FROM Player p, IN (p.teams) t WHERE t.league.sport = :sport
Data retrieved: The players who participate in the specified sport.
Description: The sport persistent field belongs to the League entity. To reach the sport field, the query must first navigate from the Player entity to Team (p.teams) and then from Team to the League entity (t.league). Because the league relationship field is not a collection, it can be followed by the sport persistent field.
Every WHERE clause must specify a conditional expression, of which there are several kinds. In the previous examples, the conditional expressions are comparison expressions that test for equality. The following examples demonstrate some of the other kinds of conditional expressions. For descriptions of all conditional expressions, see the section WHERE Clause.
SELECT p FROM Player p WHERE p.name LIKE ’Mich%’
Data retrieved: All players whose names begin with “Mich.”
Description: The LIKE expression uses wildcard characters to search for strings that match the wildcard pattern. In this case, the query uses the LIKE expression and the % wildcard to find all players whose names begin with the string “Mich.” For example, “Michael” and “Michelle” both match the wildcard pattern.
See also: LIKE Expressions
SELECT t FROM Team t WHERE t.league IS NULL
Data retrieved: All teams not associated with a league.
Description: The IS NULL expression can be used to check if a relationship has been set between two entities. In this case, the query checks to see if the teams are associated with any leagues, and returns the teams that do not have a league.
See also: NULL Comparison Expressions, NULL Values
SELECT p FROM Player p WHERE p.teams IS EMPTY
Data retrieved: All players who do not belong to a team.
Description: The teams relationship field of the Player entity is a collection. If a player does not belong to a team, then the teams collection is empty and the conditional expression is TRUE.
See also: Empty Collection Comparison Expressions
SELECT DISTINCT p FROM Player p WHERE p.salary BETWEEN :lowerSalary AND :higherSalary
Data retrieved: The players whose salaries fall within the range of the specified salaries.
Description: This BETWEEN expression has three arithmetic expressions: a persistent field (p.salary) and the two input parameters (:lowerSalary and :higherSalary). The following expression is equivalent to the BETWEEN expression:
p.salary >= :lowerSalary AND p.salary <= :higherSalary
See also: BETWEEN Expressions
SELECT DISTINCT p1 FROM Player p1, Player p2 WHERE p1.salary > p2.salary AND p2.name = :name
Data retrieved: All players whose salaries are higher than the salary of the player with the specified name.
Description: The FROM clause declares two identification variables (p1 and p2) of the same type (Player). Two identification variables are needed because the WHERE clause compares the salary of one player (p2) with that of the other players (p1).
See also: Identification Variables
The following examples show how to use the UPDATE and DELETE expressions in queries. UPDATE and DELETE operate on multiple entities according to the condition or conditions set in the WHERE clause. The WHERE clause in UPDATE and DELETE queries follows the same rules as SELECT queries.
UPDATE Player p SET p.status = ’inactive’ WHERE p.lastPlayed < :inactiveThresholdDate
Description: This query sets the status of a set of players to inactive if the player’s last game was longer than the date specified in inactiveThresholdDate.
DELETE FROM Player p WHERE p.status = ’inactive’ AND p.teams IS EMPTY
Description: This query deletes all inactive players who are not on a team.
This section discusses the query language syntax, as defined in the Java Persistence specification. Much of the following material paraphrases or directly quotes the specification.
Table 21–1 describes the BNF symbols used in this chapter.
Table 21–1 BNF Symbol Summary
Symbol |
Description |
---|---|
::= |
The element to the left of the symbol is defined by the constructs on the right. |
* |
The preceding construct may occur zero or more times. |
{...} |
The constructs within the curly braces are grouped together. |
[...] |
The constructs within the square brackets are optional. |
| |
An exclusive OR. |
BOLDFACE |
A keyword (although capitalized in the BNF diagram, keywords are not case-sensitive). |
White space |
A white space character can be a space, a horizontal tab, or a line feed. |
Here is the entire BNF diagram for the query language:
QL_statement ::= select_statement | update_statement | delete_statement select_statement ::= select_clause from_clause [where_clause] [groupby_clause] [having_clause] [orderby_clause] update_statement ::= update_clause [where_clause] delete_statement ::= delete_clause [where_clause] from_clause ::= FROM identification_variable_declaration {, {identification_variable_declaration | collection_member_declaration}}* identification_variable_declaration ::= range_variable_declaration { join | fetch_join }* range_variable_declaration ::= abstract_schema_name [AS] identification_variable join ::= join_spec join_association_path_expression [AS] identification_variable fetch_join ::= join_specFETCH join_association_path_expression association_path_expression ::= collection_valued_path_expression | single_valued_association_path_expression join_spec::= [LEFT [OUTER] |INNER] JOIN join_association_path_expression ::= join_collection_valued_path_expression | join_single_valued_association_path_expression join_collection_valued_path_expression::= identification_variable.collection_valued_association_field join_single_valued_association_path_expression::= identification_variable.single_valued_association_field collection_member_declaration ::= IN (collection_valued_path_expression) [AS] identification_variable single_valued_path_expression ::= state_field_path_expression | single_valued_association_path_expression state_field_path_expression ::= {identification_variable | single_valued_association_path_expression}.state_field single_valued_association_path_expression ::= identification_variable.{single_valued_association_field.}* single_valued_association_field collection_valued_path_expression ::= identification_variable.{single_valued_association_field.}* collection_valued_association_field state_field ::= {embedded_class_state_field.}*simple_state_field update_clause ::=UPDATE abstract_schema_name [[AS] identification_variable] SET update_item {, update_item}* update_item ::= [identification_variable.]{state_field | single_valued_association_field} = new_value new_value ::= simple_arithmetic_expression | string_primary | datetime_primary | boolean_primary | enum_primary simple_entity_expression | NULL delete_clause ::= DELETE FROM abstract_schema_name [[AS] identification_variable] select_clause ::= SELECT [DISTINCT] select_expression {, select_expression}* select_expression ::= single_valued_path_expression | aggregate_expression | identification_variable | OBJECT(identification_variable) | constructor_expression constructor_expression ::= NEW constructor_name(constructor_item {, constructor_item}*) constructor_item ::= single_valued_path_expression | aggregate_expression aggregate_expression ::= {AVG |MAX |MIN |SUM} ([DISTINCT] state_field_path_expression) | COUNT ([DISTINCT] identification_variable | state_field_path_expression | single_valued_association_path_expression) where_clause ::= WHERE conditional_expression groupby_clause ::= GROUP BY groupby_item {, groupby_item}* groupby_item ::= single_valued_path_expression having_clause ::= HAVING conditional_expression orderby_clause ::= ORDER BY orderby_item {, orderby_item}* orderby_item ::= state_field_path_expression [ASC |DESC] subquery ::= simple_select_clause subquery_from_clause [where_clause] [groupby_clause] [having_clause] subquery_from_clause ::= FROM subselect_identification_variable_declaration {, subselect_identification_variable_declaration}* subselect_identification_variable_declaration ::= identification_variable_declaration | association_path_expression [AS] identification_variable | collection_member_declaration simple_select_clause ::= SELECT [DISTINCT] simple_select_expression simple_select_expression::= single_valued_path_expression | aggregate_expression | identification_variable conditional_expression ::= conditional_term | conditional_expression OR conditional_term conditional_term ::= conditional_factor | conditional_term AND conditional_factor conditional_factor ::= [NOT] conditional_primary conditional_primary ::= simple_cond_expression |( conditional_expression) simple_cond_expression ::= comparison_expression | between_expression | like_expression | in_expression | null_comparison_expression | empty_collection_comparison_expression | collection_member_expression | exists_expression between_expression ::= arithmetic_expression [NOT] BETWEEN arithmetic_expressionAND arithmetic_expression | string_expression [NOT] BETWEEN string_expression AND string_expression | datetime_expression [NOT] BETWEEN datetime_expression AND datetime_expression in_expression ::= state_field_path_expression [NOT] IN (in_item {, in_item}* | subquery) in_item ::= literal | input_parameter like_expression ::= string_expression [NOT] LIKE pattern_value [ESCAPE escape_character] null_comparison_expression ::= {single_valued_path_expression | input_parameter} IS [NOT] NULL empty_collection_comparison_expression ::= collection_valued_path_expression IS [NOT] EMPTY collection_member_expression ::= entity_expression [NOT] MEMBER [OF] collection_valued_path_expression exists_expression::= [NOT] EXISTS (subquery) all_or_any_expression ::= {ALL |ANY |SOME} (subquery) comparison_expression ::= string_expression comparison_operator {string_expression | all_or_any_expression} | boolean_expression {= |<> } {boolean_expression | all_or_any_expression} | enum_expression {= |<> } {enum_expression | all_or_any_expression} | datetime_expression comparison_operator {datetime_expression | all_or_any_expression} | entity_expression {= |<> } {entity_expression | all_or_any_expression} | arithmetic_expression comparison_operator {arithmetic_expression | all_or_any_expression} comparison_operator ::= = |> |>= |< |<= |<> arithmetic_expression ::= simple_arithmetic_expression | (subquery) simple_arithmetic_expression ::= arithmetic_term | simple_arithmetic_expression {+ |- } arithmetic_term arithmetic_term ::= arithmetic_factor | arithmetic_term {* |/ } arithmetic_factor arithmetic_factor ::= [{+ |- }] arithmetic_primary arithmetic_primary ::= state_field_path_expression | numeric_literal | (simple_arithmetic_expression) | input_parameter | functions_returning_numerics | aggregate_expression string_expression ::= string_primary | (subquery) string_primary ::= state_field_path_expression | string_literal | input_parameter | functions_returning_strings | aggregate_expression datetime_expression ::= datetime_primary | (subquery) datetime_primary ::= state_field_path_expression | input_parameter | functions_returning_datetime | aggregate_expression boolean_expression ::= boolean_primary | (subquery) boolean_primary ::= state_field_path_expression | boolean_literal | input_parameter enum_expression ::= enum_primary | (subquery) enum_primary ::= state_field_path_expression | enum_literal | input_parameter entity_expression ::= single_valued_association_path_expression | simple_entity_expression simple_entity_expression ::= identification_variable | input_parameter functions_returning_numerics::= LENGTH(string_primary) | LOCATE(string_primary, string_primary[, simple_arithmetic_expression]) | ABS(simple_arithmetic_expression) | SQRT(simple_arithmetic_expression) | MOD(simple_arithmetic_expression, simple_arithmetic_expression) | SIZE(collection_valued_path_expression) functions_returning_datetime ::= CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP functions_returning_strings ::= CONCAT(string_primary, string_primary) | SUBSTRING(string_primary, simple_arithmetic_expression, simple_arithmetic_expression)| TRIM([[trim_specification] [trim_character] FROM] string_primary) | LOWER(string_primary) | UPPER(string_primary) trim_specification ::= LEADING | TRAILING | BOTH
The FROM clause defines the domain of the query by declaring identification variables.
An identifier is a sequence of one or more characters. The first character must be a valid first character (letter, $, _) in an identifier of the Java programming language (hereafter in this chapter called simply “Java”). Each subsequent character in the sequence must be a valid non-first character (letter, digit, $, _) in a Java identifier. (For details, see the Java SE API documentation of the isJavaIdentifierStart and isJavaIdentifierPart methods of the Character class.) The question mark (?) is a reserved character in the query language and cannot be used in an identifier.
A query language identifier is case-sensitive with two exceptions:
Keywords
Identification variables
An identifier cannot be the same as a query language keyword. Here is a list of query language keywords:
ABS |
ALL |
AND |
ANY |
AS |
ASC |
AVG |
BETWEEN |
BIT_LENGTH |
BOTH |
BY |
CASE |
CHAR_LENGTH |
CHARACTER_LENGTH |
CLASS |
COALESCE |
CONCAT |
COUNT |
CURRENT_DATE |
CURRENT_TIMESTAMP |
DELETE |
DESC |
DISTINCT |
ELSE |
EMPTY |
END |
ENTRY |
ESCAPE |
EXISTS |
FALSE |
FETCH |
FROM |
GROUP |
HAVING |
IN |
INDEX |
INNER |
IS |
JOIN |
KEY |
LEADING |
LEFT |
LENGTH |
LIKE |
LOCATE |
LOWER |
MAX |
MEMBER |
MIN |
MOD |
NEW |
NOT |
NULL |
NULLIF |
OBJECT |
OF |
OR |
ORDER |
OUTER |
POSITION |
SELECT |
SET |
SIZE |
SOME |
SQRT |
SUBSTRING |
SUM |
THEN |
TRAILING |
TRIM |
TRUE |
TYPE |
UNKNOWN |
UPDATE |
UPPER |
VALUE |
WHEN |
WHERE |
It is not recommended that you use a SQL keyword as an identifier, because the list of keywords may expand to include other reserved SQL words in the future.
An identification variable is an identifier declared in the FROM clause. Although the SELECT and WHERE clauses can reference identification variables, they cannot declare them. All identification variables must be declared in the FROM clause.
Because an identification variable is an identifier, it has the same naming conventions and restrictions as an identifier with the exception that an identification variables is case-insensitive. For example, an identification variable cannot be the same as a query language keyword. (See the preceding section for more naming rules.) Also, within a given persistence unit, an identification variable name must not match the name of any entity or abstract schema.
The FROM clause can contain multiple declarations, separated by commas. A declaration can reference another identification variable that has been previously declared (to the left). In the following FROM clause, the variable t references the previously declared variable p:
FROM Player p, IN (p.teams) AS t
Even if an identification variable is not used in the WHERE clause, its declaration can affect the results of the query. For an example, compare the next two queries. The following query returns all players, whether or not they belong to a team:
SELECT p FROM Player p
In contrast, because the next query declares the t identification variable, it fetches all players that belong to a team:
SELECT p FROM Player p, IN (p.teams) AS t
The following query returns the same results as the preceding query, but the WHERE clause makes it easier to read:
SELECT p FROM Player p WHERE p.teams IS NOT EMPTY
An identification variable always designates a reference to a single value whose type is that of the expression used in the declaration. There are two kinds of declarations: range variable and collection member.
To declare an identification variable as an abstract schema type, you specify a range variable declaration. In other words, an identification variable can range over the abstract schema type of an entity. In the following example, an identification variable named p represents the abstract schema named Player:
FROM Player p
A range variable declaration can include the optional AS operator:
FROM Player AS p
In most cases, to obtain objects a query uses path expressions to navigate through the relationships. But for those objects that cannot be obtained by navigation, you can use a range variable declaration to designate a starting point (or root).
If the query compares multiple values of the same abstract schema type, then the FROM clause must declare multiple identification variables for the abstract schema:
FROM Player p1, Player p2
For a sample of such a query, see Comparison Operators.
In a one-to-many relationship, the multiple side consists of a collection of entities. An identification variable can represent a member of this collection. To access a collection member, the path expression in the variable’s declaration navigates through the relationships in the abstract schema. (For more information on path expressions, see the following section.) Because a path expression can be based on another path expression, the navigation can traverse several relationships. See Traversing Multiple Relationships.
A collection member declaration must include the IN operator, but it can omit the optional AS operator.
In the following example, the entity represented by the abstract schema named Player has a relationship field called teams. The identification variable called t represents a single member of the teams collection.
FROM Player p, IN (p.tea ms) t
The JOIN operator is used to traverse over relationships between entities, and is functionally similar to the IN operator.
In the following example, the query joins over the relationship between customers and orders:
SELECT c FROM Customer c JOIN c.orders o WHERE c.status = 1 AND o.totalPrice > 10000
The INNER keyword is optional:
SELECT c FROM Customer c INNER JOIN c.orders o WHERE c.status = 1 AND o.totalPrice > 10000
These examples are equivalent to the following query, which uses the IN operator:
SELECT c FROM Customer c, IN(c.orders) o WHERE c.status = 1 AND o.totalPrice > 10000
You can also join a single-valued relationship.
SELECT t FROM Team t JOIN t.league l WHERE l.sport = :sport
A LEFT JOIN or LEFT OUTER JOIN retrieves a set of entities where matching values in the join condition may be absent. The OUTER keyword is optional.
SELECT c.name, o.totalPrice FROM Order o LEFT JOIN o.customer c
A FETCH JOIN is a join operation that returns associated entities as a side-effect of running the query. In the following example, the query returns a set of departments, and as a side-effect, the associated employees of the departments, even though the employees were not explicitly retrieved by the SELECT clause.
SELECT d FROM Department d LEFT JOIN FETCH d.employees WHERE d.deptno = 1
Path expressions are important constructs in the syntax of the query language, for several reasons. First, they define navigation paths through the relationships in the abstract schema. These path definitions affect both the scope and the results of a query. Second, they can appear in any of the main clauses of a query (SELECT, DELETE, HAVING, UPDATE, WHERE, FROM, GROUP BY, ORDER BY). Finally, although much of the query language is a subset of SQL, path expressions are extensions not found in SQL.
Here, the WHERE clause contains a single_valued_path_expression. The p is an identification variable, and salary is a persistent field of Player.
SELECT DISTINCT p FROM Player p WHERE p.salary BETWEEN :lowerSalary AND :higherSalary
Here, the WHERE clause also contains a single_valued_path_expression. The t is an identification variable, league is a single-valued relationship field, and sport is a persistent field of league.
SELECT DISTINCT p FROM Player p, IN (p.teams) t WHERE t.league.sport = :sport
Here, the WHERE clause contains a collection_valued_path_expression. The p is an identification variable, and teams designates a collection-valued relationship field.
SELECT DISTINCT p FROM Player p WHERE p.teams IS EMPTY
The type of a path expression is the type of the object represented by the ending element, which can be one of the following:
Persistent field
Single-valued relationship field
Collection-valued relationship field
For example, the type of the expression p.salary is double because the terminating persistent field (salary) is a double.
In the expression p.teams, the terminating element is a collection-valued relationship field (teams). This expression’s type is a collection of the abstract schema type named Team. Because Team is the abstract schema name for the Team entity, this type maps to the entity. For more information on the type mapping of abstract schemas, see the section Return Types.
A path expression enables the query to navigate to related entities. The terminating elements of an expression determine whether navigation is allowed. If an expression contains a single-valued relationship field, the navigation can continue to an object that is related to the field. However, an expression cannot navigate beyond a persistent field or a collection-valued relationship field. For example, the expression p.teams.league.sport is illegal, because teams is a collection-valued relationship field. To reach the sport field, the FROM clause could define an identification variable named t for the teams field:
FROM Player AS p, IN (p.teams) t WHERE t.league.sport = ’soccer’
The WHERE clause specifies a conditional expression that limits the values returned by the query. The query returns all corresponding values in the data store for which the conditional expression is TRUE. Although usually specified, the WHERE clause is optional. If the WHERE clause is omitted, then the query returns all values. The high-level syntax for the WHERE clause follows:
where_clause ::= WHERE conditional_expression
There are four kinds of literals: string, numeric, Boolean, and enum.
A string literal is enclosed in single quotes:
’Duke’
If a string literal contains a single quote, you indicate the quote by using two single quotes:
’Duke’’s’
Like a Java String, a string literal in the query language uses the Unicode character encoding.
There are two types of numeric literals: exact and approximate.
An exact numeric literal is a numeric value without a decimal point, such as 65,– 233, and +12. Using the Java integer syntax, exact numeric literals support numbers in the range of a Java long.
An approximate numeric literal is a numeric value in scientific notation, such as 57.,– 85.7, and +2.1. Using the syntax of the Java floating-point literal, approximate numeric literals support numbers in the range of a Java double.
A Boolean literal is either TRUE or FALSE. These keywords are not case-sensitive.
The Java Persistence Query Language supports the use of enum literals using the Java enum literal syntax. The enum class name must be specified as fully qualified class name.
SELECT e FROM Employee e WHERE e.status = com.xyz.EmployeeStatus.FULL_TIME
An input parameter can be either a named parameter or a positional parameter.
A named input parameter is designated by a colon (:) followed by a string. For example, :name.
A positional input parameter is designated by a question mark (?) followed by an integer. For example, the first input parameter is ?1, the second is ?2, and so forth.
The following rules apply to input parameters:
They can be used only in a WHERE or HAVING clause.
Positional parameters must be numbered, starting with the integer 1.
Named parameters and positional parameters may not be mixed in a single query.
Named parameters are case-sensitive.
A WHERE clause consists of a conditional expression, which is evaluated from left to right within a precedence level. You can change the order of evaluation by using parentheses.
Table 21–2 lists the query language operators in order of decreasing precedence.
Table 21–2 Query Language Order Precedence
Type |
Precedence Order |
---|---|
Navigation |
. (a period) |
Arithmetic |
+ – (unary) * / (multiplication and division) + – (addition and subtraction) |
Comparison |
= > >= < <= <> (not equal) [NOT] BETWEEN [NOT] LIKE [NOT] IN IS [NOT] NULL IS [NOT] EMPTY [NOT] MEMBER OF |
Logical |
NOT AND OR |
A BETWEEN expression determines whether an arithmetic expression falls within a range of values.
These two expressions are equivalent:
p.age BETWEEN 15 AND 19 p.age >= 15 AND p.age <= 19
The following two expressions are also equivalent:
p.age NOT BETWEEN 15 AND 19 p.age < 15 OR p.age > 19
If an arithmetic expression has a NULL value, then the value of the BETWEEN expression is unknown.
An IN expression determines whether or not a string belongs to a set of string literals, or whether a number belongs to a set of number values.
The path expression must have a string or numeric value. If the path expression has a NULL value, then the value of the IN expression is unknown.
In the following example, if the country is UK the expression is TRUE. If the country is Peru it is FALSE.
o.country IN (’UK’, ’US’, ’France’)
You may also use input parameters:
o.country IN (’UK’, ’US’, ’France’, :country)
A LIKE expression determines whether a wildcard pattern matches a string.
The path expression must have a string or numeric value. If this value is NULL, then the value of the LIKE expression is unknown. The pattern value is a string literal that can contain wildcard characters. The underscore (_) wildcard character represents any single character. The percent (%) wildcard character represents zero or more characters. The ESCAPE clause specifies an escape character for the wildcard characters in the pattern value. Table 21–3 shows some sample LIKE expressions.
Table 21–3 LIKE Expression Examples
Expression |
TRUE |
FALSE |
---|---|---|
address.phone LIKE ’12%3’ |
’123’ ’12993’ |
’1234’ |
asentence.word LIKE ’l_se’ |
’lose’ |
’loose’ |
aword.underscored LIKE ’\_%’ ESCAPE ’\’ |
’_foo’ |
’bar’ |
address.phone NOT LIKE ’12%3’ |
’1234’ |
’123’ ’12993’ |
A NULL comparison expression tests whether a single-valued path expression or an input parameter has a NULL value. Usually, the NULL comparison expression is used to test whether or not a single-valued relationship has been set.
SELECT t FROM Team t WHERE t.league IS NULL
This query selects all teams where the league relationship is not set. Please note, the following query is not equivalent:
SELECT t FROM Team t WHERE t.league = NULL
The comparison with NULL using the equals operator (=) always returns an unknown value, even if the relationship is not set. The second query will always return an empty result.
The IS [NOT] EMPTY comparison expression tests whether a collection-valued path expression has no elements. In other words, it tests whether or not a collection-valued relationship has been set.
If the collection-valued path expression is NULL, then the empty collection comparison expression has a NULL value.
Here is an example that finds all orders that do not have any line items:
SELECT o FROM Order o WHERE o.lineItems IS EMPTY
The [NOT] MEMBER [OF] collection member expression determines whether a value is a member of a collection. The value and the collection members must have the same type.
If either the collection-valued or single-valued path expression is unknown, then the collection member expression is unknown. If the collection-valued path expression designates an empty collection, then the collection member expression is FALSE.
The OF keyword is optional.
The following example tests whether a line item is part of an order:
SELECT o FROM Order o WHERE :lineItem MEMBER OF o.lineItems
Subqueries may be used in the WHERE or HAVING clause of a query. Subqueries must be surrounded by parentheses.
The following example find all customers who have placed more than 10 orders:
SELECT c FROM Customer c WHERE (SELECT COUNT(o) FROM c.orders o) > 10
The [NOT] EXISTS expression is used with a subquery, and is true only if the result of the subquery consists of one or more values and is false otherwise.
The following example finds all employees whose spouse is also an employee:
SELECT DISTINCT emp FROM Employee emp WHERE EXISTS ( SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse)
The ALL expression is used with a subquery, and is true if all the values returned by the subquery are true, or if the subquery is empty.
The ANY expression is used with a subquery, and is true if some of the values returned by the subquery are true. An ANY expression is false if the subquery result is empty, or if all the values returned are false. The SOME keyword is synonymous with ANY.
The ALL and ANY expressions are used with the =, <, <=, >, >=, <> comparison operators.
The following example finds all employees whose salary is higher than the salary of the managers in the employee’s department:
SELECT emp FROM Employee emp WHERE emp.salary > ALL ( SELECT m.salary FROM Manager m WHERE m.department = emp.department)
The query language includes several string, arithmetic, and date/time functions which may be used in the SELECT, WHERE, or HAVING clause of a query. The functions are listed in the following tables. In Table 21–4, the start and length arguments are of type int. They designate positions in the String argument. The first position in a string is designated by 1. In Table 21–5, the number argument can be either an int, a float, or a double.
Table 21–4 String Expressions
Function Syntax |
Return Type |
---|---|
String |
|
int |
|
int |
|
String |
|
String |
|
String |
|
String |
The CONCAT function concatenates two strings into one string.
The LENGTH function returns the length of a string in characters as an integer.
The LOCATE function returns the position of a given string within a string. It returns the first position at which the string was found as an integer. The first argument is the string to be located. The second argument is the string to be searched. The optional third argument is an integer that represents the starting string position. By default, LOCATE starts at the beginning of the string. The starting position of a string is 1. If the string cannot be located, LOCATE returns 0.
The SUBSTRING function returns a string that is a substring of the first argument based on the starting position and length.
The TRIM function trims the specified character from the beginning and/or end of a string. If no character is specified, TRIM removes spaces or blanks from the string. If the optional LEADING specification is used, TRIM removes only the leading characters from the string. If the optional TRAILING specification is used, TRIM removes only the trailing characters from the string. The default is BOTH, which removes the leading and trailing characters from the string.
The LOWER and UPPER functions convert a string to lower or upper case, respectively.
Table 21–5 Arithmetic Expressions
Function Syntax |
Return Type |
---|---|
ABS(number) |
int, float, or double |
MOD(int, int) |
int |
SQRT(double) |
double |
SIZE(Collection) |
int |
The ABS function takes a numeric expression and returns a number of the same type as the argument.
The MOD function returns the remainder of the first argument divided by the second.
The SQRT function returns the square root of a number.
The SIZE function returns an integer of the number of elements in the given collection.
Date/time functions return the date, time, or timestamp on the database server.
Table 21–6 Date/Time Expressions
Function Syntax |
Return Type |
---|---|
CURRENT_DATE |
java.sql.Date |
CURRENT_TIME |
java.sql.Time |
CURRENT_TIMESTAMP |
java.sql.Timestamp |
Case expressions are expressions that change based on a condition, similar to the case keyword of the Java programming language. The CASE keyword indicates the start of a case expression, and the expression is terminated by the END keyword. The WHEN and THEN keyword define individual conditions, and the ELSE keyword defines the default condition should none of the other conditions be satisfied.
The following query selects the name of a person and a conditional string, depending on the subtype of the Person entity. If the subtype is Student, it returns the string kid. If the subtype is Guardian or Staff, it returns adult. If the entity is some other subtype of Person, the string unknown is returned.
SELECT p.name CASE TYPE(p) WHEN Student THEN 'kid' WHEN Guardian THEN 'adult' WHEN Staff THEN 'adult' ELSE 'unknown' END FROM Person p
The following query sets a discount for different types of customers. Gold-level customers get a 20% discount, silver-level customers get a 15% discount, bronze-level customers get a 10% discount, and everyone else gets a 5% discount.
UPDATE Customer c SET c.discount = CASE c.level WHEN 'Gold' THEN 20 WHEN 'SILVER' THEN 15 WHEN 'Bronze' THEN 10 ELSE 5 END
If the target of a reference is not in the persistent store, then the target is NULL. For conditional expressions containing NULL, the query language uses the semantics defined by SQL92. Briefly, these semantics are as follows:
If a comparison or arithmetic operation has an unknown value, it yields a NULL value.
Two NULL values are not equal. Comparing two NULL values yields an unknown value.
The IS NULL test converts a NULL persistent field or a single-valued relationship field to TRUE. The IS NOT NULL test converts them to FALSE.
Boolean operators and conditional tests use the three-valued logic defined by Table 21–7 and Table 21–8. (In these tables, T stands for TRUE, F for FALSE, and U for unknown.)
AND |
T |
F |
U |
---|---|---|---|
T |
T |
F |
U |
F |
F |
F |
F |
U |
U |
F |
U |
Table 21–8 OR Operator Logic
OR |
T |
F |
U |
---|---|---|---|
T |
T |
T |
T |
F |
T |
F |
U |
U |
T |
U |
U |
In the query language, only values of the same type can be compared. However, this rule has one exception: Exact and approximate numeric values can be compared. In such a comparison, the required type conversion adheres to the rules of Java numeric promotion.
The query language treats compared values as if they were Java types and not as if they represented types in the underlying data store. For example, if a persistent field could be either an integer or a NULL, then it must be designated as an Integer object and not as an int primitive. This designation is required because a Java object can be NULL but a primitive cannot.
Two strings are equal only if they contain the same sequence of characters. Trailing blanks are significant; for example, the strings ’abc’ and ’abc ’ are not equal.
Two entities of the same abstract schema type are equal only if their primary keys have the same value. Table 21–9 shows the operator logic of a negation, and Table 21–10 shows the truth values of conditional tests.
Table 21–9 NOT Operator Logic
NOT Value |
Value |
---|---|
T |
F |
F |
T |
U |
U |
Table 21–10 Conditional Test
Conditional Test |
T |
F |
U |
---|---|---|---|
Expression IS TRUE |
T |
F |
F |
Expression IS FALSE |
F |
T |
F |
Expression is unknown |
F |
F |
T |
The SELECT clause defines the types of the objects or values returned by the query.
The return type of the SELECT clause is defined by the result types of the select expressions contained within it. If multiple expressions are used, the result of the query is an Object[], and the elements in the array correspond to the order of the expressions in the SELECT clause, and in type to the result types of each expression.
A SELECT clause cannot specify a collection-valued expression. For example, the SELECT clause p.teams is invalid because teams is a collection. However, the clause in the following query is valid because the t is a single element of the teams collection:
SELECT t FROM Player p, IN (p.teams) t
The following query is an example of a query with multiple expressions in the select clause:
SELECT c.name, c.country.name FROM customer c WHERE c.lastname = ’Coss’ AND c.firstname = ’Roxane’
It returns a list of Object[] elements where the first array element is a string denoting the customer name and the second array element is a string denoting the name of the customer’s country.
The result of a query may be the result of an aggregate function, listed in Table 21–11.
Table 21–11 Aggregate Functions in Select Statements
Name |
Return Type |
Description |
---|---|---|
Double |
Returns the mean average of the fields. |
|
Long |
Returns the total number of results. |
|
the type of the field |
Returns the highest value in the result set. |
|
the type of the field |
Returns the lowest value in the result set. |
|
Long (for integral fields)Double (for floating point fields)BigInteger (for BigInteger fields)BigDecimal (for BigDecimal fields) |
Returns the sum of all the values in the result set. |
For select method queries with an aggregate function (AVG, COUNT, MAX, MIN, or SUM) in the SELECT clause, the following rules apply:
For the AVG, MAX, MIN, and SUM functions, the functions return null if there are no values to which the function can be applied.
For the COUNT function, if there are no values to which the function can be applied, COUNT returns 0.
The following example returns the average order quantity:
SELECT AVG(o.quantity) FROM Order o
The following example returns the total cost of the items ordered by Roxane Coss:
SELECT SUM(l.price) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = ’Coss’ AND c.firstname = ’Roxane’
The following example returns the total number of orders:
SELECT COUNT(o) FROM Order o
The following example returns the total number of items in Hal Incandenza’s order that have prices:
SELECT COUNT(l.price) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = ’Incandenza’ AND c.firstname = ’Hal’
The DISTINCT keyword eliminates duplicate return values. If a query returns a java.util.Collection, which allows duplicates, then you must specify the DISTINCT keyword to eliminate duplicates.
Constructor expressions allow you to return Java instances that store a query result element instead of an Object[].
The following query creates a CustomerDetail instance per Customer matching the WHERE clause. A CustomerDetail stores the customer name and customer’s country name. So the query returns a List of CustomerDetail instances:
SELECT NEW com.xyz.CustomerDetail(c.name, c.country.name) FROM customer c WHERE c.lastname = ’Coss’ AND c.firstname = ’Roxane’
As its name suggests, the ORDER BY clause orders the values or objects returned by the query.
If the ORDER BY clause contains multiple elements, the left-to-right sequence of the elements determines the high-to-low precedence.
The ASC keyword specifies ascending order (the default), and the DESC keyword indicates descending order.
When using the ORDER BY clause, the SELECT clause must return an orderable set of objects or values. You cannot order the values or objects for values or objects not returned by the SELECT clause. For example, the following query is valid because the ORDER BY clause uses the objects returned by the SELECT clause:
SELECT o FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = ’CA’ ORDER BY o.quantity, o.totalcost
The following example is not valid because the ORDER BY clause uses a value not returned by the SELECT clause:
SELECT p.product_name FROM Order o, IN(o.lineItems) l JOIN o.customer c WHERE c.lastname = ’Faehmel’ AND c.firstname = ’Robert’ ORDER BY o.quantity
The GROUP BY clause allows you to group values according to a set of properties.
The following query groups the customers by their country and returns the number of customers per country:
SELECT c.country, COUNT(c) FROM Customer c GROUP BY c.country
The HAVING clause is used with the GROUP BY clause to further restrict the returned result of a query.
The following query groups orders by the status of their customer and returns the customer status plus the average totalPrice for all orders where the corresponding customers has the same status. In addition, it considers only customers with status 1, 2, or 3, so orders of other customers are not taken into account:
SELECT c.status, AVG(o.totalPrice) FROM Order o JOIN o.customer c GROUP BY c.status HAVING c.status IN (1, 2, 3)
The Criteria API is used to define queries for entities and their persistent state by creating query-defining objects. Criteria queries are written using Java programming language APIs, are type-safe, and are portable queries that work regardless of the underlying data store.
Similar to JPQL, the Criteria API is based on the abstract schema of persistent entities, their relationships, and embedded objects. The Criteria API operates on this abstract schema to allow developers to find, modify, and delete persistent entities by invoking Java Persistence API entity operations.
The Metamodel API works in concert with the Criteria API to model persistent entity classes for Criteria queries.
The Criteria API and JPQL are closely related, and designed to allow similar operations in their queries. Developers familiar with JPQL syntax will find equivalent object-level operations in the Criteria API.
The following simple Criteria query returns all instances of the Pet entity in the data source.
EntityManager em = ...; CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.select(pet); TypeQuery<Pet> q = em.createQuery(cq); List<Pet> allPets = q.getResultList();
The equivalent JPQL query is:
SELECT p FROM Pet p
This query demonstrates the basic steps to create a Criteria query:
Use an EntityManager instance to create a CriteriaBuilder object.
Create a query object by creating an instance of the CriteriaQuery interface. This query object's attributes will be modified with the details of the query.
Set the query root by calling the from method on the CriteriaQuery object.
Specify what the type of the query result will be by calling the select method of the CriteriaQuery object.
Prepare the query for execution by creating a TypedQuery<T> instance, specifying the type of the query result.
Execute the query by calling the getResultList method on the TypedQuery<T> object. Because this query returns a collection of entities, the result is stored in a List.
The tasks associated with each step are discussed in detail in this chapter.
To create a CriteriaBuilder instance call the getCriteriaBuilder method on the EntityManager instance:
CriteriaBuilder cb = em.getCriteriaBuilder();
The actual query object is created using the CriteriaBuilder instance:
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
The query will return instances of the Pet entity, so the type of the query is specified when the CriteriaQuery object is created to create a type-safe query.
The from clause of the query is set, and the root of the query specified, by calling the from method of the query object:
Root<Pet> pet = cq.from(Pet.class);
The select clause of the query is set by calling the select method of the query object, and passing in the query root:
cq.select(pet);
The query object is now used to create a TypedQuery<T> object that can be executed against the data source. The modifications to the query object are captured to create a ready-to-execute query.
TypeQuery<Pet> q = em.createQuery(cq);
This typed query object is executed by calling its getResultList method, because this query will return multiple entity instances. The results are stored in a List<Pet> collection-valued object.
List<Pet> allPets = q.getResultList();
The Metamodel API is used to create a metamodel of the managed entities in a particular persistence unit. For each entity class in a particular package, a metamodel class is created with a trailing underscore, and with attributes that correspond to the persistent fields or properties of the entity class.
The following entity class com.example.Pet has four persistent fields, id, name, color, and owners.
package com.example; ... @Entity public class Pet { @Id protected Long id; protected String name; protected String color; @ManyToOne protected Set<Person> owners; ... }
The corresponding Metamodel class is:
package com.example; ... @Static Metamodel(Pet.class) public class Pet_ { public static volatile SingularAttribute<Pet, Long> id; public static volatile SingularAttribute<Pet, String> name; public static volatile SingularAttribute<Pet, String> color; public static volatile SetAttribute<Pet, Person> owners; }
The metamodel class and its attributes are used in Criteria queries to refer to the managed entity classes and their persistent state and relationships.
Metamodel classes that correspond to entity classes are of type javax.persistence.metamodel.EntityType<T>, and are typically generated by annotation processors either at development time or at runtime. Developers of applications that use Criteria queries may generate static metamodel classes using the persistence provider's annotation processor, or may obtain the metamodel class either by calling the getModel method on the query root object or first obtaining an instance of the Metamodel interface and then passing the entity type to the instance's entity method.
The following code snippet shows how to obtain the Pet entity's metamodel class by calling Root<T>.getModel.
EntityManager em = ...; CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Pet.class); Root<Pet> pet = cq.from(Pet.class); EntityType<Pet> Pet_ = pet.getModel();
The following code snippet shows how to obtain the Pet entity's metamodel class by first obtaining a metamodel instance using EntityManager.getMetamodel, and then calling entity on the metamodel instance.
EntityManager em = ...; Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class);
The basic semantics of a Criteria query consists of a select clause, a from clause, and an optional where clause, similar to a JPQL query. Criteria queries set these clauses using Java programming language objects, so the query can be created in a type-safe manner.
The javax.persistence.criteria.CriteriaBuilder interface is used to construct:
Criteria queries
selections
expressions
predicates
ordering
To obtain an instance of the CriteriaBuilder interface, call the getCriteriaBuilder method on either an EntityManager or EntityManagerFactory instance.
The following code shows how to obtain a CriteriaBuilder instance using the EntityManager.getCriteriaBuilder method.
EntityManager em = ...; CriteriaBuilder cb = em.getCriteriaBuilder();
Criteria queries are constructed by obtaining an instance of the javax.persistence.criteria.CriteriaQuery interface. CriteriaQuery objects define a particular query that will navigate over one or more entities. Obtain CriteriaQuery instances by calling one of the CrtieriaBuilder.createQuery methods. For creating type-safe queries, call the CriteriaBuilder.createQuery method as follows:
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
The CriteriaQuery object's type should be set to the expected result type of the query. In the code above, the object's type is set to CriteriaQuery<Pet> for a query which will find instances of the Pet entity.
In the following code snippet, a CriteriaQuery object is created for a query that returns a string.
CriteriaQuery<String> cq = cb.createQuery(String.class);
For a particular CriteriaQueryobject, the root entity of the query, from which all navigation originates, is called the query root. It is similar to the FROM clause in a JPQL query.
Create the query root by calling the from method on the CriteriaQuery instance. The argument to the from method is either the entity class, or an EntityType<T> instance for the entity.
The following code sets the query root to the Pet entity.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Root<Pet> pet = cq.from(Pet.class);
The following code sets the query root to the Pet class using an EntityType<T> instance.
EntityManager em = ...; Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet_);
Criteria queries may have more than one query root. This usually occurs when the query navigates from several entities.
The following code has two Root instances.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Root<Pet> pet1 = cq.from(Pet.class); Root<Pet> pet2 = cq.from(Pet.class);
For queries that navigate to related entity classes, the query must define a join to the related entity by calling one of the From.join methods on the query root object, or another join object. The join methods are similar to the JOIN keyword in JPQL.
The target of the join uses the Metamodel class of type EntityType<T> to specify the persistent field or property of the joined entity.
The join methods return an object of type Join<X, Y>, where X is the source entity and Y is the target of the join.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); Join<Pet, Owner> owner = pet.join(Pet_.owners);
Joins can be chained together to navigate to related entities of the target entity without having to create a Join<X, Y> instance for each join.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); EntityType<Owner> Owner_ = m.entity(Owner.class); Root<Pet> pet = cq.from(Pet.class); Join<Owner, Address> address = cq.join(Pet_.owners).join(Owner_.addresses);
Path objects are used in the select and where clauses of a Criteria query, and can be query root entities, join entities, or other Path objects. The Path.get method is used to navigate to attributes of the entities of a query.
The argument to the get method is the corresponding attribute of the entity's Metamodel class. The attribute can either be a single-valued attribute (specified by @SingularAttribute in the Metamodel class) or a collection-valued attribute (specified by one of @CollectionAttribute, @SetAttribute, @ListAttribute, or @MapAttribute).
CriteriaQuery<String> cq = cb.createQuery(String.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.select(pet.get(Pet_.name));
This query returns the names of all the pets in the data store. The get method is called on the query root, pet, with the name attribute of the Pet entity's Metamodel class, Pet_ as the argument.
The results of a query can be restricted on the CriteriaQuery object according to conditions set by calling the CriteriaQuery.where method. Calling the where method is analogous to setting the WHERE clause in a JPQL query.
The where method evaluates instances of the Expression interface to restrict the results according to the conditions of the expressions. Expression instances are created using methods defined in the Expression and CriteriaBuilder interfaces.
An Expression object is used to in a query's select, where, or having clause
Table 22–1 Conditional Methods in the Expression Interface
Method |
Description |
---|---|
isNull |
Tests whether an expression is null. |
isNotNull |
Tests whether an expression is not null. |
in |
Tests whether an expression is within a list of values. |
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.where(pet.get(Pet_.color).isNull());
This query finds all pets where the color attribute is null.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.where(pet.get(Pet_.color).in("brown", "black");
This query finds all brown and black pets.
The in method also can check whether an attribute is a member of a collection.
The CriteriaBuilder interface defines additional methods for creating expressions. These methods correspond to the arithmetic, string, date, time, and case operators and functions of JPQL.
Table 22–2 Conditional Methods in the CriteriaBuilder Interface
Conditional Method |
Description |
---|---|
equal |
Tests whether two expressions are equal. |
notEqual |
Tests whether two expressions are not equal. |
gt |
Tests whether the first numeric expression is greater than the second numeric expression. |
ge |
Tests whether the first numeric expression is greater than or equal to the second numeric expression. |
lt |
Tests whether the first numeric expression is less than the second numeric expression. |
le |
Tests whether the first numeric expression is less than or equal to the second numeric expression. |
between |
Tests whether the first expression is between the second and third expression in value. |
like |
Tests whether the expression matches a given pattern. |
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.where(cb.equal(pet.get(Pet_.name)), "Fido"); ...
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); Date someDate = new Date(...); cq.where(cb.gt(pet.get(Pet_.birthday)), date);
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); Date firstDate = new Date(...); Date secondDate = new Date(...); cq.where(cb.between(pet.get(Pet_.birthday)), firstDate, secondDate);
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.where(cb.like(pet.get(Pet_.name)), "*do");
Multiple conditional predicates can be specified by using the compound predicate methods of the CriteriaBuilder interface.
Table 22–3 Compound Predicate Methods in the CriteriaBuilder Interface
Method |
Description |
---|---|
and |
A logical conjunction of two boolean expressions. |
or |
A logical disjunction of two boolean expressions. |
not |
A logical negation of the given boolean expression. |
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Metamodel m = em.getMetamodel(); EntityType<Pet> Pet_ = m.entity(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.where(cb.equal(pet.get(Pet_.name), "Fido") .and(cb.equal(pet.get(Pet_.color), "brown");
For queries that return more than one result, it's often helpful to organize those results. The CriteriaQuery interface defines the orderBymethod to order query results according to attributes of an entity. The CriteriaQuery interface also defines the groupBy method to group the results of a query together according to attributes of an entity, and the and having method to restrict those groups according to a condition.
The order of the results of a query can be set by calling the CriteriaQuery.orderBy method and passing in an Order object. Order objects are created by calling either the CriteriaBuilder.asc or CriteriaBuilder.desc methods. The asc method is used to order the results by ascending value of the passed expression parameter. The desc method is used to order the results by descending value of the passed expression parameter.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.select(pet); cq.orderBy(cb.desc(pet.get(Pet_.birthday));
In this query, the results will be ordered by the pet's birthday from highest to lowest. That is, pets born in December will appear before pets born in May.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Root<Pet> pet = cq.from(Pet.class); Join<Owner, Address> address = cq.join(Pet_.owners).join(Owner_.address); cq.select(pet); cq.orderBy(cb.asc(address.get(Address_.postalCode));
In this query, the results will be ordered by the pet owner's postal code from lowest to highest. That is, pets whose owner lives in the 10001 zip code will appear before pets whose owner lives in the 91000 zip code.
If more than one Order object is passed to orderBy, the precedence is determined by the order in which they appear in the argument list of orderBy. The first Order object has precedence.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Root<Pet> pet = cq.from(Pet.class); Join<Pet, Owner> owner = cq.join(Pet_.owners); cq.select(pet); cq.orderBy(cb.asc(owner.get(Owner_.lastName), owner.get(Owner_.firstName));
The results of this query will be ordered alphabetically by the pet owner's last name, then first name.
The CriteriaQuery.groupBy method partitions the query results into groups. These groups are set by passing an expression to groupBy.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.groupBy(pet.get(Pet_.color));
This query returns all Pet entities, and groups the results by pet's color.
The CriteriaQuery.having method is used in conjunction with groupBy to filter over the groups. The having method takes a conditional expression as a parameter. By calling the having method, the query result is restricted according to the conditional expression.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); Root<Pet> pet = cq.from(Pet.class); cq.groupBy(pet.get(Pet_.color)); cq.having(cb.in(pet.get(Pet_.color)).value("brown").value("blonde");
In this example, the query groups the returned Pet entities by color, as in the example above. However, the only returned groups will be Pet entities where the color attribute is set to brown or blonde. That is, no grey colored pets will be returned in this query.
To prepare a query for execution, create a TypedQuery<T> object with the type of the query result by passing the CriteriaQuery object to EntityManager.createQuery.
Queries are executed by calling either getSingleResult or getResultList on the TypedQuery<T> object.
The TypedQuery<T>.getSingleResult method is used for executing queries that return a single result.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); ... TypedQuery<Pet> q = em.createQuery(cq); Pet result = q.getSingleResult();
The TypedQuery<T>.getResultList method is used for executing queries that return a collection of objects.
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class); ... TypedQuery<Pet> q = em.createQuery(cq); List<Pet> results = q.getResultList();