Chapter 4
Multi-user and remote access to DataStores

DataStore applications are not limited to accessing local DataStore files. Through the use of a DataStore JDBC server, you can access DataStores from other machines. The DataStore JDBC server also enables multi-user access. Access requests from numerous clients are handled by the server process.

Using the JDBC driver for remote access

DataStore's Type 4 (direct pure Java) JDBC driver com.borland.datastore.jdbc.DataStoreDriver may be used to access both local and remote DataStore files. While the URL for local connections is:

jdbc:borland:dslocal:<filename>

the URL for remote connections is:

jdbc:borland:dsremote://<hostname>/<filename>

A DataStore JDBC server process must be running on the <hostname> machine. Communications between the client application and the DataStore JDBC server use port 2508 by default. You can change the port number when starting (or restarting) the server, and on the client by getting the connection with extended properties. For example, if you want to access the DataStore file c:\someApp\ecom.jds on the computer mobile.mycompany.com through port 9876, you might do something like:

Class.forName( "com.borland.datastore.jdbc.DataStoreDriver" );
java.util.Properties info = new java.util.Properties();
info.setProperty( "user", "MyUserName" );
info.setProperty( "port", "9876" );
Connection con = DriverManager.getConnection(
    "jdbc:borland:dsremote://mobile.mycompany.com/c:/someApp/ecom.jds", info );

For more information on connection properties, see the DataStoreDriver component "Driver properties," in Chapter 3, "datastore.jdbc package," of the DataExpress Component Library Reference.

Other than these differences, a remote JDBC connection operates in much the same manner as a local JDBC connection, from the client application's perspective. For more information, see "Using JDBC for data access".

Because DataStores accessed via a remote connection are potentially DataStores accessed by multiple users, you must consider some concurrency issues, as detailed in "Avoiding blocks and deadlocks".

Running the DataStore JDBC server

To access DataStore files through a remote JDBC connection, a DataStore JDBC server must be running on a machine that has local access to those files. This could be the actual machine that contains the files, or a machine that has direct network access to DataStore files on a networked drive.

For development purposes, you can start the server from the JBuilder menu. Select Tools|DataStore Server. This starts a minimized server process. Restoring the server reveals a simple UI:

Figure 4.1   DataStore JDBC server

From the menu, you can get help, terminate the server, or run the DataStore Explorer (for more information on the DataStore Explorer, see "Using the DataStore Explorer"). You can also change a few items in the server's configuration.

Reconfiguring the server

You cannot change the server configuration while it is running. The
File|Shutdown menu option will stop the actual server process. All connections will be closed in an orderly fashion.

Once the server has stopped, you can use the File|Port Number and
File|Temp Directory menu options to change those aspects of the server. Then use File|Startup to restart the server process.

Deploying the DataStore JDBC server

Once an application goes into real-world use, you will need to deploy the DataStore JDBC server to a server machine.

Packaging the server

The JARs you need to run the server depends on whether you want the GUI. Without the GUI, you only need:

With the GUI you will need, in addition to the JARs already listed:

If you want the server's online help to be available, you will also need to copy the help JARs from the /doc directory. The help files for the DataStore JDBC server are in jb_ui.jar. The JDataStore Programmer's Guide is in jb_dspg.jar.

Starting the server

To start the server, you must add the necessary JARs to the classpath. The main class for the DataStore JDBC server is com.borland.dbtools.dsserver.Server, so the command line for starting the server with default options is:

java com.borland.dbtools.dsserver.Server

You may also specify the options listed in the following table:

Table 4.1   DataStore JDBC server startup options 

Option

Description

-port=<number>

The port to listen to. Default: 2508

-ui=<uiType>

The look and feel of the UI. One of the following:

  • windows
  • motif
  • metal
  • none
  • <LookAndFeel class name>

-temp=<dirName>

The directory to use for all temporary files

-doc=<helpDir>

The directory that contains online help files

-?
-help

Displays a message listing these options

If you do not use the -ui option (or specify none), the server will start as a console application. Without a UI, you will not be able to reconfigure the server or launch the DataStore Explorer. To halt the server, use the appropriate operating system or shell action. For example, when running in the foreground, press Ctrl+C; when running in the background on Unix, use the kill command.

Creating custom JDBC servers

The DataStore JDBC server that comes with JBuilder provides remote access to DataStore files. You can create custom servers with additional functionality. For example, because the server will probably be running all the time, you can add a maintenance thread that backs up files at the same time every night. Another example would be to add the ability to retrieve file streams stored in a DataStore; file streams are not accessible through JDBC.

The core of any DataStore JDBC server is the com.borland.datastore.jdbc.DataStoreServer thread. In addition to anything else it might do, the server runs an instance of this thread. For more information, see the datastore.jdbc package in the DataExpress Component Library Reference.

Multi-user transaction issues

Multi-user access introduces potential problems with transactions, ranging from decreased performance to deadlock. (These same issues may also be a problem for complex single-user applications that use multiple transactional connections.) Avoiding or minimizing these problems requires an understanding of the transaction mechanisms employed by the DataStore.

Transaction isolation level

The DataStore uses both stream locks and row locks to manage access to resources. Stream locks provide the strongest possible transaction isolation level, designated by java.sql.Connection.TRANSACTION_SERIALIZABLE. It prevents:

Serializable transactions guarantee that the data is consistent during the transaction's lifetime.

Row locks are used to enforce the next strongest transaction isolation level, designated by java.sql.Connection.REPEATABLE_READ. It allows phantom reads, but prevents nonrepeatable and dirty reads. This level of isolation allows more concurrent access at the expense of absolute consistency.

Avoiding blocks and deadlocks

A connection normally requires a lock to either read from or write to a stream or row, and can be blocked by another connection that's either reading or writing. You can prevent blocks in two ways:

Conserving write transactions

Connections should use "burst" writes; that is, accumulate changes, then when necessary start a transaction, immediately write those changes, and commit them. This happens to be the preferred model for most database servers, and is the model used by DataExpress in its provider/resolver paradigm.

Using read-only transactions

Read-only transactions are not blocked by writers or other readers, and because they don't get locks, they never block other transactions.

To make JDBC connections use read-only transactions, set the readOnly property of the java.sql.Connection object (returned by the java.sql.DriverManager.getConnection and com.borland.dx.dataset.sql.Database.getJdbcConnection methods) to true. When using DataStoreConnection objects, set the readOnlyTx property to true before opening the connection.

Read-only transactions work by simulating a snapshot of the DataStore. Only data from transactions committed at the point the transaction started are seen in this snapshot (otherwise, the connection would have to see if there are pending changes and roll them back whenever it accesses the data). A snapshot is taken when the DataStoreConnection is opened, and is refreshed every time its commit method is called.

Detecting blocks and deadlocks

The DataStoreConnection has a lockWaitTime property that defaults to ten seconds. If a lock cannot be secured in that time, the transaction will abort with an informative exception. This mechanism applies to both long-duration blocks and deadlocks.

When a transaction aborts, it holds on to its locks. You must decide whether or commit or roll back the transaction, or retry the lock. Note that in a deadlock situation, the second connection will continue to be deadlocked until you end the transaction (by committing or rolling back), or that second transaction also times out.