Package io.permazen

Class Permazen

java.lang.Object
io.permazen.Permazen

@ThreadSafe public class Permazen extends Object
Permazen Java persistence layer.

Permazen is a Java persistence solution built on three layers of abstraction:

  • At the bottom is a simple Key/Value Layer represented by KVDatabase. Transactions are supported at this layer and are accessed via KVTransaction. There are several available KVDatabase implementations, including wrappers for many third party key/value stores.
  • On top of that sits the Core API Layer, which provides a rigorous "object database" abstraction on top of a KVDatabase. It supports a flat hierarchy of "object" types, where an object consists of fields that are either simple (i.e., any Java type that can be (de)serialized to/from a byte[] array), list, set, or map. It also includes tightly controlled schema tracking, simple and composite indexes, and lifecycle and change notifications. It is not Java-specific or explicitly object-oriented: an "object" at this layer is just a structure with defined fields. The core API layer may be accessed through the Database and Transaction classes.
  • The Java Layer is a Java-centric, object-oriented persistence layer for Java applications. It sits on top of the core API layer and provides a fully "Java" view of the underlying data where all data access is through user-supplied Java model classes. All schema definition and listener registrations are inferred from Java annotations. Incremental JSR 303 validation is supported. The Permazen class represents an instance of this top layer database, and PermazenTransaction represents the corresonding transactions.

User-provided Java model classes define database fields by declaring abstract Java bean methods; Permazen generates concrete subclasses of the user-provided abstract model classes at runtime. These runtime classes will implement the Java bean methods as well as the PermazenObject interface. Instances of Java model classes are always associated with a specific PermazenTransaction, and all of their state derives from the underlying key/value KVTransaction.

Java model class instances have a unique ObjId which represents database identity. Permazen guarantees that at most one Java instance will exist for any given PermazenTransaction and ObjId.

Transactions

New instance creation, index queries, and certain other database-related tasks are initiated through a PermazenTransaction. Normal transactions are created via createTransaction().

Detached Transactions

Detached transactions are in-memory transactions that are completely detached from the database. They are never committed and may persist indefinitely. Their purpose is to hold a database objects in memory where they can be manipulated like normal Java objects, but with the usual functionality available to open transactions added, such as index queries, schema tracking, change notifications, reference cascades, etc. Detached transactions are initially empty, and are often used to hold a copy of some small portion of the database. Because their state is encoded entirely by in-memory key/value pairs, they are easily serialized/deserialized. They are also useful in non-database applications where a Java data structure in which all reference fields are invertable is needed.

See PermazenTransaction.createDetachedTransaction(), PermazenTransaction.getDetachedTransaction(), PermazenObject.copyOut(), and PermazenObject.copyIn().

Branched Transactions

Branched transactions behave like normal transactions, but internally they are constructed from two separate key/value transactions, one that occurs when the branched transaction is opened, and another that occurs at commit time. When a branched transaction is opened, a read-only snapshot of the key/value database is taken, and then that key/value transaction is closed. While it remains open the branched transaction operates entirely in-memory using the snapshot, without any underlying key/value transaction, and all reads and writes are tracked internally. At commit time, a new key/value transaction is opened, a conflict check is performed, and if there are no conflicts all of the branched transaction's collected writes are flushed out and the transction is committed.

Because they consume no database resources while open, branched transactions can stay open for an arbitrarily long time, yet they still provide the same consistency guarantees as normal transactions. Of course this comes at the cost of having to track reads and writes in-memory and performing the conflict check, but they can be useful in certain scenarios, for example, when a user is editing some database object in a GUI application, where the user may take several minutes to complete the form, but because only a single object is being edited the operation is guaranteed to access a small amount of data. In many applications it's important to detect conflicts, for example, where two users edit the same object and the second user's changes would completely overwite the first user's changes. Traditionally this kind of conflict check is done at the application layer, by using two separate transactions and applying application-level conflict detection, or the application relies on database object versioning to detect unexpected changes, which is an incomplete solution because it fails to detect changes in other objects that may be viewed by the user while deciding what to change (e.g., some other object that the edited object refers to). In other words, traditional database object versioning detects write/write conflicts but not read/write conflicts. Branched transactions provide full consistency automatically by guaranteeing that all information the user sees while editing the form is still valid at the time the form is submitted.

Branched transactions require that the underlying key/value database support KVTransaction.readOnlySnapshot() and require keeping track of every database read and write during the transaction; see BranchedKVTransaction for details and other caveats. They are created via createBranchedTransaction().

Initialization

Instances of this class must be initialized before use. This involves accessing the underlying database and registering the Java data model's schema. This will happen automatically (if needed) when any method that requires the registered schema is invoked (including creating a transaction), or by explicit invocation of initialize(). See also PermazenConfig.Builder.initializeOnCreation().

See Also: