Java DB

Apache Derby

Derby Developer's Guide

Derby Getting Started
Derby Reference Manual
Derby Developer's Guide
Derby Performance Tuning
Derby Server and Admin Guide
Derby Tools and Utilities
Derby Developer's Guide
-After installing
-Upgrades
-JDBC applications and Derby basics
-Application development overview
-Derby embedded basics
-Derby JDBC driver
-Derby JDBC database connection URL
-Derby system
-A Derby database
-Connecting to databases
-Working with the database connection URL attributes
-Using in-memory databases
-Working with Derby properties
-Deploying Derby applications
-Deployment issues
-Creating Derby databases for read-only use
-Loading classes from a database
-Derby server-side programming
-Programming database-side JDBC routines
-Programming trigger actions
-Programming Derby-style table functions
-Programming user-defined types
-Controlling Derby application behavior
-The JDBC connection and transaction model
-Result set and cursor mechanisms
-Locking, concurrency, and isolation
-Working with multiple connections to a single database
-Working with multiple threads sharing a single connection
-Working with database threads in an embedded environment
-Working with Derby SQLExceptions in an application
-Using Derby as a J2EE resource manager
-Derby and Security
-Configuring security for your environment
-Working with user authentication
-Users and authorization identifiers
-User authorizations
-Encrypting databases on disk
-Signed jar files
-Notes on the Derby security features
-User authentication and authorization examples
-Running Derby under a security manager
-Developing tools and using Derby with an IDE
-SQL tips
-Localizing Derby
-Derby and standards

 

Programming user-defined types

Derby allows you to create user-defined types. A user-defined type is a serializable Java class whose instances are stored in columns. The class must implement the java.io.Serializable interface, and it must be declared to Derby by means of a CREATE TYPE statement.

The key to designing a good user-defined type is to remember that data evolves over time, just like code. A good user-defined type has version information built into it. This allows the user-defined data to upgrade itself as the application changes. For this reason, it is a good idea for a user-defined type to implement java.io.Externalizable and not just java.io.Serializable. Although the SQL standard allows a Java class to implement only java.io.Serializable, this is bad practice for the following reasons:

  • Recompilation - If the second version of your application is compiled on a different platform from the first version, then your serialized objects may fail to deserialize. This problem and a possible workaround are discussed in the "Version Control" section near the end of this Serialization Primer and in the last paragraph of the header comment for java.io.Serializable.
  • Evolution - Your tools for evolving a class which simply implements java.io.Serializable are very limited.

Fortunately, it is easy to write a version-aware UDT which implements java.io.Serializable and can evolve itself over time. For example, here is the first version of such a class:

package com.acme.types;

import java.io.*;
import java.math.*;

public class Price implements Externalizable
{
    // initial version id
    private static final int FIRST_VERSION = 0;

    public String currencyCode;
    public BigDecimal amount;

    // zero-arg constructor needed by Externalizable machinery
    public Price() {}

    public Price( String currencyCode, BigDecimal amount )
    {
        this.currencyCode = currencyCode;
        this.amount = amount;
    }

    // Externalizable implementation
    public void writeExternal(ObjectOutput out) throws IOException
    {
        // first write the version id
        out.writeInt( FIRST_VERSION );

        // now write the state
        out.writeObject( currencyCode );
        out.writeObject( amount );
    }
    
    public void readExternal(ObjectInput in) 
        throws IOException, ClassNotFoundException
    {
        // read the version id
        int oldVersion = in.readInt();
        if ( oldVersion < FIRST_VERSION ) { 
            throw new IOException( "Corrupt data stream." ); 
        }
        if ( oldVersion > FIRST_VERSION ) { 
            throw new IOException( "Can't deserialize from the future." );
        }

        currencyCode = (String) in.readObject();
        amount = (BigDecimal) in.readObject();
    }
}

After this, it is easy to write a second version of the user-defined type which adds a new field. When old versions of Price values are read from the database, they upgrade themselves on the fly. Changes are shown in bold:

package com.acme.types;

import java.io.*;
import java.math.*;
import java.sql.*;

public class Price implements Externalizable
{
    // initial version id
    private static final int FIRST_VERSION = 0;
    private static final int TIMESTAMPED_VERSION = FIRST_VERSION + 1;

    private static final Timestamp DEFAULT_TIMESTAMP = new Timestamp( 0L );

    public String currencyCode;
    public BigDecimal amount;
    public Timestamp timeInstant;

    // 0-arg constructor needed by Externalizable machinery
    public Price() {}

    public Price( String currencyCode, BigDecimal amount, 
                  Timestamp timeInstant )
    {
        this.currencyCode = currencyCode;
        this.amount = amount;
        this.timeInstant = timeInstant;
    }

    // Externalizable implementation
    public void writeExternal(ObjectOutput out) throws IOException
    {
        // first write the version id
        out.writeInt( TIMESTAMPED_VERSION );

        // now write the state
        out.writeObject( currencyCode );
        out.writeObject( amount );
        out.writeObject( timeInstant );
    }
      
    public void readExternal(ObjectInput in) 
        throws IOException, ClassNotFoundException
    {
        // read the version id
        int oldVersion = in.readInt();
        if ( oldVersion < FIRST_VERSION ) { 
            throw new IOException( "Corrupt data stream." ); 
        }
        if ( oldVersion > TIMESTAMPED_VERSION ) {
            throw new IOException( "Can't deserialize from the future." ); 
        }

        currencyCode = (String) in.readObject();
        amount = (BigDecimal) in.readObject();

        if ( oldVersion >= TIMESTAMPED_VERSION ) {
            timeInstant = (Timestamp) in.readObject(); 
        }
        else { 
            timeInstant = DEFAULT_TIMESTAMP; 
        }
    }
}

An application needs to keep its code in sync across all tiers. This is true for all Java code which runs both in the client and in the server. This is true for functions and procedures which run in multiple tiers. It is also true for user-defined types which run in multiple tiers. The programmer should code defensively for the case when the client and server are running different versions of the application code. In particular, the programmer should write defensive serialization logic for user-defined types so that the application gracefully handles client/server version mismatches.

 

javadb@jdbcurl.com