Programming with the Java Class Library
The following topics provide instructions and details for programming Java in PSQL:
PSQL Java Interface Class Structure
This topic describes the classes for the PSQL Java interface.
Applying the “factory” pattern, almost all classes (with the exception of Driver, Timestamp, FieldDef and PsqlException) can only be produced through methods of another class (or other classes). In the description of classes, under the label Constructors no constructors are listed when no public constructors are available.
The PSQL Java interface has a special class, PsqlException to handle error conditions occurring when methods of the API are invoked. PsqlException extends the java.lang.Exception. Each method of the PSQL Java classes throws PsqlException and for brevity, the following clause is left out from the method definitions:
throws PsqlException
Figure
2 shows all classes and their relationship.
Figure 2 Java API Class Structure
The classes of the PSQL Java Interface can be divided into three groups:
General Classes
The following classes belong to General Classes group:
•Driver
•Session
•Database
•Timestamp
•PsqlException
These classes can be used both for tightly coupled and loosely coupled databases.
Rowset Family Classes
The following classes belong to the Rowset Family Classes group:
•Table
•Rowset
•Buffer
•Row
•Index
•DBMetaData
•TableMetaData
•RowSetMetaData
•ColumnDef
•IndexMetaData.
These classes make use of the metadata found in the dictionary of a PSQL database and, therefore, can be applied only for tightly coupled databases.
Cursor Family Classes
This is the low-level portion of the PSQL Java Interface. If you use this portion of the interface, you must handle position blocks, data buffers, and other elements of programming that are typical of the Btrieve API.
Note You must use this family of the Java interface if your Btrieve files do not have DDFs. You can use the PSQL Control Center utility to create DDF files for your data. See PSQL User's Guide for more information.
The following classes belong to Cursor Family Classes group:
•SCursor
•XCursor
•FieldDef
These classes do not use any meta data, they directly support the “classic” Btrieve operations (for example, through a BTRV method). Therefore, they are applied typically for loosely coupled databases. However, they can also be used to access data files that store tables of a tightly coupled database. (Although, it is more convenient to use the “rowset family” in the latter case.)
Overview of Major Classes and Methods
The following are the principal classes and methods involved in a basic Java application.
Principal Classes and Methods | Used for |
Driver establishSession killAllSessions | •Establishing one or more sessions. •Stopping all Sessions |
Session connectToDatabase startTransaction commitTransaction abortTransaction | •Connecting to Databases •Handling transactions |
Database getTable getTableNames createxCursor | •Obtaining Table names and Tables •Creating an xCursor to perform low-level operations if needed. |
Table getTableMetaData createRowSet | •Creating a RowSet, which is a view into the table data. •Obtaining metadata information about tables. |
TableMetaData getIndexNames | •Obtaining the Indexes from a Table. |
RowSet createRow insertRow deleteRow updateRow getNext getByIndex reset | •Obtaining part of a table •Updating Row information |
RowSetMetaData AddColumns DropColumns AddFirstTerm SetAccessPath SetDirection | •Adding and Dropping Columns •Performing “WHERE” clause functionality •Changing direction of navigation |
Row getString setString getFloat setFloat | •Actual Updating of Data obtained through RowSets. |
There are other classes and methods that you can view in the PSQL Java Class Library Reference, but these are the classes you will use most often in your application.
The structure of the Java interface classes is flat; there is no inheritance in these classes.
DRIVER
Driver establishes Sessions. You can use Driver to instantiate as many Session objects as needed. You can also use the KillallSessions method to stop the application, as it will clean up socket connections, database connections, open files, and system resources.
SESSION
Sessions use the connection to the driver and allow you to connect to databases using URIs and start and end transactions. Each session receives a unique Btrieve client ID. Unless your application specifically needs multiple Btrieve client IDs, you can generally use one Session for the entire application. You can reset the Session and the Btrieve client by calling Session.close(). For more information about client IDs, see
Btrieve Clients in
PSQL Programmer's Guide and
License Models in
PSQL User's Guide.
DATABASE
Objects instantiated using this class are the database itself. Methods in this class allow the developer to, in the case of tightly coupled databases, get the table names, tables and so forth. In the case of the loosely coupled database, the developer can access familiar Btrieve-like APIs including extended operations.
TABLE
An instance of this class represents a table as an object. Using table objects, you can create row sets and get meta data information about tables.
ROWSET
RowSet is an extremely important class. It is used to access the rows of the associated table. In addition, rows can be inserted, updated, and deleted through methods of this class.
An instance of this class represents a set of rows that is derived from the base table. One other way to look at it: a RowSet is a virtual table (a view) generated from a physically stored table.
Note This object can be constructed only through the createRowSet method of Table.
A row set could include all rows or a subset of rows of the base table. A number of methods in RowSetMetaData (e.g.addFirstTerm, addAndTerm, and so forth) is available to provide the terms of a restriction criterion that determines which rows from the base table are included into the row set.
A row set could include all columns or any number of selected columns of the base row. When a RowSet object is created by Table.createRowSet, it is initialized according to the value of noColumns specified explicitly or implicitly in the call. If noColumns is specified as true, then no column is included into the row set and the application can selectively include the columns into the row set by calling RowSetMetaData.addColumns. If noColumns is specified a false or it is not included as an argument then the created RowSet includes all columns of the table.
The applications normally do not have to open and close the row sets. The associated data files are opened at the construction time of the RowSet objects and the data files are closed when either the application explicitly calls RowSet.close or as a result of closing a higher-level associated object (Table, Database, etc).
The elements of a row set can be accessed through Row objects. There multiple options are available to retrieve the rows.
For important additional information about row sets, see PSQL Java Class Library Reference.
ROW
An instance of this class represents a row buffer for a row in a RowSet. The Class Row enables access to rows and to column values within a row. It can be used to retrieve rows, update, or delete rows and to build rows for insertion.
This class has no methods of its own, but the methods inherited from Buffer can be used to get/set values of columns in the row buffer.
Sequence of Steps for a Java Application
The following sequence of steps are typical of an application or applet. The first two steps are the same for both tightly coupled and for loosely coupled databases:
1 Establish an instance of Session that is used as the execution environment. (A Driver object does not have to be instantiated because Driver is a static class.) It is also possible for an application/applet to create multiple sessions.
2 Get a Database object for the database by invoking Session.connectToDatabase() specifying a URI. See PSQL Programmer's Guide for more information on database URIs. It is also possible to get multiple Database objects in a session, one for each database to be accessed.
Case 1: The application is accessing tables of a tightly coupled database.
3 Optionally, get the names of tables in the database (if they are not known for the application) by invoking Database.getTableNames.
4 Get a Table object for each table to be worked on by invoking Database.getTable.
5 Optionally, get a TableMetaData object for each table if the application has to get the column names and/or index names from the dictionary.
6 Create at least one RowSet object for each table to be processed in the application. RowSet objects are used to retrieve and modify data. A row set can be created including all or none of columns of the table (see more in step
7).
An application creates more than one RowSet for a table if it wants to process the table in some parallel fashion while maintaining multiple currencies (positioning) when navigating through the table. A RowSet object for a given table can be used for the entire duration of the application. That is, it can be used for retrievals by different indexes, by different conditions, and so forth.
7 For a given row set
a. Get a RowSetMetaData object for the row set.
b. Add needed columns to the definition if the row set was created with no columns included.
c. Change the default characteristics (direction, access path, and so forth) of the row set if necessary.
d. Define a selection criterion (addFirstTerm, addAndTerm, etc) if a row set should include only some selected rows of the base table.
e. Invoke RowSet.getNext method to retrieve Row objects. Optionally, invoke other methods of RowSet (getByIndex, getbyPercent, insertRow, updateRow, and so forth).
f. Invoke methods of Row (getString, setString, getInt, and so forth) to get/set values of columns in a row.
Note Steps a–d are optional and in some applications may not be needed for some row sets.
Case 2: The application is accessing data files of a loosely coupled database (or data files that store tables of a tightly coupled database in the "classic" way).
3. Create an SCursor or an XCursor object for each data file the application plans to work on. XCursor is needed only if any extended get/step operation will be executed on the file. Optionally, the application can get multiple cursor objects for the same file if it wants to process the file in some parallel fashion maintaining multiple positions at the same time.
4 Execute “classic” Btrieve operations (open, get/step, insert, etc) using SCursor.BTRV.
Note The BTRV method can be used also on XCursor because XCursor extends SCursor.
5 Optionally, define some FieldDef objects for data fields, use these field objects to set selection criterion (XCursor.addFirstTerm, etc) and define extraction list and use the methods like getNextExtended to execute extended operations.
6 Invoke methods like getDString, setKString, getDInt4, and so forth, to get/set values of fields in the private data and key buffers of SCursor (XCursor) objects.
The sections that follow explain these overview steps in more detail.
First Steps with the Java Class Library
To build your first Java Btrieve application:
Verify your Environment is Correct
You should make sure your environment is set up correctly as described in
How to Set Up your Environment.
In addition, make sure that “psql.jar” is in your CLASSPATH environment variable. The installation procedure should have performed this step for you. To access the classes in the PSQL Java interface class library, you must import the package in your Java source files.
import PSQL.database.*;
Create your Database and Tables
Currently, the PSQL Java interface class library does not support the creation of databases and database tables. Use the PSQL Control Center utility to perform these tasks. Once the database Data Dictionary Files (DDFs) and data files have been created, you can use the Java API classes to populate and access the tables.
For more information on the PSQL Control Center and creating tables, see PSQL User's Guide.
Connect to a PSQL Database
There are two options for connecting to a PSQL database. Either connect directly to the engine or connect using the I*net Data Server.
To connect to a database using a URI
1 Get a Session object from the Driver.
Session session = Driver.establishSession();
2 Use the Session object to connect to the database. Specify a URI to connect to the database. At a minimum, this URI should contain btrv:///dbname.
Database db = session.connectToDatabase("btrv:///demodata");
or
Database db = session.connectToDatabase("btrv://user@host/demodata?pwd=password");
See Advanced Operations Guide for more information on database security and URIs.
Obtain the Tables and Create Rowsets
►Retrieving a table from the database
Table table = db.getTable("MyTable");
where “MyTable” is the name of the database table. The table name is case sensitive.
You can also get a list of the database’s table names.
String [] names = db.getTableNames();
► To access a Table object’s properties
Once you have a Table object, you can obtain information about the columns and indexes from its TableMetaData object.
TableMetaData tmd = table.getTableMetaData();
Some examples:
int num_columns = tmd.getColumnCount();
int data_type = tmd.getColumnDef(0).getType());
int col_length = tmd.getColumnDef(0).getOffset();
► To access a Table object’s rows
To access a Table’s rows, you need a RowSet object which can only be created by a Table object.
RowSet rowset = table.createRowSet();
You can create multiple RowSet objects from the same table. RowSet objects are used to retrieve a table’s rows, insert new rows, and delete and update existing rows. To iterate through all the table’s rows, create a RowSet object and call getNext() until an PsqlEOFException occurs.
try {
while(true)
Row row = rowset.getNext();
}
catch(PsqlEOFException ex) {
}
►To access a RowSet object’s properties
Once you have a RowSet object, you can obtain information about the RowSet from its RowSetMetaData.
RowSetMetaData rsmd = rowset.getRowSetMetaData();
Consult the methods of the RowSetMetaData class in PSQL Java Class Library Reference for more details.
Navigate the Rows in a Row Set
As previously shown, you can iterate through the row set’s rows by repeatedly calling the “getNext” method. You can iterate backwards by changing the “direction” property of the row set’s RowSetMetaData object.
rsmd.setDirection(Const.BTR_BACKWARDS);
After setting this property, the behavior of the “getNext” method becomes “getPrevious.”
Note You can “reset” the currency of the row set at any time to be before the first or after the last row (as shown in the following procedures). This is an efficient way to retrieve the first row or last row without having to iterate through the entire row set.
►To retrieve the first row in the row set
1 Set the direction to forwards. This is the default.
rsmd.setDirection(Const.BTR_FORWARDS);
2 Set the row set’s currency to before the 1st row.
rowset.reset();
3 Retrieve the first row.
Row first = rowset.getNext();
►To retrieve the last row in a row set
1 Set the direction to backwards.
rsmd.setDirection(Const.BTR_BACKWARDS);
2 Set the row set’s currency to after the last row.
rowset.reset();
3 Retrieve the last row.
Row last = rowset.getNext();
► To access a Row object’s column data
Row objects inherit a multitude of accessor/mutator methods from the Buffer class. These methods allow you to set/get column data to/from a Row object’s buffer.
1 For example, to retrieve the data in column 0 as a String, do one of the following.
String str = row.getString(0);
or
String str = row.getString("ColumnName");
where “ColumnName” is the name defined in the data dictionary for column 0. Column names are case sensitive.
2 Now, to set the data for column 0, you use one of the setString methods.
row.setString(0, "MyColumnData");
or
row.setString("ColumnName", "MyColumnData");
3 Consult the methods of the Buffer class in PSQL Java Interface Reference for more details.
Restrict or Filter the Data
►To restrict/filter rows in a row set
To restrict/filter the rows in a row set, you have to use the RowSetMetaData’s “addFirstTerm,” “addOrTerm,” and “addAndTerm.” For example, if the first column of a table contains integer data and you want all the rows where the first column’s value is greater that 25, do the following.
1 Get the row set’s RowSetMetaData.
RowSetMetaData rsmd = rowset.getRowSetMetaData();
2 Get the ColumnDef for first column, column number 0.
ColumnDef cmd = rsmd.getColumnDef(0);
3 Reset the row set’s currency to the beginning.
rowset.reset();
4 Add the first term.
rowset.addFirstTerm(cmd, Const.BTR_GR, "23");
5 Call getNext() to get the first row where column 0 is greater than 23.
Row row = rowset.getNext();
You can add additional terms with the RowSetMetaData’s “addOrTerm” and “addAndTerm.” These methods allow you to build up more complex filtering conditions similar to a SQL “WHERE” clause.
►To select columns from a row set
You can specify a subset of the columns to retrieve, similar to the SQL “select” statement, by using the RowSetMetaData methods, “addColumns” and “dropColumns.” By default, a row set will retrieve all the column data unless the row set was created with the “noColumns” parameter set to “true.”
RowSet rowset = table.createRowSet(true);
In this case, no columns will be retrieved. Alternatively, after creating the row set, you can drop all or some of the columns.
RowSetMetaData rsmd =rowset.getRowSetMetaData();
rsmd.dropAllColumns();
Now you can add the set of columns you’re interested in to the row set.
rsmd.addColumns("LastName", "FirstName");
You can add columns to the row set by column names or by column numbers. See the various “addColumns” and “dropColumns” methods of the “RowSetMetaData” class in the PSQL Java Interface Reference documentation for more details.
If you access column data in the rows by column number, be aware that the column numbers will be affected by the new ordering produced by “addColumns” and “dropColumns” methods. For example, if “FirstName” was originally column number 3 in the row set, after dropping all the columns and then adding “LastName” and “FirstName,” the “FirstName” column number will be 1.
►To retrieve a row by index
You can retrieve a row by using a defined index along with a comparison operator. For instance, if an index, “Last_Name” has been created on the “LastName” column for a hypothetical table, you can do the following to find a row with a “LastName” equal to “Smith.”
1 Using the row set’s RowSetMetaData object, set the access path to use index “Last_Name.”
rsmd.setAccessPath("Last_Name");
2 Create an index object using the index name. You can use the index number instead of the name.
Index index = rowset.createIndex("Last_Name");
3 Set the “LastName” column data for the index.
index.setString("LastName", "Smith");
4 Get the first row with LastName == Smith.
try {
Row row = rowset.getByIndex(Const.BTR_EQ, index);
}
5 Catch any exceptions that occur.
catch(PsqlOperationException ex) {
if (ex.getErrorCode() != 4)
throw(ex);
}
If the index allows duplicate key values, the first row which satisfies the comparison operator will be returned. The returned row becomes the current row, allowing you to retrieve the next logical row, based on the index, with the row set’s “getNext” method.
You can get the list of index names from the table’s TableMetaData object.
String [] index_names = table.getTableMetaData().getIndexNames();
Be aware that an index is not required to have a name. In that case, you should use the index number instead. Index numbers are zero based.
Insert, Update, or Delete Rows
►To insert a new row
1 First create a new Row object.
Row row = rowset.createRow();
2 Set the column data for the row.
row.setString(0, "Column0String");
row.setInt(1, 45);
row.setDouble(2, 99.99);
3 Now insert the row.
rowset.insertRow(row);
This newly inserted row becomes the current row of the row set. If you do not want the row set’s currency to be changed by the insertion, you can use the overloaded version of “insertRow” that takes a boolean argument indicating no currency change(NCC).
rowset.insertRow(row, true);
The row set’s current row will be unchanged.
►To update a row/record
1 First retrieve the row by calling one of the row set’s retrieval methods, “getNext,” “getByIndex,” and so forth.
Row row = rowset.getNext();
2 Make the changes to the column data for the row.
row.setInt(1, 45);
3 Now update the row.
rowset.updateRow(row);
Like “insertRow,” the newly updated row becomes the current row of the row set unless “no currency changed” is indicated, as shown following.
rowset.updateRow(row, true);
►To delete a row/record
1 First retrieve the row by calling one of the row set’s retrieval methods, “getNext,” “getByIndex,” and so forth.
Row row = rowset.getNext();
2 Then delete the row.
rowset.deleteRow(row);
The row to be deleted does not have to be the “current” row i.e. the row returned from the last retrieval operation. If it is not the current row, the “deleteRow” method will make the deleted row the “current” row before deleting it. After the deletion, a call to “getNext” will return the row following the deleted row.
Combine Operations into a Transaction
Transactions allow you to combine a series of operations into a single operation that will either be committed or aborted.
►To make a set of operations a transaction
You can make a set of operations a transaction by using the transaction methods of the Session class.
Here is an example of a transaction:
1 Start the transaction
try {
session.startTransaction(BTR_CONCURRENT_TRANS);
2 Perform some operations that you want to rollback if a failure occurs.
3 Attempt to commit the transaction.
session.commitTransaction(); }
catch(PsqlException ex)
4 If an error is detected, abort the transaction.
{
session.abortTransaction(); }
Transactions can be “exclusive” or “concurrent.” See the PSQL Programmer's Guide for more information about transactions.
Store and Retrieve Binary Large Objects
The setObject() and getObject() methods can be used to store and retrieve Java objects that implement the java.io.Serializable interface. The setBinaryStream() and getBinaryStream() methods can be used to store and retrieve binary data using Java InputStreams. If we have a "simple" object Employees with the following interface:
public class Employee implements java.io.Serializable
public int getID();
public void setID(int ID);
public String getName();
public void setName(String name);
public String getManagerName();
public void setManagerName();
...and we have a file that we wish to read at C:\Employees\Java Duke\report.txt, we can instantiate an Employee object, set its state with the mutator methods, and store it into the database in the Employee_Data column as well as storing the file in the Manager_Report column:
employeeObject.setName("Java Duke");
employeeObject.setID(123456789);
employeeObject.setManagerName("Big Boss");
FileInputStream managerReport = null;
try
{
managerReport = new FileInputStream(C:\Employees\Java Duke\report.txt);
}
catch(IOException ioe)
{
}
Row employeeRow = rowset.createRow();
employeeRow.setInt("ID", employeeObject.getID());
try
{
employeeRow.setObject("Employee_Data", employeeObject);
}
catch(PsqlIOException pioe)
{
}
employeeRow.setBinaryStream("Manager_Report", managerReport);
rowset.insertRow(employeeRow);
RowSetMetaData rsmd = rowset.getRowSetMetaData();
ColumnDef cdef = rsmd.getColumnDef("ID");
rsmd.addFirstTerm(cdef, Const.BTR_EQ, "123456789");
Row rowRetrieved = rowset.getNext();
try
{
Employee employeeRetrieved =
(Employee)rowRetrieved.getObject("Employee_Data");
}
catch(PsqlException pe)
{
}
InputStream reportRetrieved =
rowRetrieved.getBinaryStream("Manager_Report");
String managerName = employeeRetrieved.getManagerName();
byte file[] = new byte[reportRetrieved.available()];
reportRetrieved.read(file);
For more information, see
Binary Large Object Support.
Java Data Type Issues
This section contains information that may be of use to the PSQL Java programmer:
Binary Large Object Support
Support has been added to the Java Class Library to handle Binary Large Objects or BLOBs. BLOBs represent large (up to 4 Gigabytes) binary data, and are represented as LONGVARBINARY data types in the PSQL engine. The Java Class Library supports these data types through these methods (which are found in the PSQL.database.Row class):
public void setObject(int columnNumber, Serializable object),
public void setObject(String columnName, Serializable object),
public Serializable getObject(int columnNumber),
public Serializable getObject(String columnName),
public InputStream getBinaryStream(int columnNumber),
public InputStream getBinaryStream(String columnName),
public void setBinaryStream(int columnNumber, InputStream inStream),
public void setBinaryStream(String columnName, InputStream inStream)
where columnNumber = the zero based sequence number of a column in the row buffer. columnName = the name of a column in the row buffer. object = a serializable Java object to store into the database. inStream = a Java InputStream object used to stream bytes into the database.
The methods above can be split into two categories: those that operate on Serialized Java objects and those that operate on Java InputStream objects. Each of these categories will be discussed further in the following sections. In the following sections, examples will be assuming that a table named Employees has been created with the following schema:
Note Employee_Data and Manager_Report have the "not null" specification in their definitions because the Java Class Library does not yet support the True Null feature found in PSQL 2000 and later versions.
table Employees (SS_Num ubigint primary key,
Employee_Data longvarbinary not null,
Manager_Report longvarbinary not null)
Data inserted into a “one byte integer column” with the SQL Interface cannot be retrieved with the Java Interface
The PSQL database engines interpret one byte integers as having possible values from 0 to 255. Java interprets its byte type as signed quantities with possible values from -128 to 127.
Because of this difference in interpretation, your code must perform the procedure described here so that data is not misinterpreted.
►To convert single byte integers between Java’s interpretation and that of the PSQL Database engine
int theOneByteInt = 0;
PSQL.database.Row row = _rowset.getNext();
theOneByteInt = row.getByte("OneByte") & 0x00FF;
listCourses.add(theOneByteInt + " ");
Note All Java data types are signed.
For more information on Java data types and other Java language information, please see http://java.sun.com/docs/books/tutorial/index.html.
Sample Database Files
You can use the sample database files provided with the SDK to write Java applications. If you damage the database files because of faulty programming, you can restore them from a backup directory installed with the SDK.
►To restore the sample database files
1 Before restoring, close any programs that have the database files open, such as the sample application or your development environment.
2 Assuming you installed to the default location of file_path\PSQL\SDK, open the following folder:
file_path\psql\sdk\jcl\samples\pvideo\pvideodb\dbbackup
For default locations of PSQL files, see
Where are the PSQL files installed? in
Getting Started with PSQL.
3 Copy the files located in that directory to the next higher level folder, which in the case of step 1 is:
file_path\psql\sdk\jcl\samples\pvideo\pvideodb\
Additional Java Samples
There are additional samples included with the Java API. One sample demonstrates joining two tables provided in the SDK package. Another is a simple connection to the DEMODATA database. The final one demonstrates object serialization.
These samples are located in the following directories, (assuming you installed to the default location):
file_path\psql\sdk\jcl\samples\join
file_path\psql\sdk\jcl\samples\helloworld\
file_path\psql\sdk\jcl\samples\serialization
For default locations of PSQL files, see
Where are the PSQL files installed? in
Getting Started with PSQL. For additional PSQL Java information and to obtain future samples, see the PSQL website
.