Overview of the order Application
orderis 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?
orderis a simplified version of an ordering system that has all these elements.
orderconsists 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
orderdemonstrates 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.
Parthas a fieldbomPartthat 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
Partis a compound primary key, a combination of thepartNumberandrevisionfields. It is mapped to thePARTNUMBERandREVISIONcolumns in theEJB_ORDER_PARTtable.... @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
Parthas a field,vendorPart, that has a one-to-one relationship withVendorPart'spartfield. 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
Partuses a compound primary key, the@JoinColumnsannotation is used to map the columns in theEJB_ORDER_VENDOR_PARTtable to the columns inEJB_ORDER_PART.EJB_ORDER_VENDOR_PART'sPARTREVISIONcolumn refers toEJB_ORDER_PART'sREVISIONcolumn.One-to-Many Relationship Mapped to Overlapping Primary and Foreign Keys
Orderhas a field,lineItems, that has a one-to-many relationship withLineItem's fieldorder. That is, each order has one or more line item.
LineItemuses a compound primary key that is made up of theorderIdanditemIdfields. This compound primary key maps to theORDERIDandITEMIDcolumns in theEJB_ORDER_LINEITEMdatabase table.ORDERIDis a foreign key to theORDERIDcolumn in theEJB_ORDER_ORDERtable. This means that theORDERIDcolumn 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
LineItemhas 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
orderuses several types of primary keys: single-valued primary keys, compound primary keys, and generated primary keys.Generated Primary Keys
VendorPartuses 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@GeneratedValueannotation 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
@TableGeneratorannotation is used in conjunction with@GeneratedValue'sstrategy=TABLEelement. That is, the strategy used to generate the primary keys is use a table in the database.@TableGeneratoris used to configure the settings for the generator table. The name element sets the name of the generator, which isvendorPartGeninVendorPart. The EJB_ORDER_SEQUENCE_GENERATOR table, which has two columnsGEN_KEYandGEN_VALUE, will store the generated primary key values. This table could be used to generate other entity's primary keys, so thepkColumnValueelement is set toVENDOR_PART_IDto distinguish this entity's generated primary keys from other entity's generated primary keys. TheallocationSizeelement 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
vendorPartNumberis 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 779). To use a compound primary key, you must create a wrapper class.
In
order, two entities use compound primary keys:PartandLineItem.
Partuses thePartKeywrapper class.Part's primary key is a combination of the part number and the revision number.PartKeyencapsulates this primary key.
LineItemuses theLineItemKeyclass.LineItem's primary key is a combination of the order number and the item number.LineItemKeyencapsulates this primary key. This is theLineItemKeycompound 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
@IdClassannotation is used to specify the primary key class in the entity class. InLineItem,@IdClassis used as follows:The two fields in
LineItemare tagged with the@Idannotation 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@Columnannotation 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_ORDERtable'sORDERIDcolumn (see One-to-Many Relationship Mapped to Overlapping Primary and Foreign Keys). That is,orderIdwill be set by theOrderentity.In
LineItem's constructor, the line item number (LineItem.itemId) is set using theOrder.getNextIdmethod.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.getNextIdcounts the number of current line items, adds one, and returns that number.
Partdoesn't require the@Columnannotation 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_PARTandEJB_ORDER_PART_DETAIL. TheEJB_ORDER_PART_DETAILtable holds the specification and schematics for the part. The@SecondaryTableis 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_DETAILshares the same primary key values asEJB_ORDER_PART. ThepkJoinColumnselement of@SecondaryTableis used to specify thatEJB_ORDER_PART_DETAIL's primary key columns are foreign keys toEJB_ORDER_PART. The@PrimaryKeyJoinColumnsets 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_DETAILandEJB_ORDER_PARTare the same:PARTNUMBERandREVISION, 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 theOrderto which aLineItemis related is deleted, then theLineItemshould also be deleted. If theVendorto which aVendorPartis related is deleted, then theVendorPartshould also be deleted.You specify the cascade operations for entity relationships by setting the
cascadeelement in the inverse (non-owning) side of the relationship. The cascade element is set toALLin 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
PARTDETAILtable in the database has a column,DRAWING, of typeBLOB.BLOBstands for binary large objects, which are used for storing binary data such as an image. TheDRAWINGcolumn is mapped to the fieldPart.drawingof typejava.io.Serializable. The@Lobannotation is used to denote that the field is large object.
PARTDETAILalso has a column,SPECIFICATION, of typeCLOB.CLOBstands for character large objects, which are used to store string data too large to be stored in aVARCHARcolumn.SPECIFICATIONis mapped to the fieldPart.specificationof 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
@Columnannotation and set thetableelement to the secondary table.Temporal Types in order
The
Order.lastUpdatepersistent property, which is of typejava.util.Date, is mapped to theEJB_ORDER_ORDER.LASTUPDATEdatabase field, which is of the SQL typeTIMESTAMP. To ensure the proper mapping between these types, you must use the@Temporalannotation 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
RequestBeanstateful session bean contains the business logic and manages the entities oforder.
RequestBeanuses the@PersistenceContextannotation to retrieve an entity manager instance which is used to manageorder's entities inRequestBean's business methods.This
EntityManagerinstance 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.createPartbusiness method creates a newPartentity. TheEntityManager.persistmethod 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.getOrderPricebusiness method returns the price of a given order, based on theorderId. TheEntityManager.findmethod is used to retrieve the entity from the database.The first argument of
EntityManager.findis the entity class, and the second is the primary key.Setting Entity Relationships
The
RequestBean.createVendorPartbusiness method creates aVendorPartassociated with a particularVendor. TheEntityManager.persistmethod 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.adjustOrderDiscountbusiness method updates the discount applied to all orders. It uses thefindAllOrdersnamed query, defined inOrder:The
EntityManager.createNamedQuerymethod is used to run the query. Because the query returns aListof all the orders, theQuery.getResultListmethod is used.The
RequestBean.getTotalPricePerVendorbusiness method returns the total price of all the parts for a particular vendor. It uses a named parameter,id, defined in the named queryfindTotalVendorPartPricePerVendordefined inVendorPart.@NamedQuery( name="findTotalVendorPartPricePerVendor", query="SELECT SUM(vp.price) " + "FROM VendorPart vp " + "WHERE vp.vendor.vendorId = :id" )When running the query, the
Query.setParametermethod is used to set the named parameteridto the value ofvendorId, the parameter toRequestBean.getTotalPricePerVendor.return (Double) em.createNamedQuery( "findTotalVendorPartPricePerVendor") .setParameter("id", vendorId) .getSingleResult();The
Query.getSingleResultmethod is used for this query because the query returns a single value.Removing Entities
The
RequestBean.removeOrderbusiness method deletes a given order from the database. It uses theEntityManager.removemethod to delete the entity from the database.