CS636 Class 15 

Setting  up the pizza2 JPA Project : see MoreOnJPA for intro, also JPA2Notes

Need to reload the databases for pizza2, because there’s a new table, so its README for directions on running the project.

Also new to database directory : 3 versions of persistence.xml, one for each DB. We need to copy the appropriate one of these to be on the classpath, i.e., put it under bin in the right place, bin/META-INF/persistence.xml. Later, JPA accesses it from there, using (say, FYI) getResourceAsStream("/META_INF/persistence.xml") on a class object.

How to get the right persistence.xml in place:

ant config-oradb

  --after this, see Oracle info in bin/META-INF/persistence.xml, read by JPA at initialization

ant config-hsqldb

  --after this, see HSQLDB info in bin/META-INF/persistence.xml

and similarly for mysql.

However, you don't really need to use these targets explicitly
ant hsqlSysTest has config-hsqldb as a dependency, so it is run for us:

<target name="hsqlSysTest" depends="init, build, config-hsqldb">
   <echo message="running SystemTest on HSQLDB"/>
   <echo message="...look for JPA-generated SQL in JpaGeneratedSql.log"/>
   <java fork="true" classname="cs636.pizza.presentation.SystemTest" failonerror="true">
      <classpath refid="project.classpath" />
      <jvmarg value="${JAVAAGENT}"/>
      <arg file="test.dat" />   
   </java>
</target>

After ant-config-hsqldb, can use runTakeOrder.cmd/runShopAdmin.cmd, since they use HSQLDB.

Refreshing the project directs eclipse to reread all the files from the file system.  This is needed after “ant config-oradb” to see the right persistence.xml in eclipse Project Explorer, and have the right one in use in executions. Eclipse has its own cached data that it works from normally, and this includes files in bin. Eclipse itself does not support multiple JPA configurations.

So switching from HSQLDB to Oracle when using eclipse to run things :

JPA

Entity Manager – manages the runtime environment (named “persistence context”), set up by JPA

It’s an object that has information on your POJOs and also has refs to your POJOs being managed

A specific POJO (with @Entity annotation) becomes a “managed object” when

Before we call em.persist(obj), the obj is “new” object, not managed

em.close() shuts down object management, so then the objects are “detached”

The em.close() happens just after the transaction commit, right at the end of the service call.

Thus all objects received by presentation code from the service layer are detached.  The transaction that pulled their data from the database has finished already.

If we pass detached objects back to the service layer, in general we have to be careful: they might have obsolete data. In this class, we only allow immutable domain objects to go to the presentation layer, like Product in music. So any domain object coming back from presentation to service should have good data.

Basic steps of working with JPA:

At config time, get an emf, an EntityManagerFactory.

At this time the persistence.xml is read, so the username, password, and database URL are known to the system

Example persistence.xml on pg. 427.

We have 3 of these, one for each database. The ant task config-xxx copies one of these to src and bin, in META-INF dir, to make them active in the project.

Using the emf, create an EntityManager em.  In pizza2, this is done at transaction start. Then the em lives through the transaction and gets closed just after the commit or rollback.

Then the em is used to do database operations

em.persist(o)

o = em.find(class, id)

em.createQuery(“select …”)

em.getTransaction().begin()

em.getTransaction().commit()

no update method for em: just commit persistent object, em figures out what to do

em.close()  makes all managed objects “detached”

Entity Life Cycle diagram (simplified from slide 8 of More onJPA slides)

Note this is just what we’ve covered so far, but it’s enough to do real projects.

Just after we create o in Java, the object is not "managed". It is "new".

                                               <new> 
                                                 |
                                                 | em.persist()
                                                 |
                                                 v
 <detached> <---em.commit/rollback/close()--- <managed> <---access DB with em.find or queries---

Important idea of a “managed object”

Detached objects work fine as POJOs. They hold data that was previously read from the database, so it may be a little stale.

More discussion on detached objects in Presentation

Since the em is closed at commit/rollback time, any domain objects returned to the presentation code are all detached.  This is true for all service methods. Thus any domain object you see in presentation code is detached, and that’s one reason we don’t allow changes in (persistent) domain objects in the presentation layer: they can’t be properly tracked.  It’s not a good idea for other reasons too, like securing information. Note that Cart is a domain object but it's not persistent, so it doesn't count here. 

In fact, we are only allowing a few domain objects to ever travel into the presentation layer. In pizza, we have PizzaOrderData objects used to transport PizzaOrder information into presentation, with only strings for size and toppings, not domain objects. PizzaOrderData is a transfer object, not a domain object--it is immutable, just a snapshot of the PizzaOrder data.

In music, we have InvoiceData, UserData, CartItemData and DownloadData objects for the same purpose. We do allow Product and Track objects to travel into the presentation layer, but the excuse is that they are fully invariant. They never change (in this app), and none are added to the system during operation.

Generated IDs

Here we see how the new table pizza_id_gen works to generate a new order number. It also does this for new pizza_topping id, Pizza_size id.
@Entity
@Table(name="PIZZA_TOPPINGS")
   
public class PizzaTopping implements Serializable, Comparable<PizzaTopping> {
    private static final long serialVersionUID = 1L;

    @Id
    @TableGenerator(name="ToppingIdGen",
            table = "PIZZA_ID_GEN",
            pkColumnName = "GEN_NAME",
            valueColumnName = "GEN_VAL",
            pkColumnValue = "ToppingId_Gen")
               
    @GeneratedValue(generator="ToppingIdGen")
    @Column(unique=true, nullable=false)
    private int id;


     Near end of createdb.sql:

-- for generated ids specific to the pizza project
-- pizza_id_gen has one row for each table that needs ids, i.e. each entity table
-- gen_val start at 0, so first generated id for each entity is 1
CREATE TABLE PIZZA_ID_GEN (GEN_NAME VARCHAR(50) NOT NULL, GEN_VAL INTEGER, PRIMARY KEY (GEN_NAME));
INSERT INTO PIZZA_ID_GEN (GEN_NAME, GEN_VAL) values ('Ordno_Gen', 0);
INSERT INTO PIZZA_ID_GEN (GEN_NAME, GEN_VAL) values ('SizeId_Gen', 0);
INSERT INTO PIZZA_ID_GEN (GEN_NAME, GEN_VAL) values ('ToppingId_Gen', 0);
INSERT INTO PIZZA_ID_GEN (GEN_NAME, GEN_VAL) values ('MenuSizeId_Gen', 0);
INSERT INTO PIZZA_ID_GEN (GEN_NAME, GEN_VAL) values ('MenuToppingId_Gen', 0);


Part of sysTest run: at presentation level, one "so" command, at service level, have 3 calls here: getToppingNames(), getPizzaSizeNames(), and makeOrder. At DAO level, 3 startTransaction, commitTransaction pairs, and within find MenuToppings(), findMenuSizes(), and in makeOrder, findMenuSize(), findMenuTopping() findCurrentDay, and insertOrder().

*************so 5***************

[EL Fine]: sql: Connection(1894338251)--SELECT ID, TOPPING_NAME FROM MENU_TOPPINGS
[EL Fine]: sql: Connection(1894338251)--SELECT ID, SIZE_NAME FROM MENU_SIZES
in findPizzaSize
[EL Fine]: sql: Connection(1894338251)--SELECT ID, SIZE_NAME FROM MENU_SIZES WHERE (SIZE_NAME = ?)
    bind => [small]
cking topping Pepperoni
[EL Fine]: sql: Connection(1894338251)--SELECT ID, TOPPING_NAME FROM MENU_TOPPINGS WHERE (TOPPING_NAME = ?)
    bind => [Pepperoni]
[EL Fine]: sql: Connection(1894338251)--select current_day from pizza_sys_tab
[EL Fine]: sql: Connection(1894338251)--UPDATE PIZZA_ID_GEN SET GEN_VAL = GEN_VAL + ? WHERE GEN_NAME = ?
    bind => [50, Ordno_Gen]
[EL Fine]: sql: Connection(1894338251)--SELECT GEN_VAL FROM PIZZA_ID_GEN WHERE GEN_NAME = ?
    bind => [Ordno_Gen]
[EL Fine]: sql: Connection(1894338251)--UPDATE PIZZA_ID_GEN SET GEN_VAL = GEN_VAL + ? WHERE GEN_NAME = ?
    bind => [50, ToppingId_Gen]
[EL Fine]: sql: Connection(1894338251)--SELECT GEN_VAL FROM PIZZA_ID_GEN WHERE GEN_NAME = ?
    bind => [ToppingId_Gen]
[EL Fine]: sql: Connection(1894338251)--UPDATE PIZZA_ID_GEN SET GEN_VAL = GEN_VAL + ? WHERE GEN_NAME = ?
    bind => [50, SizeId_Gen]
[EL Fine]: sql: Connection(1894338251)--SELECT GEN_VAL FROM PIZZA_ID_GEN WHERE GEN_NAME = ?
    bind => [SizeId_Gen]
[EL Fine]: sql: Connection(1894338251)--INSERT INTO PIZZA_SIZES (ID, SIZE_NAME) VALUES (?, ?)
    bind => [1, small]
[EL Fine]: sql: Connection(1894338251)--INSERT INTO PIZZA_ORDERS (ID, DAY, room_number, STATUS, SIZE_ID) VALUES (?, ?, ?, ?, ?)
    bind => [1, 1, 5, 1, 1]
[EL Fine]: sql: Connection(1894338251)--INSERT INTO PIZZA_TOPPINGS (ID, TOPPING_NAME, ORDER_ID) VALUES (?, ?, ?)
    bind => [1, Pepperoni, 1]
----OK

Look at pizza2 files:

Same idea of top-level build.xml, database dir with own build.xml, src, lib, bin directories

Same old file names in database: createdb.sql, dropdb.sql, showdb.sql, but edits to createdb.sql.

Find persistence.xml versions under database: see db urls, username, pw's.

But note plug-in strings, for ex:

 <property name="javax.persistence.jdbc.url" value="@oracle_url@" />
   <property name="javax.persistence.jdbc.user" value="@username@" />

These are filled in by the code in ant config-oradb using <filter>, a handy trick. So when the persistence.xml reaches its destination, it has values determined by your environment variables.

New top-level config commands: ant config-oradb, config-mysqldb, config-hsqldb:  these put the persistance.xml for the current db in the right place, that is, under META-INF/persistence.xml in the .class file tree.

Running with Oracle, with tunnel working to dbs3.cs.umb.edu as before

      
      cd database
      ant drop-oradb
      ant load-oradb
      cd ..
      ant oraSysTest
      

--after this, see your Oracle login info in bin/META-INF/persistence.xml, read by JPA at initialization

To run with mysql:

     
      cd database
      ant drop-mysqldb
      ant load-mysqldb
      cd ..
      ant config-mysqldb
      ant sysTest
 

--after this, see mysql info in bin/META-INF/persistence.xml, read by JPA at initialization

Eclipse: For simple execution and source mods, can treat it as a Java project: all the needed libraries are in lib.

Refreshing the project directs eclipse to reread all the files from the file system.  This is needed after “ant config-oradb” to use the right persistence.xml in eclipse runs. Eclipse has its own cached data that it works from normally, and this includes files in bin.

So switching from HSQLDB to Oracle when using eclipse to run things :

Eclipse and pizza2:

For JPA tools accessible via the eclipse GUI, need to set it up as a JPA project--this is trickier.

JPA tools:

I should demo these at some point.


Pizza2 Service Methods

makeOrder:  receives Strings and numbers as arguments, should check them out, since a topping may no longer be available, etc. (We omitted this in pizza1).

Scenario causing bad Topping object in makeOrder call:

In makeOrder, the incoming PizzaSize and Toppings are specified by strings, but the corresponding MenuSize and MenuToppings may not exist.

To detect this defective order case, the pizza2 code looks up Pepperoni and finds out it isn’t really there, creates an Exception with a user message inside.

Note that this is a case of a ServiceException created by app logic, not a database problem.

If the topping were there, it would use it to create a new PizzaTopping.  Similarly with the PizzaSize object. Then a new PizzaOrder object. So the object graph sent to the DAO consists of only new objects. The DAO has to persist all these objects.

Alternatively, we could set up CASCADE for the relationships in PizzaOrder.java, like Murach has done for Invoice.java in the slides and on pg. 433. Then we could persist just the invoice object and the others would be persisted automatically.

Object Updates driving database updates

As an example, look at receiveOrders, which changes the status of one or more PizzaOrders. Although this code updates the pizza order, it doesn’t call a DAO method to do it.  The change to the PizzaOrder object is tracked by JPA’s persistence context, and the needed row update is made by commit time. 

Another note later in this code: eclipselink will read from presentation layer, outside the transaction. Maybe more configuration could prevent this lack of serializability.

Notes:

Question: what happens if you persist employee 158 and that PK is already in the DB?

Answer: An exception is thrown either immediately or later at commit time (or whenever the db is synch’d with the persistence context).  Commits can fail in any database programming environment. A successful transaction is one whose commit returns successfully.

Next: servlets, JSP hosted by tomcat.  You will install your own tomcat server on topcat first—in coming homework. Then you will be able to check for your assigned ports listed in a document linked to the class web page, as well as tomcat-8.5.zip and instructions.