Berkeley DB Java Edition
version 3.0.12

com.sleepycat.persist.evolve
Class Mutations

java.lang.Object
  extended by com.sleepycat.persist.evolve.Mutations
All Implemented Interfaces:
Serializable

public class Mutations
extends Object
implements Serializable

A collection of mutations for configuring class evolution.

For persistent data that is not short lived, changes to persistent classes are almost inevitable. Some changes are compatible with existing types, and data conversion for these changes is performed automatically and transparently. Other changes are not compatible with existing types. Mutations can be used to explicitly manage many types of incompatible changes.

Mutations are configured when a store is opened via StoreConfig.setMutations. Mutations cause data conversion to occur lazily as instances are read from the store. The EntityStore.evolve method may also be used to perform eager conversion. For example:

  Mutations mutations = new Mutations();
  // Add mutations...
  StoreConfig config = new StoreConfig();
  config.setMutations(mutations);
  EntityStore store = new EntityStore(env, "myStore", config);

Not all incompatible class changes can be handled via mutations. For example, complex refactoring may require a transformation that manipulates multiple entity instances at once. Such changes are not possible with mutations but can be performed using a ConversionStore.

The different categories of type changes are described below.

Key Field Changes

Unlike entity data, key data is not versioned. Therefore, the physical key format for an index is fixed once the first key is written, and the changes allowed for key fields are very limited. The only changes allowed for key fields are:

Any other changes to a key field are not only incompatible, they are not supported except by creating a new temporary index and copying the current index to the temporary index. See ConversionStore.createTempIndex.

Key ordering, including the behavior of a custom Comparator, is also fixed, since keys are stored in order in the index. The specifications for key ordering may not be changed, and the developer is responsible for not changing the behavior of a key Comparator. WARNING:: Changing the behavior of a key Comparator is likely to make the index unusable.

Compatible Type Changes

Entity data, unlike key data, is versioned. Therefore, some changes can be made compatibly and other changes can be handled via mutations. Compatible changes are defined below.

Changes to field types in entity class definitions are compatible when they conform to the Java Language Specification definitions for Widening Primitive Conversions and Widening Reference Conversions. For example, a smaller integer type may be changed to a larger integer type, and a reference type may be changed to one of its supertypes. Automatic widening conversions are performed as described in the Java Language Specification.

Primitive types may also be compatibly changed to their corresponding primitive wrapper types, or to the wrapper type for a widened primitive type. However, changing from a primitive wrapper type to a primitive type is not a compatible change since existing null values could not be represented.

In addition, adding fields to a class is a compatible change. When a persistent instance of a class is read that does not contain the new field, the new field is initialized by the default constructor.

All other changes to instance fields are considered incompatible. Incompatible changes may be handled via mutations, as described next, or via a ConversionStore.

Mutations

A class or field can be renamed using a Renamer. Renaming is not expensive, since it does not involve conversion of instance data.

An entity class or field can be deleted using a Deleter. Deleting an entity class involves removing the primary and secondary indices for the store; removal is performed when the store is opened. Deleting of a field involves conversion of instances to discard the field data, and is done lazily or by the EntityStore.evolve method, as already described.

Other incompatible changes are made by creating a Conversion mutation and implementing a Converter.convert method that manipulates the raw objects and/or simple values directly. The convert method is passed an object of the old incompatible type and it returns an object of a current type.

Conversions can be specified in two ways: for specific fields or for all instances of a class. A different Conversion constructor is used in each case. Field-specific conversions are used instead of class conversions when both are applicable.

Note that all mutations are applied to a specific class version number. Class versions are explicit for two reasons:

  1. This provides safety in the face of multiple unconverted versions of a given type. Without a version, a single conversion method would have to handle multiple input types, and would have to distinguish between them by examining the data or type information.
  2. This allows arbitrary changes to be made. For example, a series of name changes may reuse a given name for more than one version. To identify the specific type being converted or renamed, a version number is needed.

See Entity.version() and Persistent.version() for information on assigning class version numbers.

Mutations are responsible for converting an existing incompatible class version to the current version as defined by a current class definition. For example, consider that class-version A-1 is initially changed to A-2 and a mutation is added for converting A-1 to A-2. If later changes to version A-3 occur before converting all A-1 instances to version A-2, the converter for A-1 will have to be changed. Instead of converting from A-1 to A-2 it will need to convert from A-1 to A-3. In addition, a mutation converting A-2 to A-3 will be needed.

When a conversion mutation applies to a given object, other mutations that may apply to that object are not performed. It is the responsibility of the converter to return an object that conforms to the current class definition, including renaming fields. If the input object has nested objects or superclasses that also need conversion, the converter for the input object must also perform those conversions before returning the final converted object.

The above two rules are defined to avoid the complexity and potential errors that could result if multiple mutations were performed on a single object.

The EntityStore.evolve method is used to ensure that all instances of an old class version are converted to the current version. When this takes place, mutations for the old version are no longer needed and are discarded by the store.

However, even when the application has called EntityStore#evolve it may be convenient to configure a set of mutations for handling stores in various stages of evolution; in other words, to configure more mutations than may actually be needed. Therefore, a store will silently ignore configured mutations for class versions that no longer exist. To find out what mutations are currently active, call EntityStore.getMutations.

Class changes that cannot be handled using a mutation may be handled using a ConversionStore.

Other Metadata Changes

When a class is renamed that happens to be an entity class, it remains an entity class. When a field is renamed that happens to be a primary or secondary key field, its metadata remains intact as well.

When SecondaryKey is added to a new or existing field, a new index is created automatically. The new index will be populated by reading the primary index when the primary index is opened.

When a field with SecondaryKey is deleted, or when SecondaryKey is removed from a field without deleting it, the secondary index is removed (dropped). Removal occurs when the store is opened.

Changing any other information about a primary or secondary key is generally not allowed except using a ConversionStore, as described above under Key Field Changes.

Generic Types and Class Evolution

Conversion mutations always target a specific class, for example Address. When class evolution is performed, all persistent entities are processed that might contain instances of the targeted class. To ensure that only entities that actually contain instances of this class are processed, and thereby avoid unnecessary processing, it is important to supply specific type arguments when using generic classes. Additionally, you should avoid declaring type parameters for non-abstract entity classes and subclasses.

For example, consider an entity class that contains a field declared as Set<?> favoriteColors. The type argument is not specified and must be assumed to be Object. If any subclass of Object (for example, Address) is being converted then class evolution must process all entities containing the favoriteColors field. If this field were instead declared with a specific type argument (for example, Set<Color> favoriteColors) then the entity will only be processed if that specific class (Color or a subclass of it) is targeted by a conversion.

The same principle applies if a non-abstract entity class or subclass is declared to have generic type parameters. Since the entity class is the type of top level persistence instances, there is no field declaration that is known to the Direct Persistence Layer and therefore the actual type arguments are not known to the Direct Persistence Layer. The type arguments will be assumed to be Object (or the upper bound of the type parameter, if one is specified in the class declaration). Therefore, it is best to avoid the declaration of type parameters for entity classes and subclasses.

A variation on this theme is when a PersistentProxy is used to proxy a class with generic type parameters. In this case, when a field with the proxied type is declared, the type parameters of the proxied class and the proxy class are linked to determine the actual persistent types. See PersistentProxy for details.

Author:
Mark Hayes
See Also:
Serialized Form

Constructor Summary
Mutations()
          Creates an empty set of mutations.
 
Method Summary
 void addConversion(Conversion conversion)
          Adds a conversion mutation.
 void addDeleter(Deleter deleter)
          Adds a deleter mutation.
 void addRenamer(Renamer renamer)
          Adds a renamer mutation.
 Set<Conversion> getConversions()
          Returns an unmodifiable set of all conversion mutations.
 Set<Deleter> getDeleters()
          Returns an unmodifiable set of all deleter mutations.
 Set<Renamer> getRenamers()
          Returns an unmodifiable set of all renamer mutations.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Constructor Detail

Mutations

public Mutations()
Creates an empty set of mutations.

Method Detail

addRenamer

public void addRenamer(Renamer renamer)
Adds a renamer mutation.


getRenamers

public Set<Renamer> getRenamers()
Returns an unmodifiable set of all renamer mutations.


addDeleter

public void addDeleter(Deleter deleter)
Adds a deleter mutation.


getDeleters

public Set<Deleter> getDeleters()
Returns an unmodifiable set of all deleter mutations.


addConversion

public void addConversion(Conversion conversion)
Adds a conversion mutation.


getConversions

public Set<Conversion> getConversions()
Returns an unmodifiable set of all conversion mutations.


Berkeley DB Java Edition
version 3.0.12

Copyright (c) 1996-2006 Sleepycat Software, Inc. - All rights reserved.