Class Permazen
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 viaKVTransaction
. There are several availableKVDatabase
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 abyte[]
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 theDatabase
andTransaction
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, andPermazenTransaction
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 one that occurs at commit time.
When a branched transaction is opened, the first key/value transaction is opened just long enoughh to create a read-only
snapshot of the database and then closed. The branched transaction then
operates entirely in memory, based on the snapshot, and it keeps track of all keys read and written. At commit time,
a new key/value transaction is opened and a conflict check is performed. If successful, the accumulated writes are
flushed out and the transaction completes, otherwise a TransactionConflictException
is thrown.
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 conflict checks all at once on commit. Howerver they can be useful in certain scenarios. For example, imagine a user is editing some database object in a GUI application, and the user could take several minutes to complete a form. Because only a single object is being edited, the operation is guaranteed to only access a small amount of data. Using a branched transaction means no database resources are held open while the user decides.
In addition, in many applications it's important to detect conflicts, for example, when two users edit the same object where the second user's changes would otherwise overwite the first user's changes. Traditionally this kind of conflict check is done at the application layer, either by using two separate transactions and applying application-level conflict detection (which is tedious and error-prone), or by relying on database object versioning to detect unexpected changes, which is an incomplete solution because it fails to detect changes in other objects that might have been viewed by the user when 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, and only for the object being written back. Branched transactions provide full consistency in a completely automated way by guaranteeing that all information the user sees while editing a form is still valid when the form's changes are committed.
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()
.
-
Field Summary
-
Constructor Summary
ConstructorDescriptionPermazen
(PermazenConfig config) Create a new instance using the given configuration. -
Method Summary
Modifier and TypeMethodDescriptionprotected TransactionConfig
buildTransactionConfig
(Map<String, ?> kvoptions) Build theTransactionConfig
for a new core API transaction.Open a new branched transaction.createBranchedTransaction
(ValidationMode validationMode) Open a new branched transaction using the specifiedValidationMode
.createBranchedTransaction
(ValidationMode validationMode, Map<String, ?> openOptions, Map<String, ?> syncOptions) Open a new branched transaction using the specifiedValidationMode
and key/value transaction options.createDetachedTransaction
(KVStore kvstore, ValidationMode validationMode) Create a newDetachedPermazenTransaction
based on the provided key/value store.createDetachedTransaction
(ValidationMode validationMode) Create a new, emptyDetachedPermazenTransaction
backed by aMemoryKVStore
.Open a new transaction.createTransaction
(KVTransaction kvt, ValidationMode validationMode) Create a newPermazenTransaction
using an already-openedKVTransaction
.createTransaction
(ValidationMode validationMode) Open a new transaction using the specifiedValidationMode
.createTransaction
(ValidationMode validationMode, Map<String, ?> kvoptions) Open a new transaction using the specifiedValidationMode
and key/value transaction options.<T> PermazenClass<? super T>
findPermazenClass
(Class<T> type) Find the most specificPermazenClass
for which the give type is a sub-type of the corresponding Java model type.Get the core APIDatabase
underlying this instance.getPermazenClass
(int storageId) Get thePermazenClass
associated with the given storage ID.Get thePermazenClass
associated with the object ID.<T> PermazenClass<T>
getPermazenClass
(Class<T> type) Get thePermazenClass
modeled by the given type.getPermazenClass
(String typeName) Get thePermazenClass
associated with the given object type name.<T> List<PermazenClass<? extends T>>
getPermazenClasses
(Class<T> type) Get allPermazenClass
es which sub-type the given type.Get allPermazenClass
's associated with this instance, indexed by object type name.Get allPermazenClass
's associated with this instance, indexed by storage ID.Map<Class<?>,
PermazenClass<?>> Get allPermazenClass
's associated with this instance, indexed by Java model type.Get theSchemaModel
associated with this instance derived from the annotations on the scanned classes, including actual storage ID assignments.getSchemaModel
(boolean withStorageIds) Get theSchemaModel
associated with this instance derived from the annotations on the scanned classes.boolean
Initialize this instance if needed.boolean
Determine whether this instance is initialized.parseReferencePath
(Class<?> startType, String path) Parse aReferencePath
starting from a Java type.parseReferencePath
(Set<PermazenClass<?>> startTypes, String path) Parse aReferencePath
starting from a set of model object types.
-
Field Details
-
GENERATED_CLASS_NAME_SUFFIX
The suffix that is appended to Java model class names to get the corresponding Permazen generated class name.- See Also:
-
VERSION
The version of this library.
-
-
Constructor Details
-
Permazen
Create a new instance using the given configuration.- Parameters:
config
- configuration to use- Throws:
IllegalArgumentException
- ifconfig
contains a null class or a class with invalid annotation(s)IllegalArgumentException
- ifconfig
is nullInvalidSchemaException
- if the schema implied byclasses
is invalid
-
-
Method Details
-
isInitialized
public boolean isInitialized()Determine whether this instance is initialized.- Returns:
- true if initialized, otherwise false
-
initialize
public boolean initialize()Initialize this instance if needed.- Returns:
- true if this instance was actually initialized, false if it was already initialized and so nothing happened
- Throws:
InvalidSchemaException
- if the data model schema conflicts with what's registered in the database
-
getDatabase
Get the core APIDatabase
underlying this instance.- Returns:
- underlying
Database
-
createTransaction
Open a new transaction.Convenience method; equivalent to:
createTransaction
(ValidationMode.AUTOMATIC
, null)- Returns:
- the newly created transaction
- Throws:
InconsistentDatabaseException
- if inconsistent or invalid meta-data is detected in the database
-
createTransaction
Open a new transaction using the specifiedValidationMode
.Convenience method; equivalent to:
createTransaction
(validationMode, null)- Parameters:
validationMode
- theValidationMode
to use for the new transaction- Returns:
- the newly created transaction
- Throws:
InconsistentDatabaseException
- if inconsistent or invalid meta-data is detected in the databaseIllegalArgumentException
- ifvalidationMode
is null
-
createTransaction
public PermazenTransaction createTransaction(ValidationMode validationMode, Map<String, ?> kvoptions) Open a new transaction using the specifiedValidationMode
and key/value transaction options.This does not invoke
PermazenTransaction.setCurrent()
; the caller is responsible for doing that if/when needed.- Parameters:
validationMode
- theValidationMode
to use for the new transactionkvoptions
-KVDatabase
-specific transaction options, or null for none- Returns:
- the newly created transaction
- Throws:
InconsistentDatabaseException
- if inconsistent or invalid meta-data is detected in the databaseIllegalArgumentException
- ifvalidationMode
is null
-
createBranchedTransaction
Open a new branched transaction.Convenience method; equivalent to:
createBranchedTransaction
(ValidationMode.AUTOMATIC
, null, null)- Returns:
- the newly created branched transaction
- Throws:
InconsistentDatabaseException
- if inconsistent or invalid meta-data is detected in the databaseUnsupportedOperationException
- if the key/value database doesn't supportKVTransaction.readOnlySnapshot()
-
createBranchedTransaction
Open a new branched transaction using the specifiedValidationMode
.Convenience method; equivalent to:
createBranchedTransaction
(validationMode, null, null)- Returns:
- the newly created branched transaction
- Throws:
InconsistentDatabaseException
- if inconsistent or invalid meta-data is detected in the databaseUnsupportedOperationException
- if the key/value database doesn't supportKVTransaction.readOnlySnapshot()
-
createBranchedTransaction
public PermazenTransaction createBranchedTransaction(ValidationMode validationMode, Map<String, ?> openOptions, Map<String, ?> syncOptions) Open a new branched transaction using the specifiedValidationMode
and key/value transaction options.This does not invoke
PermazenTransaction.setCurrent()
; the caller is responsible for doing that if/when needed.- Parameters:
validationMode
- theValidationMode
to use for the new transactionopenOptions
-KVDatabase
-specific transaction options for the branch's opening transaction, or null for nonesyncOptions
-KVDatabase
-specific transaction options for the branch's commit transaction, or null for none- Returns:
- the newly created branched transaction
- Throws:
InconsistentDatabaseException
- if inconsistent or invalid meta-data is detected in the databaseUnsupportedOperationException
- if the key/value database doesn't supportKVTransaction.readOnlySnapshot()
IllegalArgumentException
- ifvalidationMode
is null
-
createTransaction
Create a newPermazenTransaction
using an already-openedKVTransaction
.This does not invoke
PermazenTransaction.setCurrent()
; the caller is responsible for doing that if/when needed.- Parameters:
kvt
- already opened key/value store transactionvalidationMode
- theValidationMode
to use for the new transaction- Returns:
- the newly created transaction
- Throws:
InconsistentDatabaseException
- if inconsistent or invalid meta-data is detected in the databaseIllegalArgumentException
- ifkvt
orvalidationMode
is null
-
createDetachedTransaction
Create a new, emptyDetachedPermazenTransaction
backed by aMemoryKVStore
.The returned
DetachedPermazenTransaction
does not supportcommit()
orrollback()
, and can be used indefinitely.- Parameters:
validationMode
- theValidationMode
to use for the detached transaction- Returns:
- initially empty detached transaction
-
createDetachedTransaction
public DetachedPermazenTransaction createDetachedTransaction(KVStore kvstore, ValidationMode validationMode) Create a newDetachedPermazenTransaction
based on the provided key/value store.The key/value store will be initialized if necessary (i.e.,
kvstore
may be empty), otherwise it will be validated against the schema information associated with this instance.The returned
DetachedPermazenTransaction
does not supportcommit()
orrollback()
, and can be used indefinitely.If
kvstore
is aCloseableKVStore
, then it will beclose()
'd if/when the returnedDetachedPermazenTransaction
is.- Parameters:
kvstore
- key/value store, empty or having content compatible with this transaction'sPermazen
validationMode
- theValidationMode
to use for the detached transaction- Returns:
- detached transaction based on
kvstore
- Throws:
SchemaMismatchException
- ifkvstore
contains incompatible or missing schema informationInconsistentDatabaseException
- if inconsistent or invalid meta-data is detected in the databaseIllegalArgumentException
- ifkvstore
orvalidationMode
is null
-
buildTransactionConfig
Build theTransactionConfig
for a new core API transaction.- Returns:
- core API transaction config
- Throws:
InvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getSchemaModel
Get theSchemaModel
associated with this instance derived from the annotations on the scanned classes, including actual storage ID assignments.Equivalent to:
getSchemaModel
(true)
.- Returns:
- schema model used by this database
- Throws:
InvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getSchemaModel
Get theSchemaModel
associated with this instance derived from the annotations on the scanned classes.- Parameters:
withStorageIds
- true to include actual storage ID assignments- Returns:
- schema model used by this database
- Throws:
InvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getPermazenClassesByName
Get allPermazenClass
's associated with this instance, indexed by object type name.- Returns:
- read-only mapping from object type name to
PermazenClass
- Throws:
InvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getPermazenClass
Get thePermazenClass
associated with the given object type name.- Parameters:
typeName
- object type name- Returns:
PermazenClass
instance- Throws:
UnknownTypeException
- iftypeName
is unknownIllegalArgumentException
- iftypeName
is nullInvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getPermazenClassesByStorageId
Get allPermazenClass
's associated with this instance, indexed by storage ID.- Returns:
- read-only mapping from storage ID to
PermazenClass
- Throws:
InvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getPermazenClassesByType
Get allPermazenClass
's associated with this instance, indexed by Java model type.- Returns:
- read-only mapping from Java model type to
PermazenClass
- Throws:
InvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getPermazenClass
Get thePermazenClass
modeled by the given type.- Type Parameters:
T
- Java model type- Parameters:
type
- an annotated Java object model type- Returns:
- associated
PermazenClass
- Throws:
IllegalArgumentException
- iftype
is not equal to a known Java object model typeInvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
findPermazenClass
Find the most specificPermazenClass
for which the give type is a sub-type of the corresponding Java model type.- Type Parameters:
T
- Java model type or subtype thereof- Parameters:
type
- (sub)type of some Java object model type- Returns:
- narrowest
PermazenClass
whose Java object model type is a supertype oftype
, or null if none found - Throws:
InvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getPermazenClass
Get thePermazenClass
associated with the object ID.- Parameters:
id
- object ID- Returns:
PermazenClass
instance- Throws:
UnknownTypeException
- ifid
has a type that is not defined in this instance's schemaIllegalArgumentException
- ifid
is nullInvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getPermazenClass
Get thePermazenClass
associated with the given storage ID.- Parameters:
storageId
- object type storage ID- Returns:
PermazenClass
instance- Throws:
UnknownTypeException
- ifstorageId
does not represent an object typeInvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
getPermazenClasses
Get allPermazenClass
es which sub-type the given type.- Type Parameters:
T
- Java model type- Parameters:
type
- type restriction, or null for no restrction- Returns:
- list of
PermazenClass
es whose type istype
or a sub-type, ordered by storage ID - Throws:
InvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails
-
parseReferencePath
Parse aReferencePath
starting from a Java type.Roughly equivalent to:
this.parseReferencePath(this.getPermazenClasses(startType), path)
.- Parameters:
startType
- starting Java type for the pathpath
- reference path in string form- Returns:
- parsed reference path
- Throws:
IllegalArgumentException
- if no model types are instances ofstartType
IllegalArgumentException
- ifpath
is invalidIllegalArgumentException
- if either parameter is nullInvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails- See Also:
-
parseReferencePath
Parse aReferencePath
starting from a set of model object types.- Parameters:
startTypes
- starting model types for the path, with null meaningUntypedPermazenObject
path
- reference path in string form- Returns:
- parsed reference path
- Throws:
IllegalArgumentException
- ifstartTypes
is empty or contains nullIllegalArgumentException
- ifpath
is invalidIllegalArgumentException
- if either parameter is nullInvalidSchemaException
- if this instance is not yetinitialized
and schema registration fails- See Also:
-