Overview of the order Application
order
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 command-line client adds data to the entities, 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.
order
consists of two modules:order-ejb
, an enterprise bean JAR file containing the entities, the support classes, and a stateful session bean that accesses the data in the entitiess; andorder-app-client
, the application client that populates the entities with data and manipulates the data, displaying the results in a terminal.Entity Relationships in order
order
demonstrates several types of entity relationships: one-to-many, many-to-one, one-to-one, unidirectional, and self-referential relationships.Self-Referential Relationships
A self-referential relationship is a relationship between relationship fields in the same entity.
Part
has a fieldbomPart
that has a one-to-many relationship with the fieldparts
, which is also inPart
. 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 thepartNumber
andrevision
fields. It is mapped to thePARTNUMBER
andREVISION
columns in theEJB_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; } ...One-to-One Relationships
Part
has a field,vendorPart
, that has a one-to-one relationship withVendorPart
'spart
field. That is, each part has exactly one vendor part, and vice versa.Here is the relationship mapping in
Part
: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 theEJB_ORDER_VENDOR_PART
table to the columns inEJB_ORDER_PART
.EJB_ORDER_VENDOR_PART
'sPARTREVISION
column refers toEJB_ORDER_PART
'sREVISION
column.One-to-Many Relationship Mapped to Overlapping Primary and Foreign Keys
Order
has a field,lineItems
, that has a one-to-many relationship withLineItem
's fieldorder
. That is, each order has one or more line item.
LineItem
uses a compound primary key that is made up of theorderId
anditemId
fields. This compound primary key maps to theORDERID
andITEMID
columns in theEJB_ORDER_LINEITEM
database table.ORDERID
is a foreign key to theORDERID
column in theEJB_ORDER_ORDER
table. This means that theORDERID
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
:Unidirectional Relationships
LineItem
has a field,vendorPart
, that has a unidirectional many-to-one relationship withVendorPart
. That is, there is no field in the target entity in this relationship.Primary Keys in order
order
uses several types of primary keys: single-valued primary keys, compound primary keys, and generated primary keys.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="EJB_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
'sstrategy=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 isvendorPartGen
inVendorPart
. The EJB_ORDER_SEQUENCE_GENERATOR table, which has two columnsGEN_KEY
andGEN_VALUE
, will store the generated primary key values. This table could be used to generate other entity's primary keys, so thepkColumnValue
element is set toVENDOR_PART_ID
to distinguish this entity's generated primary keys from other entity's generated primary keys. TheallocationSize
element specifies the amount to increment when allocating primary key values In this case, eachVendorPart
's primary key will increment by 10.The primary key field
vendorPartNumber
is of typeLong
, as the generated primary key's field must be an integral type.Compound Primary Keys
A compound primary key is made up of multiple fields and follows the requirements described in Primary Key Classes (page 775). To use a compound primary key, you must create a wrapper class.
In
order
, two entities use compound primary keys:Part
andLineItem
.
Part
uses thePartKey
wrapper class.Part
's primary key is a combination of the part number and the revision number.PartKey
encapsulates this primary key.
LineItem
uses theLineItemKey
class.LineItem
's primary key is a combination of the order number and the item number.LineItemKey
encapsulates this primary key. This is theLineItemKey
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. InLineItem
,@IdClass
is used as follows: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
, we 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 theEJB_ORDER_ORDER
table'sORDERID
column (see One-to-Many Relationship Mapped to Overlapping Primary and Foreign Keys). That is,orderId
will be set by theOrder
entity.In
LineItem
's constructor, the line item number (LineItem.itemId
) is set using theOrder.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.
Part
doesn't require the@Column
annotation on the two fields that comprisePart
's compound primary key. This is becausePart
'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; } ... }Entity Mapped to More Than One Database Table
Part
's fields map to more than one database table:EJB_ORDER_PART
andEJB_ORDER_PART_DETAIL
. TheEJB_ORDER_PART_DETAIL
table holds the specification and schematics for the part. The@SecondaryTable
is used to specify the secondary table.... @Entity @Table(name="EJB_ORDER_PART") @SecondaryTable(name="EJB_ORDER_PART_DETAIL", pkJoinColumns={ @PrimaryKeyJoinColumn(name="PARTNUMBER", referencedColumnName="PARTNUMBER"), @PrimaryKeyJoinColumn(name="REVISION", referencedColumnName="REVISION") }) public class Part { ... }
EJB_ORDER_PART_DETAIL
shares the same primary key values asEJB_ORDER_PART
. ThepkJoinColumns
element of@SecondaryTable
is used to specify thatEJB_ORDER_PART_DETAIL
's primary key columns are foreign keys toEJB_ORDER_PART
. The@PrimaryKeyJoinColumn
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 bothEJB_ORDER_PART_DETAIL
andEJB_ORDER_PART
are the same:PARTNUMBER
andREVISION
, respectively.Cascade Operations in order
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 theOrder
to which aLineItem
is related is deleted, then theLineItem
should also be deleted. If theVendor
to which aVendorPart
is related is deleted, then theVendorPart
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 toALL
in the case ofOrder.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
:BLOB and CLOB Database Types in order
The
PARTDETAIL
table in the database has a column,DRAWING
, of typeBLOB
.BLOB
stands for binary large objects, which are used for storing binary data such as an image. TheDRAWING
column is mapped to the fieldPart
.drawing
of typejava.io.Serializable
. The@Lob
annotation is used to denote that the field is large object.
PARTDETAIL
also has a column,SPECIFICATION
, of typeCLOB
.CLOB
stands for character large objects, which are used to store string data too large to be stored in aVARCHAR
column.SPECIFICATION
is mapped to the fieldPart.specification
of typejava.lang.String
. The @Lob annotation is also used here to denote that the field is a large object.@Column(table="EJB_ORDER_PART_DETAIL") @Lob public String getSpecification() { return specification; }Both of these fields use the
@Column
annotation and set thetable
element to the secondary table.Temporal Types in order
The
Order.lastUpdate
persistent property, which is of typejava.util.Date
, is mapped to theEJB_ORDER_ORDER.LASTUPDATE
database field, which is of the SQL typeTIMESTAMP
. 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 typejavax.persistence.TemporalType
. The possible values are:Here is the relevant section of Order:
Managing order's Entities
The
RequestBean
stateful session bean contains the business logic and manages the entities oforder
.
RequestBean
uses the@PersistenceContext
annotation to retrieve an entity manager instance which is used to manageorder
's entities inRequestBean
's business methods.This
EntityManager
instance is a container-managed entity manager, so the container takes care of all the transactions involved in the managingorder
's entities.Creating Entities
The
RequestBean.createPart
business method creates a newPart
entity. TheEntityManager.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);Finding Entities
The
RequestBean.getOrderPrice
business method returns the price of a given order, based on theorderId
. TheEntityManager.find
method is used to retrieve the entity from the database.The first argument of
EntityManager.find
is the entity class, and the second is the primary key.Setting Entity Relationships
The
RequestBean.createVendorPart
business method creates aVendorPart
associated with a particularVendor
. TheEntityManager.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);Using Queries
The
RequestBean.adjustOrderDiscount
business method updates the discount applied to all orders. It uses thefindAllOrders
named query, defined inOrder
:The
EntityManager.createNamedQuery
method is used to run the query. Because the query returns aList
of all the orders, theQuery.getResultList
method is used.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 queryfindTotalVendorPartPricePerVendor
defined inVendorPart
.@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 parameterid
to the value ofvendorId
, the parameter toRequestBean.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.Removing Entities
The
RequestBean.removeOrder
business method deletes a given order from the database. It uses theEntityManager.remove
method to delete the entity from the database.