Using the Java Binding
This chapter provides detailed examples of how to use the Caché Java binding within a Java application.
Generating Java Proxy Classes
To create a Java projection of a Caché class, specify that the Caché class automatically creates a Java class whenever it is compiled. To do this, add a projection definition to the Caché class. This projection definition specifies that the Class Compiler also generates Java code for this class whenever the class is compiled.
The process is as follows:
-
In the Studio, establish a connection to the namespace of the class and open the class.
-
Add a Java projection definition to the class using the Studio's New Projection Wizard: Invoke the New Projection choice from the Add submenu in the Class menu.
-
After choosing a name for the projection, specify its type as %Projection.JavaOpens in a new tab.
-
Enter the ROOTDIR parameter, which specifies the directory where the generated Java class will be written.
-
Compile the Caché class. This generates the Java projection of the class, whether compilation occurs in the Studio or from the Terminal command line.
At this point, you have added a line of code to your class definition similar to the following:
Projection PrjName As %Projection.Java;
where PrjName is the name of the projection.
You can now compile the Java class using either the javac command or some other tool. Whenever you modify and compile the Caché, its projection automatically updates the projected Java class.
You should never modify the generated Java Projection code directly, or attempt to add custom Java connection code, since this can have serious unintended consequences. The generated code depends on InterSystems internal code that may be changed without notice.
Generating a Java Class from a Caché Class
To use a Caché class from a Java client, generate a Java class to run on the client side.
You can specify that a Caché class automatically creates a Java class whenever it is compiled; to do this, add a projection definition to the Caché class. This projection definition specifies that the Class Compiler also generates Java code for this class whenever the class is compiled. For more information, see the Projections chapter in Using Studio.
To generate a Java class, do the following:
-
In the Studio, establish a connection to the class' namespace and open the class. For instance, to use the Person sample class, connect to the SAMPLES namespace, open the Person class in the Sample package.
-
Add a Java projection definition to the class using the Studio's New Projection Wizard: Invoke the New Projection choice from the Add submenu in the Class menu.
-
After choosing a name for the projection, specify its type as %Projection.JavaOpens in a new tab.
-
For the ROOTDIR parameter, enter the directory where the generated Java class will reside.
-
On the last screen of the New Projection Wizard, click Finish. At this point, the wizard has added a line of code to your class definition similar to the following:
Projection MyProjection As %Projection.Java(ROOTDIR="C:\temp\");
-
Compile the Caché class. This generates the Java projection of the class, whether compilation occurs in the Studio or from the Terminal command line.
-
Compile the Java class. You can do this either using the javac command or any other tool that you use for compiling Java classes.
Using Objects
A Caché Java binding application can be quite simple. Here is a complete sample program:
import com.intersys.objects.*;
public class TinyBind {
public static void main( String[] args ) {
try {
// Connect to the Cache' database
String url="jdbc:Cache://localhost:1972/SAMPLES";
String username="_SYSTEM";
String password="SYS";
Database dbconnection =
CacheDatabase.getDatabase (url, username, password);
// Create and use a Cache' object
Sample.Person person = new Sample.Person( dbconnection );
person.setName("Doe, Joe A");
System.out.println( "Name: " + person.getName() );
}
// Handle exceptions
catch (CacheException ex) {
System.out.println( "Caught exception: " +
ex.getClass().getName() + ": " + ex.getMessage() );
}
}
}
This code performs the following actions:
-
Imports the com.intersys.objects.* packages.
-
Connects to the Caché database:
-
Defines the information needed to connect to the Caché database.
-
Creates a Database object (dbconnection).
-
Creates and uses a Caché object:
-
-
Uses the Database object to create an instance of the Caché Sample.PersonOpens in a new tab class.
-
Sets the Name property of the Sample.PersonOpens in a new tab object.
-
Gets and prints the Name property.
-
Performs standard exception handling.
-
The following sections discuss these basic actions in more detail.
Creating a Connection Object
The CacheDatabase class is a Java class that manages a connection to a specific Caché server and namespace. It has a static method, getDatabase(), for creating a connection. This method returns a connection to a Caché database that is derived from the Database interface.
To establish a connection:
-
Import the appropriate packages, which will always include the com.intersys.objects.* packages (which include CacheDatabase):
import com.intersys.objects.*;
-
Next, declare and initialize variables for use in establishing the connection:
Database dbconnection = null; String url="jdbc:Cache://localhost:1972/NAMESPACE_NAME"; String username="_SYSTEM"; String password="sys";
The getDatabase() method establishes a TCP/IP connection from the Java client to the Caché server. The method takes three arguments, where the first includes a specified IP address (here, the local host, 127.0.0.1), a specified port number (here, 1972), and to a specified namespace (here, SAMPLES); the second and third arguments are the username and password, respectively, for logging into the server.
It returns a Database object, here called dbconnection. You can then use getDatabase() to establish the connection:
dbconnection = CacheDatabase.getDatabase(url, username, password);
It is also possible to create a connection and run queries through the Java-standard JDBC connection interface. For details, see Using DriverManager to Connect in Using Caché with JDBC.
Creating and Opening Proxy Objects
The following code attempts to connect to the local Caché server:
String url="jdbc:Cache://localhost:1972/SAMPLES";
String username="_SYSTEM";
String password="sys";
//...
dbconnection = CacheDatabase.getDatabase (url, username, password);
Next, the program uses standard Java functionality to prompt the user for an ID to open and to get that value. Once there is an ID, the next step is to open the specified object:
person = (Sample.Person)Sample.Person._open(dbconnection, new Id(strID));
This code invokes the _open() method of the Person object in the Sample package. This method takes two arguments: the database that contains the object being opened, and the ID of the object being opened. The value being returned is narrowed (cast) as an instance of Sample.PersonOpens in a new tab because _open() is inherited from the Persistent class and, hence, returns an instance of that class.
Using Methods and Properties
Once the object is open, the program displays the value of the object's Name property.
System.out.println("Name: " + person.getName());
Note that, unlike on the Caché server, references to object properties are through the get() and set() methods, rather than through direct references to the properties themselves.
Next, it displays the value of the City property and then gives the City property a new value:
System.out.println("City: " + person.getHome().getCity());
person.getHome().setCity("Ulan Bator");
The lines of code that manipulate the City property demonstrate the observation and modification of the properties of an embedded object. If a property is an object (such as the Home property), then it has its own properties (such as the City property) with accessor methods. You can invoke these methods using cascading dot syntax.
Saving and Closing
Having given the City property a new value, the application then saves the object, displays the value, closes the object, de-assigns it, and then shuts itself down.
person._save();
// Report the new residence of this person */
System.out.println( "New City: " + person.getHome().getCity());
// * de-assign the person object */
dbconnection.closeObject(person.getOref());
person = null;
// Close the connection
dbconnection.close();
Before closing a connection, it is important to call closeObject() on all objects that use the connection. Failure to do so may compromise the integrity of your data on the server. Objects are opened with a default concurrency value of 1, meaning that a read lock is acquired if the class uses more than one data node (see “Object Concurrency” in Using Caché Objects).
You must also call close() on all connection instances before they go out of scope. Failure to do so can cause memory leaks and other problems.
A Sample Java Binding Application
SampleApplication.java is a simple Java program that connects to the Caché SAMPLES database, opens and modifies an instance of a Sample.PersonOpens in a new tab object saved within the database, and executes a predefined query against the database. This application is invoked from the operating command line, reads an object id value from the command line, and writes output using the Java system.out object. This example assumes that Caché and Java are running on a Windows machine.
SampleApplication.java is located in the <cachesys>/dev/Java/Samples directory (see Default Caché Installation Directory in the Caché Installation Guide for the location of <cachesys> on your system). In the Samples directory, compile the program:
javac SampleApplication.java
And then run it:
java SampleApplication
When executed, this program yields results such as:
C:\java> java SampleApplication
Enter ID of Sample.Person object to be opened: 1
Name: Isaacs,Sophia R.
City: Tampa
New City: Ulan Bator
C:\java>
Here is the complete Java source for the sample application:
/*
* SampleApplication.java
*/
import java.io.*;
import java.util.*;
import com.intersys.objects.*;
public class SampleApplication {
public static void main(String[] args){
Database dbconnection = null;
String url="jdbc:Cache://localhost:1972/SAMPLES";
String username="_SYSTEM";
String password="sys";
ObjectServerInfo info = null;
Sample.Person person = null;
try {
// Connect to Cache on the local machine, in the SAMPLES namespace
dbconnection = CacheDatabase.getDatabase (url, username, password);
// Open an instance of Sample.Person,
// whose ID is read in from the console
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
System.out.print("Enter ID of Person object to be opened:");
String strID = br.readLine();
// Use the entered strID as an Id and use that Id to
// to open a Person object with the _open() method inherited
// from the Persistent class. Since the _open() method returns
// an instance of Persistent, narrow it to a Person by casting.
person = (Sample.Person)Sample.Person._open(dbconnection, new Id(strID));
// Fetch some properties of this object
System.out.println("Name: " + person.getName());
System.out.println("City: " + person.getHome().getCity());
// Modify some properties
person.getHome().setCity("Ulan Bator");
// Save the object to the database
person._save();
// Report the new residence of this person */
System.out.println( "New City: " + person.getHome().getCity());
/* de-assign the person object */
dbconnection.closeObject(person.getOref());
person = null;
// Close the connection
dbconnection.close();
} catch (Exception ex) {
System.out.println("Caught exception: "
+ ex.getClass().getName()
+ ": " + ex.getMessage());
}
}
}
Using Streams
Caché allows you to create properties known as streams that hold large sequences of characters, either in character or binary format. Character streams are long sequences of text, such as the contents of a free-form text field in a data entry screen. Binary streams are usually image or sound files, and are akin to BLOBs (binary large objects) in other database systems.
The process for using a stream in Java is:
-
When creating a Java client, define and initialize a variable of the appropriate type – either GlobalBinaryStream or GlobalCharacterStream. For instance, if you are using a character stream, define a variable such as:
com.intersys.classes.GlobalCharacterStream localnotes = null;
You can then read content from an instantiated class' stream:
localnotes = myTest.getNotes();
-
Once you have a local copy of the stream, you can read from it
IntegerHolder len = new IntegerHolder( new Integer(8)) ; while (len.value.intValue() != 0 ) { System.out.println( localnotes._read( len) ); } ;
The _read() method's argument is an integer hold specifying how many characters to read and its return value is the characters that it reads. It also places the number of characters successfully read (whether the number specified or fewer) in the integer hold variable that was its argument.
When you are writing to or reading from a stream, Caché monitors your position within the stream, so that you can move backward or forward.
The Caché stream classes are GlobalBinaryStream and GlobalCharacterStream in the com.intersys.classes package. The basic methods involving streams are:
Adding content
Adding content (character streams only)
Reading content
Reading content (character streams only)
Going to the stream's end
Going to the stream's beginning
Check if the current position is at the end of the stream
Getting the size of the stream
Checking if the stream has content or not
Erasing the stream's content
Using Queries
A Caché query is designed to fit into the framework of JDBC but provides a higher level of abstraction by hiding direct JDBC calls behind a simple and complete interface of a dynamic query. It has methods for preparing an SQL statement, binding parameters, executing the query, and traversing the result set.
Class Queries
Caché allows you to define queries as part of a class. These queries are then compiled and can be invoked at runtime.
To invoke a predefined query, use the CacheQuery class:
-
Establish a connection to a Caché server (see Creating a Connection Object for details on this process).
-
Create an instance of a CacheQuery object using code such as:
myQuery = new CacheQuery(factory, classname, queryname);
where factory specifies the existing connection, classname specifies the class on which the query is to be run, and queryname specifies the name of the predefined query that is part of the class.
-
Once you have instantiated the CacheQuery object, you can invoke the predefined query:
java.sql.Result ResultSet = myQuery.execute(parm1);
This method accepts up to three arguments, which it passes directly to the query; if the query accepts four or more arguments, you can pass in an array of argument values. The method returns an instance of a standard JDBC ResultSet object.