Package org.sandev.tools.JDBC

The JDBC package holds generic SQL utilities that work with relational databases adhering to the SQL-92 standard.

See:
          Description

Class Summary
THD_JDBCPersister A Persister implementation that communicates with a database via JDBC.
THD_QueryProcessor A SandQueryMessage processor for standard SQL.
THD_ReinitConverter A destructive implementation of the Converter interface that drops any bad tables and recreates them, DELETING ALL EXISTING DATA to avoid referential integrity problems.
THD_SchemaVerifier Write table verification code to make sure the struct definitions we are working with match what is in the database.
THD_TableKeyCounter A UniqueIDManager that finds the lowest unused uniqueID across all tables in the database, and then keeps track of the next available uniqueID via a counter kept in memory.
THD_UpdateProcessor A SandUpdateMessage processor for standard SQL.
 

Exception Summary
SandSQLException Provide a standard Exception ctor signature around a SQLException.
 

Package org.sandev.tools.JDBC Description

The JDBC package holds generic SQL utilities that work with relational databases adhering to the SQL-92 standard. In most cases these utilities can be used directly by a Persister implementation for basic setup.

This package does not make use of specific database extensions, and avoids doing work that is not generally applicable. It makes every effort to depend only on java.sql, so that the classes can be used under a variety of circumstances.

General assumptions and approach:

Inheritance is handled through separate tables for the parent and child, with the rows being joined on the uniqueID. We do not currently squish @abstract superclasses and classes together, since inheritance paths can change during initial development. This is a typical point of optimization in a customized production Persister implementation.

The uniqueID is the primary key. Management of primary keys is the responsibility of the Persister implementation and is not covered here in the general SQL utilities. Since primary key management can be tied to transaction management, this is also left to the Persister.

Non-reference arrays are converted to CSV and stored as a single VARCHAR. This emphasizes transaction simplicity over query processing. This may not be appropriate in a production environment, in which case this behavior needs to be overridden.

Query processing is subject to the limitations of the underlying database. Depending on the struct definitions and the depth of specification in the query message, it is possible to cause more joins and/or subselects than the database can handle. In that case the query will fail.

Processing of Match Expressions is limited to syntactic manipulation. If the resulting expression fails, then the query fails.

Drilldown matching is implemented via subselects using aliased table names.

Example primary key implementation:

For structs that do not inherit, and which do not contain any arrays of references, the uniqueID is declared as an IDENTITY column. This enables add operations to occur within a single database transaction, since a primary key does not need to be retrieved first. Using a struct model that takes advantage of this situation is strongly suggested for high volume persistence.

Note that the optimized add operation relies on none of the struct references being updated simulataneously. For example if you are adding a new Order, which references User, don't be adding the User as part of the same transaction. The AggregateUpdate processing will complain and the update will fail.

Steps for processing a transaction:

There are potentially more efficient ways of resolving and retrieving non-identity primary key values. This is another point of optimization in a customized Persister implementation, tailored for what is supported by a specific database.

Alternative primary key implementation:

If only one DataManager node will be deployed, then the Persister implementation may allocate new primary keys using in-memory counters. The counters are initialized at startup from the current maximum value used in the tables. This is both fast and efficient, however it is clearly unsafe to use this approach for deployments with more than one DataManager node.

One way to adapt this approach to multiple DataManager nodes is to have each DataManager reserve a chunk of IDs for use. By reserving the chunk in a transaction, and keeping track of these allocations in the database, each DataManager node can allocate IDs from its in-memory pool without risk of collision.