BerkeleyDB Java Collections
BerkeleyDB Java Collections
Release 4.8
Legal Notice
This documentation is distributed under an open source license. You may review the terms of this license at:
[Link]
Oracle, Berkeley DB, and Sleepycat are trademarks or registered trademarks of Oracle. All rights to these marks are reserved. No
third-party use is permitted without the express prior written consent of Oracle.
Java™ and all Java-based marks are a trademark or registered trademark of Sun Microsystems, Inc, in the United States and other
countries.
To obtain a copy of this document's original source code, please submit a request to the Oracle Technology Network forum at:
[Link]
Published 4/12/2010
Table of Contents
Preface ..................................................................................................... iv
Conventions Used in this Book ................................................................... iv
For More Information .............................................................................. iv
1. Introduction ............................................................................................ 1
Features .............................................................................................. 1
Developing a DB Collections Application ........................................................ 2
Tutorial Introduction ............................................................................... 3
2. The Basic Program .................................................................................... 6
Defining Serialized Key and Value Classes ..................................................... 6
Opening and Closing the Database Environment ............................................ 11
Opening and Closing the Class Catalog ........................................................ 13
Opening and Closing Databases ................................................................ 15
Creating Bindings and Collections ............................................................. 17
Implementing the Main Program ............................................................... 20
Using Transactions ............................................................................... 24
Adding Database Items .......................................................................... 26
Retrieving Database Items ...................................................................... 29
Handling Exceptions .............................................................................. 31
3. Using Secondary Indices ............................................................................ 33
Opening Secondary Key Indices ................................................................ 33
More Secondary Key Indices .................................................................... 37
Creating Indexed Collections ................................................................... 40
Retrieving Items by Index Key .................................................................. 42
4. Using Entity Classes ................................................................................. 46
Defining Entity Classes ........................................................................... 46
Creating Entity Bindings ......................................................................... 50
Creating Collections with Entity Bindings .................................................... 53
Using Entities with Collections ................................................................. 54
5. Using Tuples .......................................................................................... 58
Using the Tuple Format ......................................................................... 58
Using Tuples with Key Creators ................................................................ 59
Creating Tuple Key Bindings .................................................................... 61
Creating Tuple-Serial Entity Bindings ......................................................... 63
Using Sorted Collections ......................................................................... 66
6. Using Serializable Entities .......................................................................... 68
Using Transient Fields in an Entity Class ...................................................... 68
Using Transient Fields in an Entity Binding ................................................... 72
Removing the Redundant Value Classes ...................................................... 74
7. Summary .............................................................................................. 76
A. API Notes and Details ............................................................................... 77
Using Data Bindings .............................................................................. 77
Selecting Binding Formats ................................................................ 78
Record Number Bindings ................................................................... 79
Selecting Data Bindings ................................................................... 79
Implementing Bindings .................................................................... 79
Using Bindings .............................................................................. 80
Class names are represented in monospaced font, as are method names. For example: "The
[Link]() method returns a Database class object."
Program examples are displayed in a monospaced font on a shaded background. For example:
import [Link];
import [Link];
import [Link];
...
In situations in this book, programming examples are updated from one chapter to the next in
this book. When this occurs, the new code is presented in monospaced bold font. For example:
import [Link];
import [Link];
import [Link];
...
Together the DB Java Collections API and Berkeley DB provide an embedded data management
solution with all the benefits of a full transactional storage and the simplicity of a well known
Java API. Java programmers who need fast, scalable, transactional data management for their
projects can quickly adopt and deploy the DB Java Collections API with confidence.
Features
Berkeley DB has always provided a Java API which can be roughly described as a map and cursor
interface, where the keys and values are represented as byte arrays. This API is a Java (JNI)
interface to the C API and it closely modeled the Berkeley DB C API's interface. The DB Java
Collections API is a layer on top of that thin JNI mapping of the C API to Berkeley DB. It adds
significant new functionality in several ways.
• An implementation of the Java Collections interfaces (Map, SortedMap, Set, SortedSet, List
and Iterator) is provided.
• Transactions are supported using the conventional Java transaction-per-thread model, where
the current transaction is implicitly associated with the current thread.
• Transaction runner utilities are provided that automatically perform transaction retry and
exception handling.
• Keys and values are represented as Java objects rather than byte arrays. Bindings are used
to map between Java objects and the stored byte arrays.
• The tuple data format is provided as the simplest data representation, and is useful for keys
as well as simple compact values.
• The serial data format is provided for storing arbitrary Java objects without writing custom
binding code. Java serialization is extended to store the class descriptions separately, making
the data records much more compact than with standard Java serialization.
• Custom data formats and bindings can be easily added. XML data format and XML bindings
could easily be created using this feature, for example.
Note that the DB Java Collections API does not support caching of programming language objects
nor does it keep track of their stored status. This is in contrast to "persistent object" approaches
such as those defined by ODMG [[Link] and JDO (JSR 12). Such
approaches have benefits but also require sophisticated object caching. For simplicity the DB
Java Collections API treats data objects by value, not by reference, and does not perform
object caching of any kind. Since the DB Java Collections API is a thin layer, its reliability and
performance characteristics are roughly equivalent to those of Berkeley DB, and database
tuning is accomplished in the same way as for any Berkeley DB database.
Depending on your application's concurrency and transactional requirements, you may choose
one of the three Berkeley DB Environments: Data Store, Concurrent Data Store, or
Transactional Data Store. For details on creating and configuring the environment, see the
Berkeley DB Programmer's Reference Guide
For each Berkeley DB datastore, you may choose from any of the four Berkeley DB access
methods — BTREE, HASH, RECNO, or QUEUE — and a number of other database options. Your
choice depends on several factors such as whether you need ordered keys, unique keys,
record number access, and so forth. For more information on access methods, see the
Berkeley DB Programmer's Reference Guide.
For each database you may choose a binding format for the keys and values. For example,
the tuple format is useful for keys because it has a deterministic sort order. The serial format
is useful for values if you want to store arbitrary Java objects. In some cases a custom format
may be appropriate. For details on choosing a binding format see Using Data Bindings
(page 77).
With the serial data format you do not have to create a binding for each Java class that is
stored since Java serialization is used. But for other formats a binding must be defined that
translates between stored byte arrays and Java objects. For details see Using Data Bindings
(page 77).
The standard Java Collection interfaces are used for accessing databases and secondary
indices. The Map and Set interfaces may be used for any type of database. The Iterator
interface is used through the Set interfaces. For more information on the collection interfaces
see Using Stored Collections (page 84).
Any number of bindings and collections may be created for the same database. This allows
multiple views of the same stored data. For example, a data store may be viewed as a Map of
keys to values, a Set of keys, or a Collection of values. String values, for example, may be used
with the built-in binding to the String class, or with a custom binding to another class that
represents the string values differently.
It is sometimes desirable to use a Java class that encapsulates both a data key and a data
value. For example, a Part object might contain both the part number (key) and the part name
(value). Using the DB Java Collections API this type of object is called an "entity". An entity
binding is used to translate between the Java object and the stored data key and value. Entity
bindings may be used with all Collection types.
Please be aware that the provided DB Java Collections API collection classes do not conform
completely to the interface contracts defined in the [Link] package. For example, all
iterators must be explicitly closed and the size() method is not available. The differences
between the DB Java Collections API collections and the standard Java collections are
documented in Stored Collections Versus Standard Java Collections (page 85).
Tutorial Introduction
Most of the remainder of this document illustrates the use of the DB Java Collections API by
presenting a tutorial that describes usage of the API. This tutorial builds a shipment database,
a familiar example from classic database texts.
The examples illustrate the following concepts of the DB Java Collections API:
• Object-to-data bindings
The shipment database consists of three database stores: the part store, the supplier store,
and the shipment store. Each store contains a number of records, and each record consists of
a key and a value.
In the example programs, Java classes containing the fields above are defined for the key and
value of each store: PartKey, PartData, SupplierKey, SupplierData, ShipmentKey and
ShipmentData. In addition, because the Part's Weight field is itself composed of two fields —
the weight value and the unit of measure — it is represented by a separate Weight class. These
classes will be defined in the first example program.
In general the DB Java Collections API uses bindings to describe how Java objects are stored.
A binding defines the stored data syntax and the mapping between a Java object and the stored
data. The example programs show how to create different types of bindings, and explains the
characteristics of each type.
The following tables show the record values that are used in all the example programs in the
tutorial.
The complete source of the final version of the example program is included in the Berkeley
DB distribution.
An important point is that instances of these classes are passed and returned by value, not by
reference, when they are stored and retrieved from the database. This means that changing
a key or value object does not automatically change the database. The object must be explicitly
stored in the database after changing it. To emphasize this point the key and value classes
defined here have no field setter methods. Setter methods can be defined, but it is important
to remember that calling a setter method will not cause the change to be stored in the database.
How to store and retrieve objects in the database will be described later.
Each key and value class contains a toString method that is used to output the contents of the
object in the example program. This is meant for illustration only and is not required for
database objects in general.
Notice that the key and value classes defined below do not contain any references to
[Link] packages. An important characteristic of these classes is that they are
independent of the database. Therefore, they may be easily used in other contexts and may
be defined in a way that is compatible with other tools and libraries.
Note that PartKey (as well as SupplierKey below) contain only a single String field. Instead of
defining a specific class for each type of key, the String class by itself could have been used.
Specific key classes were used to illustrate strong typing and for consistency in the example.
The use of a plain String as an index key is illustrated in the next example program. It is up to
the developer to use either primitive Java classes such as String and Integer, or strongly typed
classes. When there is the possibility that fields will be added later to a key or value, a specific
class should be used.
The PartData class contains the Part's Name, Color, Weight and City fields.
import [Link];
The Weight class is also defined here, and is used as the type of the Part's Weight field. Just
as in standard Java serialization, nothing special is needed to store nested objects as long as
they are all Serializable.
import [Link];
The SupplierData class contains the Supplier's Name, Status and City fields.
import [Link];
The ShipmentKey class contains the keys of both the Part and Supplier.
import [Link];
The ShipmentData class contains only the Shipment's Quantity field. Like PartKey and
SupplierKey, ShipmentData contains only a single primitive field. Therefore the Integer class
could have been used instead of defining a specific value class.
import [Link];
The SampleDatabase class is used to open and close the environment. It will also be used in
following sections to open and close the class catalog and other databases. Its constructor is
used to open the environment and its close() method is used to close the environment. The
skeleton for the SampleDatabase class follows.
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
The first thing to notice is that the Environment class is in the [Link] package, not
the [Link] package. The [Link] package contains all core Berkeley
DB functionality. The [Link] package contains extended functionality that
is based on the Java Collections API. The collections package is layered on top of the
[Link] package. Both packages are needed to create a complete application based
on the DB Java Collections API.
setAllowCreate() is set to true to specify that the environment's files will be created if they
don't already exist. If this parameter is not specified, an exception will be thrown if the
environment does not already exist. A similar parameter will be used later to cause databases
to be created if they don't exist.
When an Environment object is constructed, a home directory and the environment configuration
object are specified. The home directory is the location of the environment's log files that
store all database information.
The following getter method returns the environment for use by other classes in the example
program. The environment is used for opening databases and running transactions.
public class SampleDatabase
{
...
public final Environment getEnvironment()
{
return env;
}
...
}
The SampleDatabase class is extended to open and close the class catalog. The following
additional imports and class members are needed.
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
...
While the class catalog is itself a database, it contains metadata for other databases and is
therefore treated specially by the DB Java Collections API. The StoredClassCatalog class
encapsulates the catalog store and implements this special behavior.
The following statements open the class catalog by creating a Database and a
StoredClassCatalog object. The catalog database is created if it does not already exist.
public SampleDatabase(String homeDirectory)
throws DatabaseException, FileNotFoundException
{
...
DatabaseConfig dbConfig = new DatabaseConfig();
[Link](true);
[Link](true);
[Link]([Link]);
The DatabaseConfig class is used to specify configuration parameters when opening a database.
The first configuration option specified — setTransactional() — is set to true to create a
transactional database. While non-transactional databases can also be created, the examples
in this tutorial use transactional databases.
setAllowCreate() is set to true to specify that the database will be created if it does not
already exist. If this parameter is not specified, an exception will be thrown if the database
does not already exist.
setDatabaseType() identifies the database storage type or access method. For opening a catalog
database, the BTREE type is required. BTREE is the most commonly used database type and in
this tutorial is used for all databases.
The first parameter of the openDatabase() method is an optional transaction that is used for
creating a new database. If null is passed, auto-commit is used when creating a database.
Lastly, the StoredClassCatalog object is created to manage the information in the class catalog
database. The StoredClassCatalog object will be used in the sections following for creating
serial bindings.
The getClassCatalog method returns the catalog object for use by other classes in the example
program.
The [Link]() method simply closes the underlying class catalog database
and in fact the [Link]() method may be called instead, if desired. The catalog
database, and all other databases, must be closed before closing the environment.
The SampleDatabase class is extended to open and close the three databases. The following
additional class members are needed.
public class SampleDatabase
{
...
private static final String SUPPLIER_STORE = "supplier_store";
private static final String PART_STORE = "part_store";
private static final String SHIPMENT_STORE = "shipment_store";
...
private Database supplierDb;
private Database partDb;
private Database shipmentDb;
...
}
For each database there is a database name constant and a Database object.
The database configuration object that was used previously for opening the catalog database
is reused for opening the three databases above. The databases are created if they don't already
exist. The parameters of the openDatabase() method were described earlier when the class
catalog database was opened.
All databases, including the catalog database, must be closed before closing the environment.
The following getter methods return the databases for use by other classes in the example
program.
public class SampleDatabase
{
...
public final Database getPartDatabase()
{
return partDb;
}
Standard Java collections are used to access records in a database. Stored collections use
bindings transparently to convert the records to objects when they are retrieved from the
collection, and to convert the objects to records when they are stored in the collection.
An important characteristic of stored collections is that they do not perform object caching.
Every time an object is accessed via a collection it will be added to or retrieved from the
database, and the bindings will be invoked to convert the data. Objects are therefore always
passed and returned by value, not by reference. Because Berkeley DB is an embedded database,
efficient caching of stored raw record data is performed by the database library.
The SampleViews class is used to create the bindings and collections. This class is separate from
the SampleDatabase class to illustrate the idea that a single set of stored data can be accessed
via multiple bindings and collections, or views. The skeleton for the SampleViews class follows.
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
...
...
public SampleViews(SampleDatabase db)
{
}
}
The following statements create the key and data bindings using the SerialBinding class.
public SampleViews(SampleDatabase db)
{
ClassCatalog catalog = [Link]();
EntryBinding partKeyBinding =
new SerialBinding(catalog, [Link]);
EntryBinding partValueBinding =
new SerialBinding(catalog, [Link]);
EntryBinding supplierKeyBinding =
new SerialBinding(catalog, [Link]);
EntryBinding supplierValueBinding =
new SerialBinding(catalog, [Link]);
EntryBinding shipmentKeyBinding =
new SerialBinding(catalog, [Link]);
EntryBinding shipmentValueBinding =
new SerialBinding(catalog, [Link]);
...
}
The first parameter of the SerialBinding constructor is the class catalog, and is used to store
the class descriptions of the serialized objects.
The second parameter is the base class for the serialized objects and is used for type checking
of the stored objects. If null or [Link] is specified, then any Java class is allowed.
Otherwise, all objects stored in that format must be instances of the specified class or derived
from the specified class. In the example, specific classes are used to enable strong type
checking.
The following statements create standard Java maps using the StoredMap class.
public SampleViews(SampleDatabase db)
{
...
partMap =
new StoredMap([Link](),
partKeyBinding, partValueBinding, true);
supplierMap =
new StoredMap([Link](),
supplierKeyBinding, supplierValueBinding, true);
shipmentMap =
new StoredMap([Link](),
shipmentKeyBinding, shipmentValueBinding, true);
...
}
The second and third parameters are the key and value bindings to use when storing and
retrieving objects via the map.
The fourth and last parameter specifies whether changes will be allowed via the collection. If
false is passed, the collection will be read-only.
The following getter methods return the stored maps for use by other classes in the example
program. Convenience methods for returning entry sets are also included.
public class SampleViews
{
...
public final StoredMap getPartMap()
{
return partMap;
}
StoredMap, StoredEntrySet, and other stored collection classes have a small number of extra
methods beyond those in the Java collection interfaces. The stored collection types are therefore
returned to avoid casting when using the extended methods. Normally, however, only a Map
or Set is needed, and may be used as follows.
SampleDatabase sd = new SampleDatabase(new String("/home"));
SampleViews views = new SampleViews(sd);
Map partMap = [Link]();
Set supplierEntries = [Link]();
The Sample class contains the main program. The skeleton for the Sample class follows.
import [Link];
import [Link];
The -h command is used to set the homeDir variable, which will later be passed to the
SampleDatabase() constructor. Normally all Berkeley DB programs should provide a way to
configure their database environment home directory.
The default for the home directory is ./tmp — the tmp subdirectory of the current directory
where the sample is run. The home directory must exist before running the sample. To re-create
the sample database from scratch, delete all files in the home directory before running the
sample.
The home directory was described previously in Opening and Closing the Database Environment
(page 11).
Of course, the command line arguments shown are only examples and a real-life application
may use different techniques for configuring these options.
The Sample() constructor will open the environment and databases, and the run() method will
run transactions for storing and retrieving objects. If either of these throws an exception, then
the program was unable to run and should normally terminate. (Transaction retries are handled
at a lower level and will be described later.) The first catch statement handles such exceptions.
The finally statement is used to call the close() method since an attempt should always be
made to close the environment and databases cleanly. If an exception is thrown during close
and a prior exception occurred above, then the exception during close is likely a side effect
of the prior exception.
Recall that creating the SampleDatabase object will open the environment and all databases.
Using Transactions
DB transactional applications have standard transactional characteristics: recoverability,
atomicity and integrity (this is sometimes also referred to generically as ACID properties). The
DB Java Collections API provides these transactional capabilities using a transaction-per-thread
model. Once a transaction is begun, it is implicitly associated with the current thread until it
is committed or aborted. This model is used for the following reasons.
• The transaction-per-thread model is commonly used in other Java APIs such as J2EE.
• Since the Java collections API is used for data access, there is no way to pass a transaction
object to methods such as [Link].
The DB Java Collections API provides two transaction APIs. The lower-level API is the
CurrentTransaction class. It provides a way to get the transaction for the current thread, and
to begin, commit and abort transactions. It also provides access to the Berkeley DB core API
Transaction object. With CurrentTransaction, just as in the [Link] API, the
application is responsible for beginning, committing and aborting transactions, and for handling
deadlock exceptions and retrying operations. This API may be needed for some applications,
but it is not used in the example.
The example uses the higher-level TransactionRunner and TransactionWorker APIs, which are
build on top of CurrentTransaction. [Link]() automatically begins a transaction
and then calls the [Link]() method, which is implemented by the
application.
Using this high-level API, if [Link]() throws an exception, the application can
assume that the operation failed and the transaction was aborted; otherwise, when an exception
The [Link]() method creates a TransactionRunner object and calls its run() method.
import [Link];
import [Link];
...
public class Sample
{
private SampleDatabase db;
...
private void run()
throws Exception
{
TransactionRunner runner = new TransactionRunner([Link]());
[Link](new PopulateDatabase());
[Link](new PrintDatabase());
}
...
private class PopulateDatabase implements TransactionWorker
{
public void doWork()
throws Exception
{
}
}
The run() method is called by main() and was outlined in the previous section. It first creates
a TransactionRunner, passing the database environment to its constructor.
For each call to [Link](), a separate transaction will be performed. The use
of two transactions in the example — one for populating the database and another for printing
its contents — is arbitrary. A real-life application should be designed to create transactions for
each group of operations that should have ACID properties, while also taking into account the
impact of transactions on performance.
The [Link]() method calls private methods for adding objects to each of
the three database stores. It is called via the TransactionRunner class and was outlined in the
previous section.
import [Link];
import [Link];
...
public class Sample
{
...
private SampleViews views;
...
private class PopulateDatabase implements TransactionWorker
{
public void doWork()
throws Exception
{
addSuppliers();
addParts();
addShipments();
}
}
...
The key and value classes used above were defined in the Defining Serialized Key and Value
Classes (page 6).
In each method above, objects are added only if the map is not empty. This is a simple way
of allowing the example program to be run repeatedly. In real-life applications another technique
— checking the [Link] method, for example — might be used.
The [Link]() method calls printEntries() to print the map entries for each
database store. It is called via the TransactionRunner class and was outlined in the previous
section.
import [Link];
...
public class Sample
{
...
private SampleViews views;
...
private class PrintDatabase implements TransactionWorker
{
public void doWork()
throws Exception
{
printEntries("Parts",
[Link]().iterator());
printEntries("Suppliers",
[Link]().iterator());
printEntries("Shipments",
[Link]().iterator());
}
}
...
The Set of [Link] objects for each store is obtained from the SampleViews object. This set
can also be obtained by calling the [Link] method of a stored map.
The printEntries() prints the map entries for any stored map. The [Link] method
of each key and value is called to obtain a printable representation of each object.
private void printEntries(String label, Iterator iterator)
{
[Link]("\n--- " + label + " ---");
while ([Link]())
{
This is one of a small number of behavioral differences between standard Java collections and
stored collections. For a complete list see Using Stored Collections (page 84).
Handling Exceptions
Exception handling was illustrated previously in Implementing the Main Program (page 20)
and Using Transactions (page 24) exception handling in a DB Java Collections API application
in more detail.
There are two exceptions that must be treated specially: RunRecoveryException and
DeadlockException.
RunRecoveryException is thrown when the only solution is to shut down the application and
run recovery. All applications must catch this exception and follow the recovery procedure.
When DeadlockException is thrown, the application should normally retry the operation. If a
deadlock continues to occur for some maximum number of retries, the application should give
up and try again later or take other corrective actions. The DB Java Collections API provides
two APIs for transaction execution.
• When using the CurrentTransaction class directly, the application must catch
DeadlockException and follow the procedure described previously.
• When using the TransactionRunner class, retries are performed automatically and the
application need only handle the case where the maximum number of retries has been
reached. In that case, [Link] will throw DeadlockException.
When using the TransactionRunner class there are two other considerations.
The complete source of the final version of the example program is included in the Berkeley
DB distribution.
Both primary and secondary databases contain key-value records. The key of an index record
is the secondary key, and its value is the key of the associated record in the primary database.
When lookups by secondary key are performed, the associated record in the primary database
is transparently retrieved by its primary key and returned to the caller.
Secondary indices are maintained automatically when index key fields (the City field in this
case) are added, modified or removed in the records of the primary database. However, the
application must implement a SecondaryKeyCreator that extracts the index key from the
database record.
It is useful to contrast opening an secondary index with opening a primary database (as described
earlier in Opening and Closing Databases (page 15).
• A primary database may be associated with one or more secondary indices. A secondary
index is always associated with exactly one primary database.
The SampleDatabase class is extended to open the Supplier-by-City secondary key index.
import [Link];
import [Link];
import [Link];
...
public class SampleDatabase
{
...
[Link](
new SupplierByCityKeyCreator(javaCatalog,
[Link],
[Link],
[Link]));
supplierByCityDb = [Link](null,
SUPPLIER_CITY_INDEX,
null,
supplierDb,
secConfig);
...
}
}
For a primary database, duplicate keys are not normally used since a primary database with
duplicate keys may not have any associated secondary indices. If primary database keys are
not unique, there is no way for a secondary key to reference a specific record in the primary
database.
Note that setSortedDuplicates() and not setUnsortedDuplicates() was called. Sorted duplicates
are always used for indices rather than unsorted duplicates, since sorting enables optimized
equality joins.
How to use the secondary index to access records will be shown in a later section.
In general, a key creator class must implement the SecondaryKeyCreator interface. This interface
has methods that operate on the record data as raw bytes. In practice, it is easiest to use an
abstract base class that performs the conversion of record data to and from the format defined
for the database's key and value. The base class implements the SecondaryKeyCreator interface
and has abstract methods that must be implemented in turn by the application.
In this example the SerialSerialKeyCreator base class is used because the database record
uses the serial format for both its key and its value. The abstract methods of this class have
key and value parameters of type Object which are automatically converted to and from the
raw record data by the base class.
Note that the primaryKeyInput parameter is not used in the example. This parameter is needed
only when an index key is derived from the key of the primary database record. Normally an
index key is derived only from the primary database record value, but it may be derived from
the key, value or both.
The following getter methods return the secondary database object for use by other classes
in the example program. The secondary database object is used to create Java collections for
accessing records via their secondary keys.
public class SampleDatabase
{
...
public final SecondaryDatabase getSupplierByCityDatabase()
{
return supplierByCityDb;
}
...
}
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
}
...
}
Secondary databases must be closed before closing their associated primary database.
[Link](
new ShipmentBySupplierKeyCreator(javaCatalog,
[Link],
[Link],
[Link]));
shipmentBySupplierDb = [Link](null,
The statements in this example are very similar to the statements used in the previous section
for opening a secondary index.
The key creator classes above are almost identical to the one defined in the previous section
for use with a secondary index. The index key fields are different, of course, but the interesting
difference is that the index keys are extracted from the key, not the value, of the Shipment
record. This illustrates that an index key may be derived from the primary database record
key, value, or both.
The following getter methods return the secondary database objects for use by other classes
in the example program.
public class SampleDatabase
{
...
public final SecondaryDatabase getShipmentByPartDatabase()
{
return shipmentByPartDb;
}
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
}
...
}
When a map is created from a SecondaryDatabase, the keys of the map will be the index keys.
However, the values of the map will be the values of the primary database associated with the
index. This is how index keys can be used to access the values in a primary database.
For example, the Supplier's City field is an index key that can be used to access the Supplier
database. When a map is created using the supplierByCityDb() method, the key to the map
will be the City field, a String object. When [Link] is called passing the City as the key
parameter, a SupplierData object will be returned.
The SampleViews class is extended to create an index key binding for the Supplier's City field
and three Java maps based on the three indices created in the prior section.
import [Link];
import [Link];
import [Link];
import [Link];
...
In general, the indexed maps are created here in the same way as the unindexed maps were
created in the Basic example. The differences are:
• The second parameter is the index key binding rather than the primary key binding.
For the supplierByCityMap, the cityKeyBinding must first be created. This binding was not
created in the Basic example because the City field is not a primary key.
Like the bindings created earlier for keys and values, the cityKeyBinding is a SerialBinding.
Unlike the bindings created earlier, it is an example of creating a binding for a built-in Java
class, String, instead of an application-defined class. Any serializable class may be used.
This illustrates that bindings and formats may and should be reused where appropriate for
creating maps and other collections.
The following getter methods return the stored maps for use by other classes in the example
program. Convenience methods for returning entry sets are also included.
public class SampleViews
{
...
public final StoredMap getShipmentByPartMap()
{
return shipmentByPartMap;
}
Using the standard Java collections API, the [Link] method for a stored collection with
duplicate keys will return only the first value for a given key. To obtain all values for a given
key, the [Link] method may be called. This returns a Collection of values for
the given key. If duplicate keys are not allowed, the returned collection will have at most one
value. If the key is not present in the map, an empty collection is returned.
The Sample class is extended to retrieve duplicates for specific index keys that are present in
the database.
import [Link];
...
public class Sample
{
...
private SampleViews views;
...
private class PrintDatabase implements TransactionWorker
{
public void doWork()
The [Link] method is called passing the desired key. The returned value is a
standard Java Collection containing the values for the specified key. A standard Java Iterator
is then obtained for this collection and all values returned by that iterator are printed.
Another technique for retrieving duplicates is to use the collection returned by [Link].
When duplicate keys are present, a [Link] object will be present in this collection for each
duplicate. This collection can then be iterated or a subset can be created from it, all using the
standard Java collection API.
Note that we did not discuss how duplicates keys can be explicitly added or removed in a
collection. For index keys, the addition and deletion of duplicate keys happens automatically
when records containing the index key are added, updated, or removed.
While not shown in the example program, it is also possible to create a store with duplicate
keys in the same way as an index with duplicate keys — by calling
[Link]() method. In that case, calling [Link] will add duplicate
keys. To remove all duplicate keys, call [Link]. To remove a specific duplicate key, call
Such a combined key and value class is called an entity class and is used along with an entity
binding. Entity bindings combine a key and a value into an entity when reading a record from
a collection, and split an entity into a key and a value when writing a record to a collection.
Entity bindings are used in place of value bindings, and entity objects are used with collections
in place of value objects.
• When the key is a property of an entity object representing the record as a whole, the object's
identity and concept are often clearer than with key and value objects that are disjoint.
• A single entity object per record is often more convenient to use than two objects.
Of course, instead of using an entity binding, you could simply create the entity yourself after
reading the key and value from a collection, and split the entity into a key and value yourself
before writing it to a collection. But this would detract from the convenience of the using the
Java collections API. It is convenient to obtain a Part object directly from [Link] and to add
a Part object using [Link]. Collections having entity bindings can be used naturally without
combining and splitting objects each time a collection method is called; however, an entity
binding class must be defined by the application.
In addition to showing how to use entity bindings, this example illustrates a key feature of all
bindings: Bindings are independent of database storage parameters and formats. Compare this
example to the prior Index example and you'll see that the Sample and SampleViews classes
have been changed to use entity bindings, but the SampleDatabase class was not changed at
all. In fact, the Entity program and the Index program can be used interchangeably to access
the same physical database files. This demonstrates that bindings are only a "view" onto the
physical stored data.
Warning: When using multiple bindings for the same database, it is the application's responsibility
to ensure that the same format is used for all bindings. For example, a serial binding and a
tuple binding cannot be used to access the same records.
The complete source of the final version of the example program is included in the Berkeley
DB distribution.
The Part, Supplier and Shipment entity classes are defined below.
An important difference between the entity classes defined here and the key and value classes
defined earlier is that the entity classes are not serializable (do not implement the Serializable
interface). This is because the entity classes are not directly stored. The entity binding
decomposes an entity object into key and value objects, and only the key and value objects
are serialized for storage.
One advantage of using entities can already be seen in the toString() method of the classes
below. These return debugging output for the combined key and value, and will be used later
to create a listing of the database that is more readable than in the prior examples.
public class Part
{
private String number;
private String name;
private String color;
private Weight weight;
private String city;
The entity bindings will be used in the next section to construct stored map objects.
In general, an entity binding is any class that implements the EntityBinding interface, just as
an ordinary binding is any class that implements the EntryBinding interface. In the prior
examples the built-in SerialBinding class (which implements EntryBinding) was used and no
application-defined binding classes were needed.
In this example, application-defined binding classes are used that extend the
SerialSerialBinding abstract base class. This base class implements EntityBinding and provides
the conversions between key/value bytes and key/value objects, just as the SerialBinding
class does. The application-defined entity class implements the abstract methods defined in
the base class that map between key/value objects and entity objects.
Three abstract methods are implemented for each entity binding. The entryToObject() method
takes as input the key and data objects, which have been deserialized automatically by the
base class. As output, it returns the combined Part entity.
The objectToKey() and objectToData() methods take an entity object as input. As output they
return the part key or data object that is extracted from the entity object. The key or data
will then be serialized automatically by the base class.
Specifying an EntityBinding will select a different StoredMap constructor, but the syntax is
the same. In general, an entity binding may be used anywhere that a value binding is used.
Notice that the collection returned by the [Link] method is actually a StoredValueSet
and not just a Collection as defined by the [Link] interface. As long as duplicate keys
are not allowed, this collection will behave as a true set and will disallow the addition of
duplicates, etc.
For adding and iterating entities, the collection of entities returned by [Link] is used. In
general, when using an entity binding, all Java collection methods that are passed or returned
a value object will be passed or returned an entity object instead.
The Sample class has been changed in this example to add objects using the [Link] method
rather than the [Link] method that was used in the prior examples. Entity objects are
constructed and passed to [Link].
import [Link];
...
public class Sample
Instead of printing the key/value pairs by iterating over the [Link] as done in the prior
example, this example iterates over the entities in the [Link] collection.
import [Link];
import [Link];
...
public class Sample
{
...
private class PrintDatabase implements TransactionWorker
{
public void doWork()
throws Exception
{
printValues("Parts",
[Link]().iterator());
printValues("Suppliers",
[Link]().iterator());
printValues("Suppliers for City Paris",
[Link]().duplicates(
"Paris").iterator());
printValues("Shipments",
[Link]().iterator());
printValues("Shipments for Part P1",
[Link]().duplicates(
new PartKey("P1")).iterator());
printValues("Shipments for Supplier S1",
[Link]().duplicates(
new SupplierKey("S1")).iterator());
}
}
...
}
Tuples are useful as keys because they have a meaningful sort order, while serialized objects
do not. This is because the binary data for a tuple is written in such a way that its raw byte
ordering provides a useful sort order. For example, strings in tuples are written with a null
terminator rather than with a leading length.
Tuples are useful as keys or values when reducing the record size to a minimum is important.
A tuple is significantly smaller than an equivalent serialized object. However, unlike serialized
objects, tuples cannot contain complex data types and are not easily extended except by
adding fields at the end of the tuple.
Whenever a tuple format is used, except when the key or value class is a Java primitive wrapper
class, a tuple binding class must be implemented to map between the Java object and the
tuple fields. Because of this extra requirement, and because tuples are not easily extended,
a useful technique shown in this example is to use tuples for keys and serialized objects for
values. This provides compact ordered keys but still allows arbitrary Java objects as values,
and avoids implementing a tuple binding for each value class.
Compare this example to the prior Entity example and you'll see that the Sample class has not
changed. When changing a database format, while new bindings are needed to map key and
value objects to the new format, the application using the objects often does not need to be
modified.
The complete source of the final version of the example program is included in the Berkeley
DB distribution.
For example, to read and write a tuple containing two string values, the following code snippets
could be used.
import [Link];
import [Link];
...
TupleInput input;
TupleOutput output;
...
String partNumber = [Link]();
Since a tuple is defined as an ordered sequence, reading and writing order must match. If the
wrong data type is read (an integer instead of string, for example), an exception may be thrown
or at minimum invalid data will be read.
When the tuple format is used, bindings and key creators must read and write tuples using the
tuple API as shown above. This will be illustrated in the next two sections.
• For all key input and output parameters, the TupleInput and TupleOutput classes are used
instead of Object (representing a deserialized object).
• Instead of returning a key output object, these methods call tuple write methods such as
[Link].
For the Part key, Supplier key, and Shipment key, the SampleViews class was changed in this
example to create a custom TupleBinding instead of a SerialBinding. The custom tuple key
binding classes are defined further below.
import [Link];
...
public class SampleViews
{
...
public SampleViews(SampleDatabase db)
{
...
ClassCatalog catalog = [Link]();
EntryBinding partKeyBinding =
new PartKeyBinding();
EntityBinding partDataBinding =
new PartBinding(catalog, [Link]);
EntryBinding supplierKeyBinding =
new SupplierKeyBinding();
EntityBinding supplierDataBinding =
new SupplierBinding(catalog, [Link]);
EntryBinding shipmentKeyBinding =
new ShipmentKeyBinding();
EntityBinding shipmentDataBinding =
new ShipmentBinding(catalog, [Link]);
EntryBinding cityKeyBinding =
[Link]([Link]);
...
For the City key, however, a custom binding class is not needed because the key class is a
primitive Java type, String. For any primitive Java type, a tuple binding may be created using
the [Link] static method.
As with any entity binding, a key and value is converted to an entity in the
[Link] method, and from an entity to a key and value in the
[Link] and [Link] methods. But since
keys are stored as tuples, not as serialized objects, key fields are read and written using the
TupleInput and TupleOutput parameters.
The SampleViews class contains the modified entity binding classes that were defined in the
prior example: PartBinding, SupplierBinding and ShipmentBinding.
In addition to using the tuple format, the [Link] access method must be used
when creating the database. [Link] is used for the databases in all examples.
The [Link] access method does not support sorted keys.
Although not shown in the example, all methods of the SortedMap and SortedSet interfaces
may be used with sorted collections. For example, submaps and subsets may be created.
The output of the example program shows that records are sorted by key value.
Adding Suppliers
Adding Parts
Adding Shipments
When using serializable values it is possible to remove this redundancy by changing the entity
class in two ways:
• Make the entity class serializable, so it can be used in place of the value class.
• Make the key fields transient, so they are not redundantly stored in the record.
The modified entity class can then serve double-duty: It can be serialized and stored as the
record value, and it can be used as the entity class as usual along with the Java collections
API. The PartData, SupplierData and ShipmentData classes can then be removed.
Transient fields are defined in Java as fields that are not stored in the serialized form of an
object. Therefore, when an object is deserialized the transient fields must be explicitly
initialized. Since the entity binding is responsible for creating entity objects, it is the natural
place to initialize the transient key fields.
Note that it is not strictly necessary to make the key fields of a serializable entity class transient.
If this is not done, the key will simply be stored redundantly in the record's value. This extra
storage may or may not be acceptable to an application. But since we are using tuple keys and
an entity binding class must be implemented anyway to extract the key from the entity, it is
sensible to use transient key fields to reduce the record size. Of course there may be a reason
that transient fields are not desired; for example, if an application wants to serialize the entity
objects for other purposes, then using transient fields should be avoided.
The complete source of the final version of the example program is included in the Berkeley
DB distribution.
• A package-private setKey() method is added to each class for initializing the transient key
fields. This method will be called from the entity bindings.
import [Link];
...
public class Part implements Serializable
{
Before, the entryToObject() method combined the deserialized value object with the key
fields to create a new entity object. Now, this method uses the deserialized object directly
as an entity, and initializes its key using the fields read from the key tuple.
Before, the objectToData() method constructed a new value object using information in the
entity. Now it simply returns the entity. Nothing needs to be changed in the entity, since the
transient key fields won't be serialized.
import [Link];
...
public class SampleViews
{
...
private static class PartBinding extends TupleSerialBinding
{
private PartBinding(ClassCatalog classCatalog, Class dataClass)
{
super(classCatalog, dataClass);
}
The following table summarizes the differences between the examples in the tutorial.
Having completed this tutorial, you may want to explore how other types of bindings can be
implemented. The bindings shown in this tutorial are all external bindings, meaning that the
data classes themselves contain none of the binding implementation. It is also possible to
implement internal bindings, where the data classes implement the binding.
Internal bindings are called marshalled bindings in the DB Java Collections API, and in this
model each data class implements a marshalling interface. A single external binding class that
understands the marshalling interface is used to call the internal bindings of each data object,
and therefore the overall model and API is unchanged. To learn about marshalled bindings,
see the marshal and factory examples that came with your DB distribution (you can find them
in <INSTALL_DIR>/examples_java/src/com/sleepycat/examples/collections/ship where
<INSTALL_DIR> is the location where you unpacked your DB distribution). These examples
continue building on the example programs used in the tutorial. The Marshal program is the
next program following the Serializable Entity program, and the Factory program follows the
Marshal program. The source code comments in these examples explain their differences.
The selection of data bindings is, in general, independent of the selection of access methods
and collection views. In other words, any binding can be used with any access method or
collection. One exception to this rule is described under Record Number Bindings (page 79)
below.
☞ In this document, bindings are described in the context of their use for stored data in a
database. However, bindings may also be used independently of a database to operate on
an arbitrary byte array. This allows using bindings when data is to be written to a file or
sent over a network, for example.
As shown in the table above, the tuple format supports built-in ordering (without specifying a
custom comparator), while the serial format does not. This means that when a specific key
order is needed, tuples should be used instead of serial data. Alternatively, a custom Btree
comparator should be specified using [Link](). Note that a
custom Btree comparator will usually execute more slowly than the default byte-by-byte
comparison. This makes using tuples an attractive option, since they provide ordering along
with optimal performance.
The tuple binding uses less space and executes faster than the serial binding. But once a tuple
is written to a database, the order of fields in the tuple may not be changed and fields may
not be deleted. The only type evolution allowed is the addition of fields at the end of the
tuple, and this must be explicitly supported by the custom binding implementation.
The serial binding supports the full generality of Java serialization including type evolution.
But serialized data can only be accessed by Java applications, its size is larger, and its bindings
are slower to execute.
☞ You may not use RecordNumberBinding except with record number keys, as determined by
the access method. Using RecordNumberBinding in other cases will create a database that
is not portable between platforms. When constructing the stored collection, the DB Java
Collections API will throw an IllegalArgumentException in such cases.
Simple entry bindings map between the key or value data stored by Berkeley DB and a key or
value object. This is a simple one-to-one mapping.
Simple entry bindings are easy to implement and in some cases require no coding. For example,
a SerialBinding can be used for keys or values without writing any additional code. A tuple
binding for a single-item tuple can also be used without writing any code; see the
[Link]() method.
Entity bindings must divide an entity object into its key and value data, and then combine the
key and value data to re-create the entity object. This is a two-to-one mapping.
Entity bindings are useful when a stored application object naturally has its primary key as a
property, which is very common. For example, an Employee object would naturally have an
EmployeeNumber property (its primary key) and an entity binding would then be needed. Of
course, entity bindings are more complex to implement, especially if their key and data formats
are different.
Note that even when an entity binding is used a key binding is also usually needed. For example,
a key binding is used to create key objects that are passed to the [Link]() method. A key
object is passed to this method even though it may return an entity that also contains the key.
Implementing Bindings
There are two ways to implement bindings. The first way is to create a binding class that
implements one of the two binding interfaces, EntryBinding or EntityBinding. For tuple
bindings and serial bindings there are a number of abstract classes that make this easier. For
example, you can extend TupleBinding to implement a simple binding for a tuple key or value.
Abstract classes are also provided for entity bindings and are named after the format names
of the key and value. For example, you can extend TupleSerialBinding to implement an entity
binding with a tuple key and serial value.
Using Bindings
Bindings are specified whenever a stored collection is created. A key binding must be specified
for map, key set and entry set views. A value binding or entity binding must be specified for
map, value set and entry set views.
Any number of bindings may be created for the same stored data. This allows multiple views
over the same data. For example, a tuple might be bound to an array of values or to a class
with properties for each object.
It is important to be careful of bindings that only use a subset of the stored data. This can be
useful to simplify a view or to hide information that should not be accessible. However, if you
write records using these bindings you may create stored data that is invalid from the
application's point of view. It is up to the application to guard against this by creating a read-only
collection when such bindings are used.
Like bindings, key creators may be implemented using a separate key creator class or using a
marshalling interface. Abstract key creator classes and marshalling interfaces are provided in
the [Link] and [Link] packages.
Unlike bindings, key creators fundamentally operate on key and value data, not necessarily on
the objects derived from the data by bindings. In this sense key creators are a part of a database
definition, and may be independent of the various bindings that may be used to view data in
a database. However, key creators are not prohibited from using higher level objects produced
by bindings, and doing so may be convenient for some applications. For example, marshalling
interfaces, which are defined for objects produced by bindings, are a convenient way to define
key creators.
The recommended technique is to use the TransactionRunner class along with your own
implementation of the TransactionWorker interface. TransactionRunner will call your
TransactionWorker implementation class to perform the data access or work of the transaction.
This technique has the following benefits:
• Transaction exceptions will be handled transparently and retries will be performed when
deadlocks are detected.
If you don't want to use TransactionRunner, the alternative is to use the CurrentTransaction
class.
If you choose to use CurrentTransaction directly you must handle the DeadlockException
exception and perform retries yourself. Also note that CurrentTransaction may only be used
in a transactional environment.
The DB Java Collections API supports transaction auto-commit. If no transaction is active and
a write operation is requested for a transactional database, auto-commit is used automatically.
The DB Java Collections API also supports transaction dirty-read via the StoredCollections
class. When dirty-read is enabled for a collection, data will be read that has been modified by
Transaction Rollback
When a transaction is aborted (or rolled back) the application is responsible for discarding
references to any data objects that were modified during the transaction. Since the DB Java
Collections API treats data by value, not by reference, neither the data objects nor the DB
Java Collections API objects contain status information indicating whether the data objects
are 1- in sync with the database, 2- dirty (contain changes that have not been written to the
database), 3- stale (were read previously but have become out of sync with changes made to
the database), or 4- contain changes that cannot be committed because of an aborted
transaction.
For example, a given data object will reflect the current state of the database after reading
it within a transaction. If the object is then modified it will be out of sync with the database.
When the modified object is written to the database it will then be in sync again. But if the
transaction is aborted the object will then be out of sync with the database. References to
objects for aborted transactions should no longer be used. When these objects are needed
later they should be read fresh from the database.
When an existing stored object is to be updated, special care should be taken to read the data,
then modify it, and then write it to the database, all within a single transaction. If a stale data
object (an object that was read previously but has since been changed in the database) is
modified and then written to the database, database changes may be overwritten
unintentionally.
When an application enforces rules about concurrent access to specific data objects or all data
objects, the rules described here can be relaxed. For example, if the application knows that
a certain object is only modified in one place, it may be able to reliably keep a current copy
of that object. In that case, it is not necessary to reread the object before updating it. That
said, if arbitrary concurrent access is to be supported, the safest approach is to always read
data before modifying it within a single transaction.
Similar concerns apply to using data that may have become stale. If the application depends
on current data, it should be read fresh from the database just before it is used.
Please see the Berkeley DB Programmer's Reference Guide for more information on access
method configuration.
• If keys are ordered then data may be enumerated in key order and key ranges may be used
to form subsets of a data store. The SortedMap and SortedSet interfaces are supported for
collections with ordered keys.
• If duplicates are allowed then more than one value may be associated with the same key.
This means that the data store cannot be strictly considered a map — it is really a multi-map.
See Using Stored Collections (page 84) for implications on the use of the collection interfaces.
• If duplicate keys are allowed for a data store then the data store may not have secondary
indices.
• For secondary indices with duplicates, the duplicates must be sorted. This restriction is
imposed by the DB Java Collections API.
• With sorted duplicates, all values for the same key must be distinct.
• If duplicates are unsorted, then values for the same key must be distinct.
• If record number keys are used, the the number of records is limited to the maximum value
of an unsigned 32-bit integer.
• If record number keys are renumbered, then standard List add/remove behavior is supported
but concurrency/performance is reduced.
• SortedSet and SortedMap interfaces may only be used if keys are ordered. This means ordered
keys are required for creating a StoredSortedEntrySet, StoredSortedKeySet, StoredSortedMap,
or StoredSortedValueSet.
• All iterators for stored collections implement the ListIterator interface as well as the
Iterator interface. [Link]() and [Link]() work in all
cases. However, the following ListIterator method behavior is dependent on the access
method.
• [Link]() inserts before the current position and renumbers following keys if
the RECNO-RENUMBER access method is used.
• Only the access methods that use a record number key may be used with a List view.
• To create a stored List that supports the [Link]() method, only the RECNO-RENUMBER
access method may be used.
For these access methods, stored Lists are most useful as read-only collections where indices
are not required to be sequential.
• When duplicates are allowed the Collection interfaces are modified in several ways as
described in the next section.
The Java collections interface does not support duplicate keys (multi-maps or multi-sets).
When the access method allows duplicate keys, the collection interfaces are defined as follows.
• [Link]() may contain multiple [Link] objects with the same key.
• [Link]() contains all values including the values associated with duplicate keys.
• [Link]() appends a duplicate if the key already exists rather than replacing the existing
value, and always returns null.
• [Link]() is an additional method for returning the values for a given key as
a Collection.
• Because the size() method cannot be used, the bulk operation methods of standard Java
collections cannot be passed stored collections as parameters, since the implementations
rely on size(). However, the bulk operation methods of stored collections can be passed
standard Java collections as parameters. [Link](standardCollection) is
allowed while [Link](storedCollection) is not allowed. This restriction
applies to the standard collection constructors that take a Collection parameter (copy
constructors), the [Link]() method, and the following Collection methods: addAll(),
containsAll(), removeAll() and retainAll().
• The [Link]() method is not used to determine whether a key or value is contained
in a collection, to locate a value by key, etc. Instead the byte array representation of the
keys and values are used. However, the equals() method is called for each key and value
when comparing two collections for equality. It is the responsibility of the application to
make sure that the equals() method returns true if and only if the byte array representations
of the two objects are equal. Normally this occurs naturally since the byte array representation
is derived from the object's fields.
• All stored collections are thread safe (can be used by multiple threads concurrently) whenever
the Berkeley DB Concurrent Data Store or Transactional Data Store environment is used.
Locking is handled by the Berkeley DB environment. To access a collection from multiple
threads, creation of synchronized collections using the Collections class is not necessary
except when using the Data Store environment. Iterators, however, should always be used
only by a single thread.
• All stored collections may be read-only if desired by passing false for the writeAllowed
parameter of their constructor. Creation of immutable collections using the Collections
class is not necessary.
• [Link]() returns a SortedSet, not just a Collection, whenever the keys of the
map can be derived from the values using an entity binding. Note that the sorted set returned
is not really a set if duplicates are allowed, since it is technically a collection; however, the
SortedSet methods (for example, subSet()), can still be used.
• For SortedSet and SortedMap views, additional subSet() and subMap() methods are provided
that allow control over whether keys are treated as inclusive or exclusive values in the key
range.
• All iterators for stored collections implement the ListIterator interface as well as the
Iterator interface. This is to allow use of the [Link]() and
[Link]() methods, which work for all collections since Berkeley DB provides
bidirectional cursors.
• Iterator stability for stored collections is greater than the iterator stability defined by the
Java collections interfaces. Stored iterator stability is the same as the cursor stability defined
by Berkeley DB.
• When an entity binding is used, updating (setting) a value is not allowed if the key in the
entity is not equal to the original key. For example, calling [Link]() is not allowed when
the key parameter is not equal to the key of the entity parameter. [Link](), [Link](),
[Link](), and [Link]() will throw IllegalArgumentException in this
situation.
• Adding and removing items from stored lists is not allowed for sublists. This is simply an
unimplemented feature and may be changed in the future. Currently for sublists the following
methods throw UnsupportedOperationException: [Link](), [Link](),
[Link]() and [Link]().
1. Provide the Java developer with an API that is as familiar and easy to use as possible.
2. Provide access to all, or a large majority, of the features of the underlying Berkeley DB
storage system.
4. For ease of use, support object-to-data bindings, per-thread transactions, and some
traditional database features such as foreign keys.
5. Provide a thin layer that can be thoroughly tested and which does not significantly impact
the reliability and performance of DB.
Admittedly there are several things about the Java Collections API that don't quite fit with DB
or with any transactional database, and therefore there are some new rules for applying the
Java Collections API. However, these disadvantages are considered to be smaller than the
disadvantages of the alternatives:
• A new API not based on the Java Collections API could have been designed that maps well
to DB but is higher-level. However, this would require designing an entirely new model. The
exceptions for using the Java Collections API are considered easier to learn than a whole
new model. A new model would also require a long design stabilization period before being
as complete and understandable as either the Java Collections API or the DB API.
• The ODMG API or another object persistence API could have been implemented on top of
DB. However, an object persistence implementation would add much code and require a
long stabilization period. And while it may work well for applications that require object
persistence, it would probably never perform well enough for many other applications.