PriDE Introduction


  • What is PriDE?
  • Initialization of PriDE
  • Mapping of data
  • Generation of entity classes
  • Standard operations
  • Transaction handling
  • Error handling
  • Accessing multiple databases
  • Prepared Statements
  • Stored Procedures
  • Further reading

  • What is PriDE?

    PriDE stands for 'Primitive Database Environment' and is a simple O/R mapper for relational databases. O/R mapping is the wide-spread approach to map records of a relation SQL database to objects of an object-oriented application. The application should operate on its entities as object-oriented as possible, not regarding that some of them come from a database or must be saved in one. PriDE provides an infrastructure to In principle, the database access is performed via JDBC, version 2.0 and uses plain standard SQL for communication. Therefore, PriDE is basically suitable for any databases supporting SQL 92 syntax. PriDE was designed for usage in J2SE and J2EE environments and is used identically in both except some initialization operations. The framework follows a very pragmatic approach to provide basic development support quickly and easily. It does not claim to conform with established persistence management standards but follows common design patterns and proved to be suitable in mission-critical projects over many years now. The feature list PriDE-Features.html may help to figure out wether PriDE meets the requirements of individual development projects, and allows to roughly compare this toolkit with existing well-known O/R mapping products and standards like EJB CMP 2.0 and JDO. PriDE is probably the smallest O/R mapping framework available and thus meets the very key requirement for a persistence manager:

    If persistence management is a critical problem,
    the persistence manager must be an uncritical solution



    PriDE is so small that it can actually be understood in any single line of its code, providing the developer full control over how data is exchanged between an SQL database and a Java application. Its simplicity is based on a few important (and partly unpopular) design principles which are explained in PriDE-Inside.html. The following examples gives a short impression about how PriDE works in an application:


    // Initialization
    DatabaseFactory.setResourceAccessor(myAccessor);
    DatabaseFactory.setDatabaseName(myDatabase);

    // Record creation
    Customer customer = new Customer(01, "lessner");
    customer.create();

    // Single record retrieval
    customer = new Customer(01);
    customer.find();

    // Record update
    customer.setName("Lessner");
    customer.update();

    // Record deletion
    customer.delete();

    // Record query
    ResultIterator iter = customer.query("name");
    do {
        System.out.println(customer.getId());
    } while(iter.next());

    // End of transaction
    DatabaseFactory.getDatabase().commit();

    The operations above are explained in the following sections step by step. Basically, the application just deals with the following classes from the PriDE library.

    The class Database makes up the heart of the framework although its operations are usually not called directly. In general, there are the following different levels of abstraction, the user may work on:

    Initializing PriDE

    As already mentioned above, the database access is performed through the central class Database. An instance of this class can be fetched from the DatabaseFactory calling the static function getDatabase(). This requires a preceeding initialization of the DatabaseFactory to have suitable resources available like The link to physical resources is provided by implementations of the interface ResourceAccessor following a strategy pattern for database access. PriDE provides the classes ResourceAccessorJ2SE, ResourceAccessorWeb and ResourceAccessorJ2EE, making the framework suitable for standard environments as well as servlet engines or application servers. The following example demonstrates the initialization of PriDE which has to be performed only once per application (e.g. in the main() function):


    Properties props = new Properties();
    props.setProperty(ResourceAccessor.Config.DRIVER,"sun.jdbc.odbc.JdbcOdbcDriver"); 
    props.setProperty(ResourceAccessor.Config.USER, "sa"); 
    props.setProperty(ResourceAccessor.Config.PASSWORD, ""); 
    props.setProperty(ResourceAccessor.Config.LOGFILE, "sql.log"); 

    ResourceAccessorJ2SE accessor = new ResourceAccessorJ2SE(props);
    DatabaseFactory.setResourceAccessor(accessor);
    DatabaseFactory.setDatabaseName("jdbc:odbc:mydb");

    The classes ResourceAccessorWeb and ResourceAccessorJ2EE assume a database being available as a DataSource by a JNDI lookup. The initialization is very similar and may look like this:


    Properties props = new Properties();
    props.setProperty(ResourceAccessor.Config.USER, "sa"); 
    props.setProperty(ResourceAccessor.Config.PASSWORD, ""); 
    props.setProperty(ResourceAccessor.Config.LOGFILE, "sql.log"); 

    ResourceAccessorJ2EE accessor = new ResourceAccessorJ2EE(props);
    DatabaseFactory.setResourceAccessor(accessor);
    DatabaseFactory.setDatabaseName("jdbc.mydb");

    When using an application server, there is a suitable place to be found for such a static initialization. The pattern collection includes a general recommendation for that case. The usage of multiple databases is explained below. A complete list of all available configuration properties for the pre-defined resource accessor classes is listed at PriDE-Config.html.
     

    Mapping of data

    PriDE basically supports the mapping of records in a database to Java objects and vice versa. The results of a query are iteratively copied from a JDBC ResultSet in a Java entity object of a corresponding type. For database manipulation of the database, the data is extracted from an entity object to dynamically assemble an SQL statement from it. The mapping details are fetched from a descriptor and per reflection from the entity object. The class RecordDescriptor is used to provide mapping descriptions. The following example illustrates the usage of descriptor classes:

    For a database table described by


    CREATE TABLE customer
    (
      id          INTEGER      NOT NULL,
      name        VARCHAR(20)  NOT NULL,

      CONSTRAINT PK_customer PRIMARY KEY (id)
    );

    a corresponding Java entity type may be defined like this:


    class Customer extends MappedObject {
        protected static RecordDescriptor red = new RecordDescriptor
            (Customer.class, "customer", null, new String[][] {
                { "id",      "getId",      "setId" },
                { "name",    "getName",    "setName" },
            });

        private int id;
        private String name;

        public int getId() { return id; }
        public String getName() { return name; }

        public void setId(int val) { id = val; }
        public void setName(String val) { name = val; }

        // to be continued
    }

    The desriptor red describes the mapping of attributes from the Java class Customer to the columns of the database table customer. The constructor gets passed the following specifications

    1. The Java class the descriptor refers to
    2. The database table name, the descriptor maps the Java objects to
    3. A base class descriptor if existing
    4. A 2-dimensional string array with one entry per attribute/column:
    The mapping of an entity class to a database table is based on the existence of public read and write methods for all attributes which are supposed to be persisted. An entity class' mapping descriptor may simply be defined as a static class variable of the entity class itself. It is recommended to define the descriptor as a protected member so that it can be referred from derived entity types. Using the base class MappedObject is not mandatory here, but it provides a set of ready-to-use database read and write operations. Synchronization with the database is not performed automatically but requires explicite calls of appropriate member functions. The following extension of the class above shows how to use the standard operations provided by MappedObject to define a persistence constructor and a reconstructor. A persistence constructor immediately stores an entity in the database on construction. A reconstructor restores an entity's data by key from the database when the entitiy is created. The notions persistence constructor and reconstructor are used with the meaning above in this documentation.


    class Customer extends MappedObject {
        // attributes and descriptor like above

        // provide descriptor access
        protected RecordDescriptor getDescriptor() { return red; }

        // typical persistent construction
        public Customer(int id, String name) throws SQLException {
            setId(id);
            setName(name);
            create();
        }

        // typical reconstruction
        public Customer(int id) throws SQLException {
            setId(id);
            find();
        }
    }

    Find, update and deletion functions for an existing record require a primary key to identify the record in the database. By default, the methods MappedObject.update() and MappedObject.delete() assume that the first database field in the entity's mapping descriptor makes up the primary key. If this rule is not applicable, an alternative specification can be provided by overriding the function getKeyFields(). The following example illustrates the required extensions for such a case:


    class Customer extends MappedObject {
        // attributes and descriptor like above

        // primary key
        protected static String[] pk = { "id" };

        // primary key access function
        public String[] getKeyFields() { return pk; }
    }

    Generating entity types

    Entity types with a canonic 1:1 mapping for existing database tables can be generated by a simple generator. The generator is located in package  de.mathema.pride.util and is called by the following command:


    java de.mathema.pride.util.CreateTableTemplate \
         <driver> <db> <user> <password> <table>[,<table2>,...] [ <class name> ] [-h | -b | <bean class name>] [ <base class name> ]

    When using the Eclipse 2.x platform, the generatror can also be run from within the IDE by installing the PriDE plugin.

    Standard operations

    Entity types derived from MappedObject, inherit a set of standard operations for creating, reading, updating and deleting records in the database. The same operations are available in the ValueObjectAdapter suitable for mapping pure value objects like ordinary Java Beans instead of itself. The  most important operations are roughly explained in a few examples below.

    Creation

    The member function create() allows to create a database record. It reads the contents of an entity object and assembles an SQL insert statement according to the object's mapping descriptor to transmit the data to the database. PriDE's internal operations on record creation are illustrated in a UML diagramm (sequence view). Before calling the operation, the object's attributes must be reasonably initialized e.g. concerning Entity types created by PriDE's generator tool (s.a.) for example, map database columns to primitive data types if the columns don't accept NULL values. This technically elliminates the risk of setting these attributes to null and thus elliminates a typical source for mistakes. Nevertheless it is the application's responsibility to also fulfill all other constraints as well. If an error occurs on record creation, the creation method forwards any  SQLExceptions to the caller. Any other exceptions are handled by a central error handler (see chapter Error handling).
    The following examples demonstrates the creation of an entity of type Customer according to the examples above.


    try {
        new Customer(01, "Lessner");
    }
    catch(SQLException x) {
        x.printStackTrace();
    }

    Read/Query

    Reading a particular instance by its primary key is performed by member function find(). Overriding method getKeyFields() allows an entity type to declare the database fields making up the primary key. By default, the very first attribute in the entity's RecordDescriptor is interpreted as primary key. This is often a good guess as many databases use artificial technical keys for all tables.
    Reading a record from the database usually follows a query-by-example approach. An entity is created and the primary key attributes are set. Afterwards the find() method is called which assembles an SQL query from an AND conjunction of all key attribute values. The result is put in the same entity i.e. all other but the key attributes are supplemented from the query's result set. PriDE's internal operations on record retrieval are illustrated in a UML diagramm (sequence view). As in all other database operations, any database errors are forwarded as SQLExceptions to the caller and all other exceptions are handled by a central error handler. The following example shows how to read an entity of type Customer.


    try {
        Customer c = new Customer();
        c.setId(01);
        c.find();
        System.out.println(c.getName());
    }
    catch(NoResultsException nrx) {
        System.out.println("no such customer");
    }
    catch(SQLException sqlx) {
        sqlx.printStackTrace();
    }

    An unsuccessful database query in this connection must normally be understood as an error condition and is therefore reported by the find() function as a NoResultsException. As it is a derivation from SQLException, appropriate catch blocks must always be defined before the one for SQLExpression. However, a special treatment can often be omitted anyway. Querying single objects by key attributes is often encapsulated in the entity types' constructors as shown above for Customer class' reconstructor. The following example shows the same query operation as above in a slightly simplified form:


    try {
        Customer c = new Customer(01);
        System.out.println(c.getName());
    }
    catch(SQLException sqlx) {
        sqlx.printStackTrace();
    }

    A query-by-example can also be performed on any other combination of columns, using function find(String[]). The entity always gets passed the data of the very first matching database record. If all matching records are of interest, the methods query(String[]) or wildcard(String[]) must be used. They also store the very first matching record's data in the entity but additionally return a ResultIterator, which allows iterative processing of all matching records. The following example shows an iteration over all Customers with a name starting with 'L':


    try {
        Customer c = new Customer();
        c.setName("L%");
        ResultIterator iter = c.wildcard(new String[] { "name" });
        do {
            System.out.println(c.getName());
        }
        while(iter.next());
    }
    catch(NoResultsException nrx) { /* Nothing to do */ }
    catch(SQLException sqlx) {
        sqlx.printStackTrace();
    }

    The functions toArray() and toArrayList() allow to fetch the complete query result as a whole, which is comfortable for the processing of small result  sets. Additional variants for complex query operations can be found in the pattern overview.

    Updates

    The method update() allows updating of existing database records. Similar to the record creation, the attributes of an entity object are read to assemble an SQL update statement. As a difference, only those attributes are updated which are not part of the primary key. The latter ones are used to assemble a where-clause to identify the record in the database. PriDE's internal operations on record update are illustrated in a UML diagramm (sequence view). The following example shows an update for the famous Customer class from the examples above.


    try {
        Customer c = new Customer(01);
        System.out.println(c.getName());
        c.setName(c.getName() + "foo");
        c.update();
    }
    catch(SQLException sqlx) {
        sqlx.printStackTrace();
    }

    Like for query operations, the method update(String[]) also allows updating multiple records in one statement by specifying another combination of identifying attributes. Of course, this function must only be used with great care. In addition it is often required to update only selected attributes when addressing mutiple records. Therefore the function update(String[], String[]) is usually of more interest, as it allows to specifiy both, the key fields and the fields to update.

    Deletion

    Similar to update calls, the function delete() uses an entity's primary key attributes as identification of the record which should be removed from the database. Also deletion can be performed on multiple objects in one operation, using function delete(String[]). PriDE's internal operations on record deletion are illustrated in a UML diagramm (sequence view). The following example demonstrates record deletion for a Customer entity:


    try {
        Customer c = new Customer();
        c.setId(01);
        c.delete();
    }
    catch(SQLException sqlx) {
        sqlx.printStackTrace();
    }

    Most of the operations demonstrated above cause a database manipulation. If the database connection in use does not apply auto commitment, the operations require an explicite commitment of the current transaction before the changes become durable. The ResourceAccessorJ2SE and ResourceAccessorJ2SE switch off the default auto commitment used by most database drivers. The following section gives a short insight to PriDE's transaction handling.

    Transaction handling

    Explicit transaction handling should only be performed in J2SE applications. In application servers it is recommended to use container-managed transactions, to keep the responsibility for transaction handling out of the application code. Even in J2SE applications, it is helpful not to spread explicit transaction handling all over the code but keep it in one dedicated layer of a structured software architecture.

    Running transactions in J2SE applications are terminated by the functions Database.commit() and Database.rollback(). The corresponding functions in  MappedObject resp. ValueObjectMapper have the same meaning, i.e. they are not limited to the object they are applied to. The termination of a transaction refers to the database connection of the current thread. A unique association of database connections to application threads is managed transparently by the ResourceAccessorJ2SE when calling Database.getConnection(). An explicit invokation of this function is not required as it is implicitely called as soon as there is any database interaction performed. The following code is a minimal example for transaction handling in PriDE:


    Customer customer = new Customer(01, "Lessner"); // Creation
    customer = new Customer(01); // Retrieval
    System.out.println(customer.getName());
    DatabaseFactory.getDatabase().commit();
    // Alternatively: customer.commit();

    Components which must be informed about the termination of transactions can be registered as TransactionListener. This is required e.g. to synchronize database-related caches. The following example illustrates the definition of such a listener:


    class TxListener implements TransactionListener {
        public TxListener() {
            DatabaseFactory.getDatabase().addListener(this);
        }
        public void commit(TransactionEvent e) { // Do something }
        public void rollback(TransactionEvent e) { // Do something }
    }

    Error handling

    By default PriDE provides a central error handling which is used for all exceptions not being of type java.sql.SQLException. SQLExceptions propagated to the caller who is supposed to handle it reasonably and potentially case-specific. Any other exceptions indicate bugs in PriDE or application errors which usually don't allow to continue the work in a reasonable way (e.g. a miss-defined RecordDescriptor may cause a java.lang.IllegalAccessException to be thrown). A centralized handling of these cases keep the throws-clauses of the PriDE API clear.

    Objects of type Database get passed an ExceptionListener in the constructor which implements the methods process() and processSevere(). The method process() is called in case of an error which cancels the current operation but does not compromize the application as a whole. The method  processSevere() handles exceptions, which indicate a severe error in PriDE or the application code with the risk of loosing system integrity. PriDE currently only uses processSevere() inside. the function is not supposed to return control to the caller. i.e. it must either terminate the application or throw a runtime exception.

    The ExceptionListener to use can be changed with the function DatabaseFactory.setDefaultExceptionListener(). The standard handling prints a stack trace of the processed exception and terminates the application with exit code 1. Function process() performs no handling at all, i.e. the passed exception is rethrown. Substituting this listener is reasonable if the default behaviour does not meet the expectations in a particular project, e.g. if the handling must be unified with an application-specific scheme for logging or exception propagation and reporting.

    Accessing multiple databases

    The section above concerning PriDE initialization already explained the operations required to make a single database accessible. Multiple databases or different types of connections to the same database can be added with the context concept. A context is identified by a name and makes up the combination of a database name, a suitable ResourceAccessor and optionally an associated ExceptionListener. The initialization operations introduced so far, defined the details of the default context which is used, if nothing else is specified. The function DatabaseFactory.setContext(String) allows to change the context. The first call of setContext with a so far undefined context name causes an implicit creation of a new context which can be initialized in the same way as already explained. The following example demonstrates a simple creation and usage of multiple contexts:


    // Initialization of the default context
    ResourceAccessorJ2SE accessor = new ResourceAccessorJ2SE
        ("sun.jdbc.odbc.JdbcOdbcDriver", "sa", "", "sql.log");
    DatabaseFactory.setResourceAccessor(accessor);
    DatabaseFactory.setDatabaseName("jdbc:odbc:mydb");

    // Creation and initialization of a second context
    // See ResourceAccessorJ2SE Javadocs for performance considerations
    DatabaseFactory.setContext("second");
    DatabaseFactory.setResourceAccessor(accessor);
    DatabaseFactory.setDatabaseName("jdbc:odbc:mydb2");

    // Fetch a record, now refering to second context
    Customer c = new Customer(8400719);

    // Switch to default context and store it in there
    DatabaseFactory.setContext(DatabaseFactory.DEFAULT_CONTEXT);
    c.create();

    Beside an explicite programmatic change, the context may also be switched based on entity types. If different contexts are used for different databases, the schemata in these databases are usually also different and thus require different entity types for access. For this case there is an extended constructor of RecordDescriptor available, which gets passed the declaration of a context to refer to. The selection of the database is done implicitely and type-driven within the functions of class ObjectMapper and its derivations. The following example shows two entity types which by definition are associated to different contexts:


    // Entity A associated with default context
    class A extends MappedObject {
        protected static RecordDescriptor red = new RecordDescriptor
            (Customer.class, DatabaseFactory.DEFAULT_CONTEXT, "A", null,
             new String[][] { { "id", "getId", "setId" } });

        // to be continued
    }

    // Entity B associated with context "second"
    class B extends MappedObject {
        protected static RecordDescriptor red = new RecordDescriptor
            (Customer.class, "second", "B", null,
             new String[][] { { "id", "getId", "setId" } });

        public B(A a) { setId(a.getId()); }

        // to be continued
    }

    An explicite context switch is not required any more which simplifies the application code and reduces the risk of applying wrong contexts.


    // Initialization of the default context
    ResourceAccessorJ2SE accessor = new ResourceAccessorJ2SE
        ("sun.jdbc.odbc.JdbcOdbcDriver", "sa", "", "sql.log");
    DatabaseFactory.setResourceAccessor(accessor);
    DatabaseFactory.setDatabaseName("jdbc:odbc:mydb");

    // Creation and initialization of a second context
    DatabaseFactory.setContext("second");
    DatabaseFactory.setResourceAccessor(accessor);
    DatabaseFactory.setDatabaseName("jdbc:odbc:mydb2");

    // Fetch a record, implicitely using the default context
    A a = new A(8400719);

    // Write a record, implicitely using the second context
    B b = new B(a);
    b.create();


     

    Prepared Statements

    PriDE's standard operations are all based on plain SQL and work without prepared statements. However, there is also rudimentary support for that in Version 2.0. The classes PreparedInsert and PreparedUpdate are available for the most popular types of prepared statements for the creation and the modification of database records. Additional helper classes may be derived from the common base class PreparedOperation following the same definition scheme. The concept may be changed in future versions of PriDE. The usage of prepared statements within PriDE is illustrated in the following example, using PreparedInsert for an optimized creation of Customer records:


    Customer customer = new Customer(01, "lessner");
    PreparedOperation pop = new PreparedInsert(customer.getDesc());
    pop.execute(customer);
    customer.setId(02);
    pop.execute(customer);
    customer.setId(03);
    pop.execute(customer);
    db.commit();

    Stored Procedures

    For a simplified invocation of stored procedures, PriDE provides the abstract base class StoredProcedure. For any required concrete SP there is a corresponding caller class derived from StoredProcedure, defining the call parameters as public members. Input parameters are defined as final members which therefore must be initialized in the constructor. Output parameters are declared as non-final members. The execution is performed by calling StoredProcedure.execute(), which causes PriDE to dynamically assemble a call string and associate the object's members with the SP parameters. The following example shows such a caller class for the stored procedure "CreateCustomer", which is supposed to create Customer records. If the SP has a different name than the class, the function getName() must be overridden.


    // Accessor class for Stored Procedure "CreateCustomer"
    public class CreateCustomer extends StoredProcedure {
      // Output parameter, is supposed to be generated by the SP here
      public int id;
      // Input parameter to be provided by the caller
      public final String name;

      public CreateCustomer(String name) {
        // This is a must, otherwise the compiler will complain
        this.name = name;
      }
    }

    // Usage
    StoredProcedure sp = new CreateCustomer("Lessner");
    sp.execute(DatabaseFactory.getDatabase());

    Like for entity types it were helpful to generate the access classes. Unfortunately the structure of meta data for SPs is strongly vendor-specific in the database. For Oracle databases there is a generator available in package de.mathema.pride.util which is called by the following command line:


    java de.mathema.pride.util.StoredProcedureGenerator \
         -p <package> -s <sp> -c <class> -d <driver> -i <db> -u <user> -w <password>

    Further reading

    For efficient usage of PriDE in more complex systems, it is stronly recommended to read through the patterns chapter which explains solutions for common problems in persistence management not directly being covered by the PriDE standard functionality.

    For an overview of PriDE's standard attribute type mappings and extension facilities, see chapter PriDE-Mapping.html.

    An extended option for the mapping of entities are generic attributes. This features hasn't evolved for a while now and therefore tends to be a little out of date.

    For the assembly of complex queries, there exists a simple expression builder.




    Home Introduction Javadoc