Mobile Databases for MIDP Devices
-
font size
decrease font size
increase font size
12.1 PointBase Micro Edition
Among all mobile databases, PointBase Micro (v4.5) has the best CLDC/MIDP support. It actually produces a SQL database on the MIDP platform. Point-Base provides a set of proprietary Lite APIs under the com.pointbase.me package to access its MIDP database. The PointBase Lite API is very similar to JDBC. The only thing a JDBC developer needs to know to get started is the URL to obtain a Connection object (Listing 12.1).
Listing 12.1. Connect to a PointBase Micro MIDP database
public void connect() {
try {
Connection c = DriverManager.getConnection(
"jdbc:pointbase:micro:" +
m_dbname, "PBPUBLIC", "PBPUBLIC");
Statement s = c.createStatement();
} catch (Exception ex) {
// Handle the error
}
}
The PointBase Micro Lite database has a footprint of 47 KB. In the Lite API's PreparedStatement and ResultSet classes, the setter and getter methods support only the following types: Int, String, Decimal, Date, and Bytes. The setBytes() and getBytes() methods allow us to manipulate binary database fields (i.e., Blob fields). You can use your camera phone to take pictures and store them in your on-device PointBase MIDP database! For more details, please refer to PointBase Micro documentation and sample applications (see "Resources").
PointBase also provides a utility MIDlet, com.pointbase.me.tools. MicroConsoleMIDP. It allows developers to quickly peek into a database without writing a single line of access and UI code. The console has a simple UI to browse the content and schema of currently available tables. This console MIDlet is especially useful during the testing and debugging stages of application development. We can bundle it into our testing MIDlet Suite to view the database content at real time for diagnostic purposes. Screen shots of the MIDP Micro Console in action are shown in Figure 12.1.

12.2 The Oracle J2ME SODA SDK
The Oracle9i Lite database does not run on the MIDP platform. Instead, Oracle provides a Java object database for MIDP devices. The Oracle J2ME SDK (beta) supports the Oracle Simple Object Database Access (SODA) data store. Built on top of the standard RMS, SODA allows us to store, search, and retrieve Java data objects directly. The oracle.wireless.me.soda package contains six classes (see descriptions in Table 12.1).

Listing 12.2. The SODA demo
DBSession sess = new DBSession();
// Create a new DBClass with an INT field
// and a String field.
// The name of this DBClass is "dept". You can
// get it by its name using findClass() method
// in the sess object.
DBClass dept = sess.createClass(
"dept",
new DBAttr[] {
new DBAttr("id", DBAttr.C_INT),
new DBAttr("name", DBAttr.C_STRING)
}
);
// Load a comma delimited database file.
// It will create a number of DBObjects
// under the "dept" DBClass.
DBLoader.loadCSV(dept, new InputStreamReader(in), dept.allAttr());
// Now, get all DBObjects (null match condition)
// in dept class in a DBCursor
DBCursor c = dept.createCursor(null, null);
// Iterate and get all field attributes
// in the DBClass.
DBAttr[] a = dept.getAttrs();
for (int i = 0; i < a.length; i++)
System.out.print(a[i].name+", ");
System.out.println();
DBObject o;
// Iterate through the cursor and get
// data in each column (field).
while ((o = c.next()) != null) {
for (int i = 0; i < a.length; i++)
System.out.print(o.getString(i)+",");
System.out.println();
}
c.close();
}
// Remember to release resources.
sess.close();
12.3 The IBM DB2e FastRecordStore
IBM DB2e (v8.1) provides a FastRecordStore class over the MIDP standard RMS record store. FastRecordStore packs several database rows into one RMS record and supports indexes for fast lookup. This results in much improved performance over the linear RMS record store.
To create a database table in a FastRecordStore, we have to first create the table in a DB2e backend database. Then, we synchronize the table to the MIDP device using IBM DB2e Sync. After the synchronization, a FastRecordStore with the same name as the backend table is created. We can now read or update data in the FastRecordStore via the TableMetaData class. All the changes we make on the MIDP client will be sent back to the backend table upon the next synchronization operation. For more information about database synchronization and the IBM Sync,
Note
We cannot start with record stores on the MIDP client first, as they will be deleted on the first synchronization.
In the FastRecordStore, each table row is packed into a byte array. Developers are required to manipulate those raw arrays manually. All rows start with a dirty byte and are followed by data in each column according to the following rules.
-
If the column is not nullable, the data for the column is present.
-
If the column is nullable there is a 1-byte boolean null indicator. If the indicator is true, no data follows. Otherwise, the column data follows.
To illustrate the above points, the SQL INSERT statement in Listing 12.3 can be interpreted as the Java code in Listing 12.4.
Listing 12.3. SQL script for a sample table
create table MyTable (a int, b int not null, c varchar(20),
id bigint not null primary key);
insert into MyTable values (null, 10, 'Have fun', 99);
Listing 12.4. Java code for the INSERT statement in Listing 12.3
DataOutputStream dout = new DataOutputStream(byteArrayOutStrm);
// The dirty byte
dout.writeByte(0);
// 'a' is null, no data follows
dout.writeBoolean(true);
// 'b', not nullable
// So there is no null indicator
dout.writeInt(10);
// 'c' is nullable, but not null
dout.writeBoolean(false);
dout.writeUTF("Have fun");
// 'id' not nullable
dout.writeLong(99);
The following code (Listing 12.5) demonstrates how to iterate through rows, delete a row, and read out data in a FastRecordStore.
Listing 12.5. Browse the FastRecordStore
TableMetaData rs = ((MIDPISync)isync).getTableMetaDataByName(sName);
int numCols = rs.getNumCols();
FastRecordStore rms = FastRecordStore.openRecordStore(sName, false);
Index index = new Index(rms, rs);
FastRecordEnumeration enum = rms.enumerateRecords(null, null, false);
// Iterate through rows
while (enum.hasNextElement()) {
int id = enum.nextRecordId();
int recSize = rms.getRecordSize(id);
byte data = new byte[recSize];
recSize = rms.getRecord(id, data, 0);
// Example: delete a row
if (id == rowToDelete) {
data[0] = ISync.ROW_DELETED;
index.updateRecord(id, data, recSize);
continue;
}
// Read out each field for each row
ByteArrayInputStream bin = new ByteArrayInputStream(data);
DataInputStream din = new DataInputStream(bin);
din.readByte(); // dirty byte
for (int c = 0; c < numCols; c++) {
int type = rs.getType(c);
boolean isNullable = rs.isNullable(c);
boolean ind = false;
if (isNullable) ind = din.readBoolean();
if (ind) continue;
switch (type) {
case VARCHAR:
strval = din.readUTF();
break;
case INTEGER:
intval = din.readInt();
break;
// more ...
}
}
}