apache > db
Apache DB Project
 
Font size:      

Using JPOX JDO with Derby

Overview

This paper gives a concrete example of using JPOX JDO to persist and retrieve data to and from Apache Derby. Java Data Objects (JDO) is a specification for persisting Java objects. JPOX JDO 1.1 is the reference implementation for the JDO 2.0 specification. The JDO 2.0 API is provided by the Apache JDO project and is required to run JPOX JDO 1.1. The JDO 2.0 API conforms to the JDO 2.0 specification.

Apache Derby, a subproject of the Apache DB project, is a standards compliant database, written and implemented entirely in Java.

The steps below show how to use the bottom-up approach to designing an application which persists Derby data using JPOX Java Data Objects. The bottom-up approach means the database schema has already been defined. We will need to provide an object to relational mapping file and the Java classes (objects) which will be made persistence capable. (The alternate approach, called top-down, is to define the Java classes and then generate the database schema based on the mapping file between database and object classes.)

The section, Download sample code and configuration files, contains a zip file which includes a populated Derby database and SQL to create the database schema (if desired), Java source files, the JPOX JDO meta-data file and the application classes to issue the JPOX queries. This is not a detailed explanation of either JPOX or Derby, but should be used as a "cookbook" on how to use the two together.

Step 1. Prerequisites

The following software needs to be downloaded and installed prior to using the source files provided in the download zip file, jpox_derby.zip.

Software Minimum Release Level Required Release Used in this Example Download Location
Java JDK or SDK 1.4.2 1.4.2 Sun or IBM
Apache Derby 10.1 10.1.2 Apache Derby
Apache JDO 2.0-snapshot7.jar 2.0-rc1 Apache JDO, 2.0 RC1
JPOX JDO "Core" 1.1.0-beta 5 1.1.0-rc-1 JPOX "core" jpox.org
JPOX JDO Enhancer 1.1.0-beta 5 1.1.0-rc-1 JPOX Enhancer jpox.org
Apache Jakarta Byte Code Engineering Library (BCEL) 5.1 5.1 BCEL(Used by JPOX during the enhancement of the Java classes.)
Apache Log4j 1.2.8 1.2.12 Apache Log4j

Step 2. Download sample code and configuration files

This paper will list each step required to get the sample application up and running, including partial code listings. However, to get the most out of this paper download the file, jpox_derby.zip to follow along with each step or to view the entire source and configuration files.

After downloading the jpox_derby.zip file unzip it to a convenient directory. The examples that follow assume it was unzipped to the C:/ directory on a windows machine.

Step 3. Examine/Create the Database schema

As mentioned above, using the bottom-up approach means we need to define our database schema first. The schema shown below consists of five tables for a simple airlines reservation system. The data directory contained in the zip file contains the airlinesDB already populated with these tables and data. However, if you would like to run the SQL at a later time to create the database, the airlinesDB.sql file contained in the zip file can be used to do so.

SQL for Derby database tables

CREATE TABLE APP.CITIES ( CITY_ID INTEGER NOT NULL CONSTRAINT cities_pk PRIMARY KEY, CITY_NAME VARCHAR(24) NOT NULL, COUNTRY VARCHAR(26) NOT NULL, AIRPORT CHAR(3), LANGUAGE VARCHAR(16), COUNTRY_ISO_CODE CHAR(2) ); CREATE TABLE APP.FLIGHTS ( FLIGHT_ID CHAR(6) NOT NULL, SEGMENT_NUMBER INTEGER NOT NULL, ORIG_AIRPORT CHAR(3), DEPART_TIME TIME, DEST_AIRPORT CHAR(3), ARRIVE_TIME TIME, MEAL CHAR(1) CONSTRAINT MEAL_CONSTRAINT CHECK (meal IN ('B', 'L', 'D', 'S')), FLYING_TIME DOUBLE PRECISION, MILES INTEGER, AIRCRAFT VARCHAR(6), DEPART_DATE DATE, CONSTRAINT FLIGHTS_PK Primary Key (FLIGHT_ID) ); CREATE INDEX DESTINDEX ON APP.FLIGHTS (DEST_AIRPORT) ; CREATE INDEX ORIGINDEX ON APP.FLIGHTS (ORIG_AIRPORT) ; CREATE TABLE APP.USERS ( FIRSTNAME VARCHAR(40) NOT NULL, LASTNAME VARCHAR (40) NOT NULL, USERNAME VARCHAR(20) NOT NULL CONSTRAINT username_pk PRIMARY KEY, PASSWORD VARCHAR(20) NOT NULL, PASSWORD_VERIFY VARCHAR(20), EMAIL VARCHAR(30) NOT NULL ); CREATE TABLE APP.USER_CREDIT_CARD ( ID INT NOT NULL GENERATED ALWAYS AS IDENTITY CONSTRAINT user_cc_pk PRIMARY KEY, USERNAME varchar(20) NOT NULL, LASTNAME varchar(40), CREDIT_CARD_TYPE varchar(15) NOT NULL, CREDIT_CARD_NUMBER varchar(20) NOT NULL, CREDIT_CARD_DISPLAY varchar(25) NOT NULL ); ALTER TABLE APP.USER_CREDIT_CARD ADD CONSTRAINT USERNAME_FK Foreign Key (username) REFERENCES APP.USERS (username); CREATE TABLE APP.FLIGHTHISTORY ( ID INT NOT NULL GENERATED ALWAYS AS IDENTITY CONSTRAINT user_fh_pk PRIMARY KEY, USERNAME VARCHAR(20) NOT NULL, FLIGHT_ID CHAR(6) NOT NULL, ORIG_AIRPORT CHAR(3) NOT NULL, DEST_AIRPORT CHAR(3) NOT NULL, MILES INTEGER, AIRCRAFT VARCHAR(6), DEPARTURE_DATE VARCHAR(25), CREDIT_CARD_TYPE varchar(15) NOT NULL, CREDIT_CARD_DISPLAY varchar(25) NOT NULL ); ALTER TABLE APP.FLIGHTHISTORY ADD CONSTRAINT USERNAME_FH_FK Foreign Key (username) REFERENCES APP.USERS (username);

Step 4. Write the classes used to persist the data

Once you have created the database schema you will need to code one class for each corresponding database table you want to persist using JPOX. The Java class needs to consist of setters and getters with a default constructor to match the database table.

Here's the class that corresponds to the APP.CITIES table above. The only constraint on the Java class to be persisted is it must have a default constructor, which can be private if you want it to be. The source code for all five java classes that correspond to the database schema shown above is in jpox_derby.zip.

Partial listing of CityBean.Java

public class CityBean { private int cityId; private String cityName; private String country; private String airport; private String language; private String countryCode; public CityBean() { cityId = 0; cityName = ""; country = ""; airport = ""; language = ""; countryCode = ""; } public CityBean(int city_id, String city, String ctry, String air) { cityId = city_id; cityName = city; country = ctry; airport = air; } public CityBean(int cityId, String cityName, String country, String airport, String language, String countryCode) { this.cityId = cityId; this.cityName = cityName; this.country = country; this.airport = airport; this.language = language; this.countryCode = countryCode; } public String getAirport() { return airport; } public void setAirport(String airport) { this.airport = airport; } // create getters and setters for all other member variables ... }

Step 5. Create the JDO meta-data mapping file

By convention this file is usually referred to as package.jdo. Take a look at the partial listing of the package.jdo file I am using to map the Derby database tables to the Java classes. (The full package.jdo is available in the download.)

Partial listing of package.jdo

<?xml version="1.0"?> <!DOCTYPE jdo PUBLIC "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 2.0//EN" "http://java.sun.com/dtd/jdo_2_0.dtd"> <jdo> <package name="org.apache.derby.demo.beans.view"> <class name="CityBean" identity-type="application" table="CITIES"> <field name="cityId" primary-key="true"> <column name="CITY_ID" jdbc-type="INTEGER"/> </field> <field name="cityName"> <column name="CITY_NAME" length="24" jdbc-type="VARCHAR"/> </field> <field name="country"> <column name="COUNTRY" length="26" jdbc-type="VARCHAR"></column> </field> <field name="airport"> <column name="AIRPORT" length="3" jdbc-type="CHAR" allows-null=""> </column> </field> <field name="language"> <column name="LANGUAGE" length="16" jdbc-type="VARCHAR" allows-null=""> </column> </field> <field name="countryCode"> <column name="COUNTRY_ISO_CODE" length="2" jdbc-type="CHAR" allows-null=""> </column> </field> </class> <class name="FlightsBean" identity-type="application" table="FLIGHTS"> <field name="flightId" primary-key="true"> ... <class name="FlightHistoryBean" identity-type="application" table="FLIGHTHISTORY" detachable="true"> <field name="id" primary-key="true" value-strategy="autoassign"> <column name="id" jdbc-type="INTEGER"/> </field> ... </class> </package> <package name="org.apache.derby.demo.beans.model"> <class name="UserBean" identity-type="application" table="USERS" detachable="true"> ... <class name="UserCreditCardBean" identity-type="application" table="USER_CREDIT_CARD"> ... </class> </package> </jdo>

The package name refers to the package of the Java class, the class to the Java Class and the table attribute in the class element is the name of the database table. Some of the interesting things to note are the use of primary keys and the autoassign attribute for the FlightHistoryBean (FlightHistory table). Here is the section showing the FlightHistoryBean to FlightHistory table mapping:

<class name="FlightHistoryBean" identity-type="application" table="FLIGHTHISTORY" detachable="true"> <field name="id" primary-key="true" value-strategy="autoassign"> <column name="id" jdbc-type="INTEGER"/> </field>

Referring back to the first part of the create table statement of the FlightHistory table above:

CREATE TABLE APP.FLIGHTHISTORY ( id int not null generated always as identity constraint user_fh_pk primary key, USERNAME VARCHAR(20) NOT NULL, ...

notice how the id column is the primary key which is generated as an identity column. To map this to the persistence class the value-strategy must be autoassign.

This meta-data file, package.jdo, needs to be at a level in the source tree that includes all classes it references. For instance, the package.jdo below references classes in the org.apache.derby.demo.beans.view and org.apache.derby.demo.beans.model packages, so the package.jdo file would need to be at the level of org.apache.derby.demo.beans to ensure that it can have access to the view and model packages below the beans package.

For this reason I just put my package.jdo at the top level of all of the packages I'm using.

Step 6. Create a log4j.properties file to be used by JPOX

JPOX uses Apache Log4j to report output and errors. Therefore a log4j.properties file is required. A sample one is shown below which includes JPOX specific categories. The file to be used for the JPOX output is called jpox.log. Check this log for messages if you have problems during enhancement or runtime.

log4j.properties file required by JPOX

# Define the destination and format of our logging log4j.appender.A1=org.apache.log4j.FileAppender log4j.appender.A1.File=jpox.log log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} (%t) %-5p [%c] - %m%n # JPOX Categories log4j.category.JPOX.JDO=INFO, A1 log4j.category.JPOX.Cache=INFO, A1 log4j.category.JPOX.MetaData=INFO, A1 log4j.category.JPOX.General=INFO, A1 log4j.category.JPOX.Utility=INFO, A1 log4j.category.JPOX.Transaction=INFO, A1 log4j.category.JPOX.RDBMS=DEBUG, A1 log4j.category.JPOX.Enhancer=INFO, A1 log4j.category.JPOX.SchemaTool=INFO, A1

Step 7. Enhancing the JPOX classes

After all of the Java classes representating the objects corresponding to the database tables have been written, they first need to be compiled and then they need to be "enhanced", which makes them persistence-capabable. JPOX provides a helper class to do this for you which is contained in the jar file jpox-enhancer-*.jar. and is called org.jpox.enhancer.JPOXEnhancer.

We'll defer the compilation and enhancement step until we are ready to configure our environment after we've coded the business logic class and application class.

Step 8. Code the class which makes the JPOX JDO queries

Here is the class that contains the business logic specific to our trivialized airlines reservation application. Also shown is a properties file that is read upon startup to set some JPOX and JDO properties. Although these properties are required to configure JPOX appropriately to work with Derby, they can be set from within the application, versus using a properties file as I've done here. The complete source for this class is available in the download.

Partial listing of JPoxJDO.java containing the JPOX queries

package org.apache.derby.demo.persistence; import javax.jdo.JDOHelper; import javax.jdo.PersistenceManager; import javax.jdo.PersistenceManagerFactory; import javax.jdo.Query; import javax.jdo.Transaction; ... public class JPoxJDO { private static final String JPOX_CONFIG = "jpox.properties"; private static Properties prop; private boolean isInitialized = false; private PersistenceManager pm; public JPoxJDO() { if (isInitialized) { return; } else { initialize(); } } private void initialize() { System.out.println("JpoxJDO, initialize called"); getDatasourceProps(); pm = createPersistenceManager(); isInitialized = true; return; } // load all of the various JPOX properties from a properties file private void getDatasourceProps() { InputStream is = this.getClass().getResourceAsStream(JPOX_CONFIG); prop = new Properties(); try { prop.load(is); } catch (IOException exception) { throw new RuntimeException("jpox.properties not found.", exception); } } // create the PersistenceManager using the JDO Helper class // the persistencemanage is used for all JPOX transactions private static PersistenceManager createPersistenceManager() { PersistenceManagerFactory pmf = JDOHelper .getPersistenceManagerFactory(prop); return pmf.getPersistenceManager(); } // close the persistence manager. public void shutdown() { if (pm != null) { pm.close(); } } // If the username is in the APP.USERS table retrieve // the database row, which in JPOX is the class UserBean public UserBean getUserPassword(String userName) { UserBean userBean; Transaction txn = pm.currentTransaction(); try { txn.begin(); // this represents the equivalent of the SQL query: // select username, password from APP.USERS where username = ? Query query = pm.newQuery(UserBean.class, "userName == username"); query.declareParameters("String username"); query.setUnique(true); userBean = (UserBean) query.execute(userName.trim()); pm.retrieve(userBean); if (userBean == null) { userBean = new UserBean(); } txn.commit(); } finally { if (txn.isActive()) { txn.rollback(); } } return userBean; } // Insert a row into the APP.USERS table by first creating a // UserBean, then using JPOX to persist the UserBean to Derby. public int insertUser(String firstName, String lastName, String userName, String email, String password) { ... // insert a row into the UserCreditCard table by first // creating a UserCreditCardBean and then using JPOX // to persist the data public int insertUserCreditCard(String lastname, String userName, String creditCardType, String creditCardNum, String creditCardDisplay) { int success = 0; UserCreditCardBean userCCBean = new UserCreditCardBean(lastname.trim(), userName.trim(), creditCardType.trim(), creditCardNum.trim(), creditCardDisplay.trim()); Transaction txn = pm.currentTransaction(); try { txn.begin(); pm.makePersistent(userCCBean); txn.commit(); success = 1; } finally { if (txn.isActive()) { txn.rollback(); success = 0; } } return success; } // query for the Destination Airports of a City based on // the Origin Airport of a flight // return the data in the form of the JPOX enhanced class, // CityBean public CityBean[] destAirports(String origAirport) { ... // query for all Cities in the APP.CITIES table // returning an Array of CityBeans public CityBean[] cityList() { ... // return an array of FlightsBean which include a specific // ORIG_AIRPORT and DEST_AIRPORT public FlightsBean[] origDestFlightList(String origAirport, String destAirport, Date startDate) { ... // return the rows in the FlightHistory table based on the Username // in the form of an array of FlightHistoryBeans public FlightHistoryBean[] fetchFlightHistory(String userName) { ... // insert into the UserFlightHistory table by creating // the FlightHistoryBean and persisting it to Derby public int insertUserFlightHistory(String userName, FlightsBean flightsBean, String creditCardType, String creditCardDisplay) { ... }

Notice the use of the javax.jdo.Query and javax.jdo.Transaction classes to create the queries and transactions used by JPOX.

Here is the properties file, jpox.properties, used by the JPoxJDO.java class to set properties for JDO and JPOX. The Derby JDBC Embedded Driver is used for the JDBC Driver and the Derby Connection URL points to the relative path to the airlinesDB database under the data directory.

Properties file to set the JPOX properties

javax.jdo.PersistenceManagerFactoryClass=org.jpox.PersistenceManagerFactoryImpl org.jpox.autoCreateSchema=false org.jpox.validateTables=false org.jpox.validateConstraints=false javax.jdo.option.NontransactionalRead=true javax.jdo.option.ConnectionDriverName=org.apache.derby.jdbc.EmbeddedDriver javax.jdo.option.ConnectionURL=jdbc:derby:data/airlinesDB javax.jdo.option.ConnectionUserName=APP javax.jdo.option.ConnectionPassword=mine

Prior to running the sample application below make sure the jpox.properties file is in the directory where the JPoxJDO class is since the jpox.properties file is read at run time to initialize the JPoxJDO class.

Step 9. Code the application class which calls the business logic class

The DerbyJPox.java class calls the methods of the JPoxJDO.java class as you might in a sample application.

Partial Listing of DerbyJPox.java application class

package org.apache.derby.demo.application; public class DerbyJPox { public static void main(String[] args) { JPoxJDO jpoxJDO = new JPoxJDO(); UserBean userBean = jpoxJDO.getUserPassword("apacheu"); ... // Note, the user table has a primary key on username ... this insert // will only work once. int success = jpoxJDO.insertUser("Susan", "Cline", "slc", "slc@mycomp.org", "mypass"); ... CityBean[] cityBeans = jpoxJDO.destAirports("ATH"); ... FlightsBean flightsBean = new FlightsBean("AA1134", 1, "ATH", new Time(0), "AMS", new Time(1), "B", 12.00, "1200", "B747", new Date()); int success2 = jpoxJDO.insertUserFlightHistory("slc", flightsBean, "visa", "4728-xxxx-xxxx-xxxx-4567"); ... FlightHistoryBean[] flightHistoryBeans = jpoxJDO.fetchFlightHistory("slc"); ... jpoxJDO.shutdown(); ... } }

Step 10. Compile all classes, enhance the persistence capable ones and run the application

The instructions below show how to compile all classes and enhance the persistence-capable classes assuming the download file, jpox_derby.zip was unzipped to the C:/ directory on a windows machine.

Directory structure after unzipping jpox_derby.zip

C:\JPOX_Derby | + --- data | | | + --- airlinesDB.sql | + --- airlinesDB | + --- src (the java source files) | | | + --- package.jdo | + --- log4j.properties | + --- org | | | + --- apache | | | + --- derby | | | + --- demo | | | + --- application | | | | | + --- DerbyJPox.java | | | | + --- beans | | | | | + --- model | | | | | | | + --- UserBean.java | | | + --- UserCreditCardBean.java | | | | | + --- view | | | | | | | + --- CityBean.java | | | + --- FlightHistoryBean.java | | | + --- FlightsBean.java | | | | + --- persistence | | | | | + --- JPoxJDO.java | | + --- jpox.properties | | |

To make this example more straightforward, complete the following steps before proceeding to the next step.

  • Create a lib and a bin directory under the \JPOX_Derby directory.
  • Copy these six jar files to the lib directory.
    • bcel-5.1.jar
    • derby.jar
    • jdo2-api-2.0-rc1.jar
    • jpox-1.1.0-rc-1.jar
    • jpox-enhancer-1.1.0-rc-1.jar
    • log4j-1.2.12.jar
  • Copy the log4j.properties file that is in the \JPOX_Derby\src directory to the \JPOX_Derby\bin directory.
  • Copy the package.jdo file that is in the \JPOX_Derby\src directory to the \JPOX_Derby\bin directory.

Referring to the above directory structure as an example, first compile the classes that we will later make persistence capable. The compiled classes need to go into the bin directory you just created.

Note: In all of these examples make sure there are no spaces in the value for the classpath specified by the -cp flag in the java command. The spaces have been added for readability.

Compiling the Java Data Objects

C:\JPOX_Derby>javac -d bin src\org\apache\derby\demo\beans\model\*.java src\org\apache\derby\demo\beans\view\*.java

Enhancing these classes using the JPOXEnhancer class

C:\JPOX_Derby>java -Dlog4j.configuration=file:src\log4j.properties -cp lib\bcel-5.1.jar;lib\jpox-enhancer-1.1.0-rc-1.jar;lib\jpox-1.1.0-rc-1.jar; lib\jdo2-api-2.0-rc1.jar;lib\log4j-1.2.12.jar;bin org.jpox.enhancer.JPOXEnhancer src\package.jdo

The output from enhancing these five classes

JPOX Enhancer (version 1.1.0-rc-1) : Enhancement of classes ENHANCED (PersistenceCapable) : org.apache.derby.demo.beans.view.CityBean ENHANCED (PersistenceCapable) : org.apache.derby.demo.beans.view.FlightsBean ENHANCED (PersistenceCapable) : org.apache.derby.demo.beans.view.FlightHistoryBe an ENHANCED (PersistenceCapable) : org.apache.derby.demo.beans.model.UserBean ENHANCED (PersistenceCapable) : org.apache.derby.demo.beans.model.UserCreditCard Bean

Next, we'll compile both the business logic class, org.apache.derby.demo.persistence.JPoxJDO and the application class, org.apache.derby.demo.application.DerbyJPox. When compiling these two classes do not recompile the classes you just enhanced. Doing so, will "remove" the enhancement and cause the application to fail at runtime.

Compiling the business logic and application class

C:\JPOX_Derby>javac -d bin -cp lib\jdo2-api-2.0-rc1.jar;lib\jpox-1.1.0-rc-1.jar; bin src\org\apache\derby\demo\persistence\*.java src\org\apache\derby\demo\application\*.java

We need to do one last step prior to running the application class. Copy the jpox.properties file from the C:\JPOX_Derby\src\org\apache\derby\demo\persistence directory to the C:\JPOX_Derby\bin\org\apache\derby\demo\persistence directory.

Now we're ready to run the sample application. Use the command below to run the DerbyJPox.java class.

Run the application class, DerbyJPox

C:\JPOX_Derby>java -cp lib\jdo2-api-2.0-rc1.jar;lib\jpox-1.1.0-rc-1.jar; lib\derby.jar;lib\log4j-1.2.12.jar;bin org.apache.derby.demo.application.DerbyJPox

The output from running the application class, DerbyJPox.java is shown below.

Output from DerbyJPox

JpoxJDO, initialize called Issuing JPOX queries against the Derby DB, airlinesDB *********************************************************** Query 1: The UserBean corresponding to the username apacheu is: firstname: apache lastname: user email: apacheu@mycompany.com *********************************************************** Insert 1: Create a new user by calling JPoxJDO.insertUser( "Susan", "Cline", "slc", "slc@mycomp.org", "mypass") Successfully inserted the user 'slc' into the database. *********************************************************** Query 2: Find the destination cities of flights from Athens. Dest City: Amsterdam Dest City: Cairo Dest City: Istanbul Dest City: Lagos Dest City: London Dest City: Paris *********************************************************** Insert 2: Create a FlightsHistoryBean by calling JPoxJDO.insertUserFlightHistory ("slc", myFlightsBean, "visa", "4728-xxxx-xxxx-xxxx-4567") Successfully inserted a row into the FlightHistory table. *********************************************************** Query 3: Select the row from the FlightHistory table based on username slc by calling JPoxJDO.fetchFlightHistory("slc") Flight: AA1134 Orig Airport: ATH Dest: AMS Credit Crd: 4728-xxxx-xxxx-xxxx-4567 *********************************************************** Shutting down JPOX persistence manager. ***********************************************************

Resources and Acknowledgements

The Derby web site and wiki are the best sources of information about Derby.

For in-depth information about JPOX see the JPOX web site. Apache JDO provides the JD0 2.0 API which is available from the Apache JDO web site. JPOX JDO provides the reference implementation of the JDO 2.0 specification via the JPOX JDO 1.1 release.

The JPOX 1.1 Tutorial is vital to get up to speed with JPOX JDO.

Many thanks to Michelle Caisse and Craig Russell of the Apache JDO project in helping me with my "newbie" questions on configuring JPOX with Derby and general JDO questions.

Susan Cline wrote this paper and would appreciate any feedback in the form of suggestions, corrections or questions about it by posting to the derby-user mailing list.

Last updated: March 8, 2006