In Database Example, we created
MyDbEnv
, a class that manages
DatabaseEnvironment
and Database
opens and closes. We will now write an application that takes advantage of
this class to open databases, put a series of records in them, and then
close the databases and environment.
Remember that all of the classes and programs presented here can be found in the following directory:
JE_HOME/examples/je/gettingStarted
where JE_HOME
is the location where you
placed your JE distribution.
Note that in this example, we are going to save two types of information. First there are a series of inventory records that identify information about some food items (fruits, vegetables, and desserts). These records identify particulars about each item such as the vendor that the item can be obtained from, how much the vendor has in stock, the price per unit, and so forth.
We also want to manage vendor contact information, such as the vendor's address and phone number, the sales representative's name and his phone number, and so forth.
Example 8.1 Inventory.java
All Inventory data is encapsulated in an instance of the following
class. Note that because this class is not serializable, we need a
custom tuple binding in order to place it on a DatabaseEntry
object. Because the TupleInput
and
TupleOutput
classes used by custom tuple bindings
support Java numerical types and not Java numerical classes, we use
int
and float
here instead of the
corresponding Integer
and Float
classes.
// File Inventory.java package je.gettingStarted; public class Inventory { private String sku; private String itemName; private String category; private String vendor; private int vendorInventory; private float vendorPrice; public void setSku(String data) { sku = data; } public void setItemName(String data) { itemName = data; } public void setCategory(String data) { category = data; } public void setVendorInventory(int data) { vendorInventory = data; } public void setVendor(String data) { vendor = data; } public void setVendorPrice(float data) { vendorPrice = data; } public String getSku() { return sku; } public String getItemName() { return itemName; } public String getCategory() { return category; } public int getVendorInventory() { return vendorInventory; } public String getVendor() { return vendor; } public float getVendorPrice() { return vendorPrice; } }
Example 8.2 Vendor.java
The data for vendor records are stored in instances of the following class. Notice that we are using serialization with this class simply to demonstrate serializing a class instance.
// File Vendor.java package je.gettingStarted; import java.io.Serializable; public class Vendor implements Serializable { private String repName; private String address; private String city; private String state; private String zipcode; private String bizPhoneNumber; private String repPhoneNumber; private String vendor; public void setRepName(String data) { repName = data; } public void setAddress(String data) { address = data; } public void setCity(String data) { city = data; } public void setState(String data) { state = data; } public void setZipcode(String data) { zipcode = data; } public void setBusinessPhoneNumber(String data) { bizPhoneNumber = data; } public void setRepPhoneNumber(String data) { repPhoneNumber = data; } public void setVendorName(String data) { vendor = data; } ... // Corresponding getter methods omitted for brevity. // See examples/je/gettingStarted/Vendor.java // for a complete implementation of this class. }
Because we will not be using serialization to convert our
Inventory
objects to a DatabaseEntry
object, we need a custom tuple binding:
Example 8.3 InventoryBinding.java
// File InventoryBinding.java package je.gettingStarted; import com.sleepycat.bind.tuple.TupleBinding; import com.sleepycat.bind.tuple.TupleInput; import com.sleepycat.bind.tuple.TupleOutput; public class InventoryBinding extends TupleBinding { // Implement this abstract method. Used to convert // a DatabaseEntry to an Inventory object. public Object entryToObject(TupleInput ti) { String sku = ti.readString(); String itemName = ti.readString(); String category = ti.readString(); String vendor = ti.readString(); int vendorInventory = ti.readInt(); float vendorPrice = ti.readFloat(); Inventory inventory = new Inventory(); inventory.setSku(sku); inventory.setItemName(itemName); inventory.setCategory(category); inventory.setVendor(vendor); inventory.setVendorInventory(vendorInventory); inventory.setVendorPrice(vendorPrice); return inventory; } // Implement this abstract method. Used to convert a // Inventory object to a DatabaseEntry object. public void objectToEntry(Object object, TupleOutput to) { Inventory inventory = (Inventory)object; to.writeString(inventory.getSku()); to.writeString(inventory.getItemName()); to.writeString(inventory.getCategory()); to.writeString(inventory.getVendor()); to.writeInt(inventory.getVendorInventory()); to.writeFloat(inventory.getVendorPrice()); } }
In order to store the data identified above, we write the
ExampleDatabasePut
application. This application
loads the inventory and vendor databases for you.
Inventory information is stored in a Database
dedicated for that purpose. The key for each such record is a product SKU.
The inventory data stored in this database are objects of the
Inventory
class (see Inventory.java for more information).
ExampleDatabasePut
loads the inventory database
as follows:
Reads the inventory data from a flat text file prepared in advance for this purpose.
Uses java.lang.String
to create a key
based on the item's SKU.
Uses an Inventory
class instance for the
record data. This object is stored on a DatabaseEntry
object using InventoryBinding
, a custom tuple
binding that we implemented above.
Saves each record to the inventory database.
Vendor information is also stored in a Database
dedicated for that purpose. The vendor data stored in this database are objects of the
Vendor
class (see Vendor.java for more information). To load this
Database
, ExampleDatabasePut
does the following:
Reads the vendor data from a flat text file prepared in advance for this purpose.
Uses the vendor's name as the record's key.
Uses a Vendor
class instance for the
record data. This object is stored on a DatabaseEntry
object using com.sleepycat.bind.serial.SerialBinding
.
Example 8.4 Stored Class Catalog Management with MyDbEnv
Before we can write ExampleDatabasePut
, we need to update
MyDbEnv.java
to support the class catalogs that we need for this application.
To do this, we start by importing an additional class to support stored class catalogs:
// File MyDbEnv.java
package je.gettingStarted;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Environment;
import java.io.File;
import com.sleepycat.bind.serial.StoredClassCatalog;
We also need to add two additional private data members to this class. One supports the database used for the class catalog, and the other is used as a handle for the class catalog itself.
public class MyDbEnv {
private Environment myEnv;
private Database vendorDb;
private Database inventoryDb;
private Database classCatalogDb;
// Needed for object serialization
private StoredClassCatalog classCatalog;
public MyDbEnv() {}
Next we need to update the MyDbEnv.setup()
method to open the class catalog database and
create the class catalog.
public void setup(File envHome, boolean readOnly)
throws DatabaseException {
...
// Database and environment configuration omitted for brevity
...
// Instantiate the Environment. This opens it and also possibly
// creates it.
myEnv = new Environment(envHome, myEnvConfig);
// Now create and open our databases.
vendorDb = myEnv.openDatabase(null, "VendorDB", myDbConfig);
inventoryDb = myEnv.openDatabase(null, "InventoryDB", myDbConfig);
// Open the class catalog db. This is used to
// optimize class serialization.
classCatalogDb =
myEnv.openDatabase(null,
"ClassCatalogDB",
myDbConfig);
// Create our class catalog
classCatalog = new StoredClassCatalog(classCatalogDb);
}
Next we need a getter method to return the class catalog. Note that we do not provide a getter for the catalog database itself – our application has no need for that.
// Getter methods
public Environment getEnvironment() {
return myEnv;
}
public Database getVendorDB() {
return vendorDb;
}
public Database getInventoryDB() {
return inventoryDb;
}
public StoredClassCatalog getClassCatalog() {
return classCatalog;
}
Finally, we need to update the MyDbEnv.close()
method to close the
class catalog database.
// Close the environment
public void close() {
if (myEnv != null) {
try {
vendorDb.close();
inventoryDb.close();
classCatalogDb.close()
myEnv.close();
} catch(DatabaseException dbe) {
System.err.println("Error closing MyDbEnv: " +
dbe.toString());
System.exit(-1);
}
}
}
}
So far we have identified the data that we want to store in our
databases and how we will convert that data in and out of
DatabaseEntry
objects for database storage. We
have also updated MyDbEnv
to manage our databases
for us. Now we write ExampleDatabasePut
to
actually put the inventory and vendor data into their respective
databases. Because of the work that we have done so far, this
application is actually fairly simple to write.
Example 8.5 ExampleDatabasePut.java
First we need the usual series of import statements:
//File ExampleDatabasePut.java package je.gettingStarted; // Bind classes used to move class objects in an out of byte arrays. import com.sleepycat.bind.EntryBinding; import com.sleepycat.bind.serial.SerialBinding; import com.sleepycat.bind.tuple.TupleBinding; // Standard JE database imports import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.DatabaseException; // Most of this is used for loading data from a text file for storage // in the databases. import java.io.File; import java.io.FileInputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.List;
Next comes the class declaration and the private data members that we need for this class. Most of these are setting up default values for the program.
Note that two DatabaseEntry
objects are
instantiated here. We will reuse these for every database operation that
this program performs. Also a MyDbEnv
object is
instantiated here. We can do this because its constructor never throws
an exception. See Stored Class Catalog Management with MyDbEnv for
its implementation details.
Finally, the inventory.txt
and
vendors.txt
file can be found in the GettingStarted
examples directory along with the classes described in this extended
example.
public class ExampleDatabasePut { private static File myDbEnvPath = new File("/tmp/JEDB"); private static File inventoryFile = new File("./inventory.txt"); private static File vendorsFile = new File("./vendors.txt"); // DatabaseEntries used for loading records private static DatabaseEntry theKey = new DatabaseEntry(); private static DatabaseEntry theData = new DatabaseEntry(); // Encapsulates the environment and databases. private static MyDbEnv myDbEnv = new MyDbEnv();
Next comes the usage()
and
main()
methods. Notice the exception handling
in the main()
method. This is the only place in the application where we
catch exceptions. For this reason, we must catch
DatabaseException
which is thrown by the
com.sleepycat.je.*
classes.
Also notice the call to MyDbEnv.close()
in the finally
block. This is the only place in the
application where MyDbEnv.close()
is called.
MyDbEnv.close()
is responsible for closing the
Environment
and all open Database
handles for you.
private static void usage() { System.out.println("ExampleDatabasePut [-h <env directory>]"); System.out.println(" [-s <selections file>]"); System.out.println(" [-v <vendors file>]"); System.exit(-1); } public static void main(String args[]) { ExampleDatabasePut edp = new ExampleDatabasePut(); try { edp.run(args); } catch (DatabaseException dbe) { System.err.println("ExampleDatabasePut: " + dbe.toString()); dbe.printStackTrace(); } catch (Exception e) { System.err.println("Exception: " + e.toString()); e.printStackTrace(); } finally { myDbEnv.close(); } System.out.println("All done."); }
Next we write the ExampleDatabasePut.run()
method. This method is responsible for initializing all objects.
Because our environment and databases are all opened using the
MyDbEnv.setup()
method, ExampleDatabasePut.run()
method is only responsible for calling MyDbEnv.setup()
and then calling
the ExampleDatabasePut
methods that actually load the databases.
private void run(String args[]) throws DatabaseException { // Parse the arguments list parseArgs(args); myDbEnv.setup(myDbEnvPath, // path to the environment home false); // is this environment read-only? System.out.println("loading vendors db."); loadVendorsDb(); System.out.println("loading inventory db."); loadInventoryDb(); }
This next method loads the vendor database. This method
uses serialization to convert the Vendor
object
to a DatabaseEntry
object.
private void loadVendorsDb() throws DatabaseException { // loadFile opens a flat-text file that contains our data // and loads it into a list for us to work with. The integer // parameter represents the number of fields expected in the // file. List<String[]> vendors = loadFile(vendorsFile, 8); // Now load the data into the database. The vendor's name is the // key, and the data is a Vendor class object. // Need a serial binding for the data EntryBinding dataBinding = new SerialBinding(myDbEnv.getClassCatalog(), Vendor.class); for (int i = 0; i < vendors.size(); i++) { String[] sArray = vendors.get(i); Vendor theVendor = new Vendor(); theVendor.setVendorName(sArray[0]); theVendor.setAddress(sArray[1]); theVendor.setCity(sArray[2]); theVendor.setState(sArray[3]); theVendor.setZipcode(sArray[4]); theVendor.setBusinessPhoneNumber(sArray[5]); theVendor.setRepName(sArray[6]); theVendor.setRepPhoneNumber(sArray[7]); // The key is the vendor's name. // ASSUMES THE VENDOR'S NAME IS UNIQUE! String vendorName = theVendor.getVendorName(); try { theKey = new DatabaseEntry(vendorName.getBytes("UTF-8")); } catch (IOException willNeverOccur) {} // Convert the Vendor object to a DatabaseEntry object // using our SerialBinding dataBinding.objectToEntry(theVendor, theData); // Put it in the database. These puts are transactionally // protected (we're using autocommit). myDbEnv.getVendorDB().put(null, theKey, theData); } }
Now load the inventory database. This method uses our
custom tuple binding (see InventoryBinding.java) to convert the Inventory
object to a DatabaseEntry
object.
private void loadInventoryDb() throws DatabaseException { // loadFile opens a flat-text file that contains our data // and loads it into a list for us to work with. The integer // parameter represents the number of fields expected in the // file. List<String[]> inventoryArray = loadFile(inventoryFile, 6); // Now load the data into the database. The item's sku is the // key, and the data is an Inventory class object. // Need a tuple binding for the Inventory class. TupleBinding inventoryBinding = new InventoryBinding(); for (int i = 0; i < inventoryArray.size(); i++) { String[] sArray = inventoryArray.get(i); String sku = sArray[1]; try { theKey = new DatabaseEntry(sku.getBytes("UTF-8")); } catch (IOException willNeverOccur) {} Inventory theInventory = new Inventory(); theInventory.setItemName(sArray[0]); theInventory.setSku(sArray[1]); theInventory.setVendorPrice( (new Float(sArray[2])).floatValue()); theInventory.setVendorInventory( (new Integer(sArray[3])).intValue()); theInventory.setCategory(sArray[4]); theInventory.setVendor(sArray[5]); // Place the Vendor object on the DatabaseEntry object using // our the tuple binding we implemented in // InventoryBinding.java inventoryBinding.objectToEntry(theInventory, theData); // Put it in the database. myDbEnv.getInventoryDB().put(null, theKey, theData); } }
The remainder of this application provides utility methods to read a flat text file into an array of strings and parse the command line options. From the perspective of this document, these things are relatively uninteresting. You can see how they are implemented by looking at:
JE_HOME/examples/je/gettingStarted/ExampleDataPut.java
where JE_HOME
is the location where you
placed your JE distribution.
private static void parseArgs(String args[]) { // Implementation omitted for brevity. } private List loadFile(File theFile, int numFields) { List<String[]> records = new ArrayList<String[]>(); // Implementation omitted for brevity. return records; } protected ExampleDatabasePut() {} }