Keys are defined on Managed Objects using annotations. The annotation to define a key captures this information:
Name of key.
List of one or more fields making up the key.
Property indicating whether a key is unique or non-unique.
Property indicating whether a key is ordered or unordered.
Property indicating whether key is mutable or not.
Here is a snippet of defining a unique unordered key on a Managed Object.
Example 5.13. Managed Object with Unique Keys
// $Revision: 1.1.2.3 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.KeyList; import com.kabira.platform.annotation.Managed; /** * Defining a managed object with unique keys. Key fields must be declared as * final. * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ @Managed @KeyList(keys = { @Key(name = "ByName", fields = { "name" }, unique = true, ordered = false), @Key(name = "ByIdentifier", fields = { "identifier" }, unique = true, ordered = false) }) public class Keys { Keys(String name, int identifier) { this.name = name; this.identifier = identifier; } final String name; final int identifier; }
Key fields are inherited from parent classes. Immutable key fields
must be defined as non-static final
. Any field type supported in Managed Objects (see the section called “Supported field types”) can be a key field.
The only exceptions are:
Array fields
By-value field (
annotation)@ByValue
By-reference field (
annotation)@ByReference
Once a key is defined on a Managed Object, an index is maintained in shared memory for that key. This index is used to perform queries against the Managed Object by specifying the key values that should be used in the query. For example queries see the section called “Queries”.
Two annotations are used to define keys:
A single key can be defined using the @KeyList
annotation, but the @Key
annotation is provided to
support more concise single key definitions.
Example 5.14, “@Key Annotation” shows the definition of the
@Key
annotation and its usage.
Example 5.14. @Key Annotation
package com.kabira.platform.annotation; import java.lang.annotation.*; /** This annotation defines a single key for a Managed class. */ @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Key { /** The name of this key on the given type. Key names must be unique * for a single type (including inherited keys). */ String name(); /** An ordered list of the fields that make up this key. The fields * must be defined in this class or in a superclass. */ String[] fields(); /** If true, the runtime will enforce that only one instance will contain * the key data. */ boolean unique() default true; /** If true, the key data will be managed as an ordered set. */ boolean ordered() default false; /** If true, the key fields can be updated. */ boolean mutable() default false; } // // Use of @Key annotation // @Managed @Key ( name = "ByName", fields = { "name" }, unique = true, ordered = false, mutable = true ) public class Keys { Keys(String name) { this.name = name; } final String name; }
Example 5.15, “@KeyList Annotation” shows the definition of
the @KeyList
annotation and its usage.
Example 5.15. @KeyList Annotation
package com.kabira.platform.annotation; import java.lang.annotation.*; /** This annotation is used to define multiple keys on a single Managed class. */ @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface KeyList { Key[] keys(); } // // Use of @KeyList annotation // @Managed @KeyList(keys= { @Key(name="ByName",fields={"name"},unique=true,ordered=false), @Key(name="ByIdentifier",fields={"identifier"},unique=true,ordered=false) }) public class Keys { Keys(String name, int identifier) { this.name = name; this.identifier = identifier; } final String name; final int identifier; }
Managed Object keys have the following restrictions:
@Key
and @KeyList
cannot
be used on the same class.
@KeyList
must be of non-zero length (at
least one @Key
must be defined).
@Key
and @KeyList
can
only be used on a class marked @Managed
or a
class extending a managed class.
All fields defined in each @Key
annotation
must exist in the class, or in a superclass.
All immutable key fields must be declared
final
and non-static.
Mutable key fields must be declared as non-static.
The key name must be unique within its class and among all inherited keys.
Key restrictions are audited when the class is loaded. If any key
definitions in the class fail audit, a
java.lang.ClassNotFoundException
is thrown.
Key definitions are inherited by child classes. Key names must be unique in a class hierarchy. Inherited fields can be used in key definitions in child classes.
Example 5.16, “Inherited keys” shows inherited keys being used.
Example 5.16. Inherited keys
// $Revision: 1.1.4.1 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.KeyFieldValueList; import com.kabira.platform.KeyManager; import com.kabira.platform.KeyQuery; import com.kabira.platform.LockMode; import com.kabira.platform.Transaction; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.Managed; /** * Inherited keys * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ public class InheritedKeys { final static String Version = "version 3.14159265359"; @Managed @Key(name = "ByVersion", fields = { "version" }, ordered = false, unique = false) private static class Base { final String version; Base(String version) { this.version = version; } void describe() { System.out.println(getClass().getName() + " " + this); } } /** * Extend Base - inherits the ByVersion key */ private static class Extension1 extends Base { Extension1(String version) { super(version); } } private static class Extension2 extends Base { Extension2(String version) { super(version); } } public static void main(String[] args) { createObjects(); showAllObjects(); showExtension1Objects(); showExtension2Objects(); } static void createObjects() { new Transaction() { @Override public void run() { new Extension1(Version); new Extension1(Version); new Extension2(Version); } }.execute(); } static void showAllObjects() { new Transaction() { @Override public void run() { KeyManager<Base> keyManager = new KeyManager<Base>(); KeyQuery<Base> keyQuery = keyManager.createKeyQuery( Base.class, "ByVersion"); KeyFieldValueList keyFieldValueList = new KeyFieldValueList(); keyFieldValueList.add("version", Version); keyQuery.defineQuery(keyFieldValueList); System.out.println("Select against Base"); for (Base obj : keyQuery.getResults(LockMode.READLOCK)) { obj.describe(); } } }.execute(); } static void showExtension1Objects() { new Transaction() { @Override public void run() { KeyManager<Extension1> keyManager = new KeyManager<Extension1>(); KeyQuery<Extension1> keyQuery = keyManager.createKeyQuery( Extension1.class, "ByVersion"); KeyFieldValueList keyFieldValueList = new KeyFieldValueList(); keyFieldValueList.add("version", Version); keyQuery.defineQuery(keyFieldValueList); System.out.println("Select against Extension1"); for (Extension1 obj : keyQuery.getResults(LockMode.READLOCK)) { obj.describe(); } } }.execute(); } static void showExtension2Objects() { new Transaction() { @Override public void run() { KeyManager<Extension2> keyManager = new KeyManager<Extension2>(); KeyQuery<Extension2> keyQuery = keyManager.createKeyQuery( Extension2.class, "ByVersion"); KeyFieldValueList keyFieldValueList = new KeyFieldValueList(); keyFieldValueList.add("version", Version); keyQuery.defineQuery(keyFieldValueList); System.out.println("Select against Extension2"); for (Extension2 obj : keyQuery.getResults(LockMode.READLOCK)) { obj.describe(); } } }.execute(); } }
When this snippet is run it outputs (annotation added):
// // Querying against base returns all instances // [A] Select against Base [A] com.kabira.snippets.managedobjects.InheritedKeys$Extension2 com.kabira.snippets.managedobjects.InheritedKeys$Extension2@6733d4b8 [A] com.kabira.snippets.managedobjects.InheritedKeys$Extension1 com.kabira.snippets.managedobjects.InheritedKeys$Extension1@42bb4baf [A] com.kabira.snippets.managedobjects.InheritedKeys$Extension1 com.kabira.snippets.managedobjects.InheritedKeys$Extension1@d3743e75 // // Querying against Extension1 only returns Extension1 instances // [A] Select against Extension1 [A] com.kabira.snippets.managedobjects.InheritedKeys$Extension1 com.kabira.snippets.managedobjects.InheritedKeys$Extension1@42bb4baf [A] com.kabira.snippets.managedobjects.InheritedKeys$Extension1 com.kabira.snippets.managedobjects.InheritedKeys$Extension1@d3743e75 // // Querying against Extension2 only returns Extension1 instances // [A] Select against Extension2 [A] com.kabira.snippets.managedobjects.InheritedKeys$Extension2 com.kabira.snippets.managedobjects.InheritedKeys$Extension2@6733d4b8
By default key values are immutable once an object has
been created. However, it is possible to specify that a key value can be
changed after an object has been created. This is done with the
mutable
field in the key annotation. If a key is
modified after it has been created, the
KeyManager.updateIndexes(...)
method must be called
after the key value is modified. If this method is not called after a
key modification a runtime exception is thrown when the transaction is
committed and the node is brought down.
![]() | |
The |
Example 5.17, “Modifying key values” shows how key values are modified.
Example 5.17. Modifying key values
// $Revision: 1.1.2.1 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.KeyFieldValueList; import com.kabira.platform.KeyManager; import com.kabira.platform.KeyQuery; import com.kabira.platform.LockMode; import com.kabira.platform.ManagedObject; import com.kabira.platform.Transaction; import com.kabira.platform.Transaction.Rollback; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.Managed; /** * Define a managed object with a mutable key. <p> <h2> Target Nodes</h2> <ul> * <li> <b>domainnode</b> = A </ul> */ @Key( name = "ByName", unique = true, ordered = false, mutable = true, fields = { "name" }) @Managed public class MutableKeys { public MutableKeys(final String name) { this.name = name; } String name; public static void main(String[] args) { // // Create an instance // new Transaction("Create") { @Override protected void run() throws Rollback { new MutableKeys("Jim"); } }.execute(); // // Modify the key value // new Transaction("Modify") { @Override protected void run() throws Rollback { MutableKeys m = lookup("Jim"); System.out.println("Name is " + m.name); // // Update the key value // m.name = "Bob"; KeyManager.updateIndexes(m); } }.execute(); // // Look up the object using the new key value // new Transaction("Lookup") { @Override protected void run() throws Rollback { MutableKeys m = lookup("Bob"); System.out.println("Name is " + m.name); ManagedObject.delete(m); } }.execute(); } // // Lookup the object // private static MutableKeys lookup(final String name) { KeyManager<MutableKeys> km = new KeyManager<MutableKeys>(); KeyQuery<MutableKeys> kq = km.createKeyQuery( MutableKeys.class, "ByName"); KeyFieldValueList fields = new KeyFieldValueList(); fields.add("name", name); kq.defineQuery(fields); return kq.getSingleResult(LockMode.READLOCK); } }
When this snippet is run it outputs:
[A] Name is Jim [A] Name is Bob
It is not possible to create multiple instances of a uniquely keyed Managed Object. Creating a second instance of a uniquely keyed Managed Object will cause this exception:
com.kabira.platform.ObjectNotUniqueError
When a duplicate key exception is thrown it indicates that the object was not created.
See the section called “Atomic Create or Select” for the safe way to create uniquely keyed objects in the TIBCO ActiveSpaces® Transactions multi-threaded environment.
Example 5.18, “Duplicate Key Exception” shows the handling of a duplicate key exception.
Example 5.18. Duplicate Key Exception
// $Revision: 1.1.2.1 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.ManagedObject; import com.kabira.platform.ObjectNotUniqueError; import com.kabira.platform.Transaction; import com.kabira.platform.Transaction.Rollback; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.Managed; /** * Duplicate keys * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ public class DuplicateKey { /** * A managed object with a unique key */ @Managed @Key ( name = "ByNumber", fields = { "number" }, unique = true, ordered = false ) public static class MyObject { /** * Create MyObject * * @param number Initialize number field */ public MyObject(int number) { this.number = number; } final int number; } /** * Main entry point * @param args Not used */ public static void main(String [] args) { new Transaction("Duplicate Object") { @Override protected void run() throws Rollback { // // Create object with a key value of 1 // MyObject myObject = new MyObject(1); // // Create another object with the same key value // try { new MyObject(1); } catch (ObjectNotUniqueError ex) { System.out.println(ex.getMessage()); } ManagedObject.delete(myObject); } }.execute(); } }
When this snippet is run it outputs:
[A] Duplicate found for key 'com.kabira.snippets.managedobjects.DuplicateKey$MyObject::ByNumber' in com.kabira.snippets.managedobjects.DuplicateKey$MyObject, instance 1247018:5286456:2001:2. Duplicate is com.kabira.snippets.managedobjects.DuplicateKey$MyObject, instance 1247018:5286456:2001:1. Key data: [A] [ [A] [A] number = 1 [A] ]
The following classes provide the query interface for managed objects.
com.kabira.platform.KeyManager<T>
- Factory to create query objects.
com.kabira.platform.KeyQuery<T>
- Query object. Used to define and execute queries.
com.kabira.platform.QueryScope
-
Support for a user-defined query scope.
com.kabira.platform.KeyFieldValueList
- Used to define key name/value pairs.
com.kabira.platform.KeyFieldValueRangeList
- Used to define key name/value pairs that have a range.
These classes are generally used in this order to define and execute a query.
Create a KeyManager<T>
object.
Create a KeyQuery<T>
object.
Create a KeyFieldValueList
object.
Add key name/value pairs to the
KeyFieldValueList
object to define the query
values.
Define the query using the
KeyQuery<T>
and the
KeyFieldValueList
objects.
Optionally define a user-defined query scope using a
QueryScope
object.
Execute the query using the
KeyQuery<T>
object.
Once a query is defined, it can be used multiple times.
Indexes defined by keys support a transaction isolation of SERIALIZABLE. This means that all updates to an index are transactionally isolated from all other transactions. Any modifications to an index caused by the creation or deletion of a keyed Managed Object is not visible outside of the current transaction until it commits.
The specific isolation rules for indexes are as follows:
Creates are always visible in the transaction in which they occur
Deletes are always visible in the transaction in which they occur
Creates are visible outside of the transaction in which they were executed after the transaction commits
Deletes are visible outside of the transaction in which they were executed after the transaction commits
The locking of objects returned from queries is explicit. When a query is executed the specific locking that should be applied to the objects returned from the query can be specified in the query API.
![]() | |
Non-cached remote objects returned from a query are always write locked and created in the current transaction, because they must be created on the local node before returning the result set. See Example 5.19, “Cluster Extent Query” for an example. |
Queries are executed on the local node, a user-defined subset of nodes in the cluster, or all active nodes in a cluster. By default queries are executed on the local node and will only return objects in the local node's index.
![]() | |
When a query is executed on multiple nodes, the application must be running on all nodes on which the query is executed. Remote queries executed on nodes on which the application is not running will hang until the application is started. |
Query scope is supported for:
All queries using a key, including atomic creates or select (see the section called “Atomic Create or Select”).
Extents
Extent cardinality
The query scope is defined using the
com.kabira.platform.QueryScope
class. As mentioned,
the possible query scopes are:
QueryScope.QUERY_CLUSTER
- all active
nodes in the cluster.
QueryScope.QUERY_LOCAL
- the local node
only, the default query scope.
A user-defined subset of cluster nodes.
When a user-defined query scope is defined using the
QueryScope
class, the following audits can be
specified using the QueryScope.AuditMode
enumeration:
AuditMode.AUDIT_NODE_LIST
- Audit that at
least one node is defined in the query scope. No other audits are
performed. Inactive nodes are ignored when the query is
performed.
AuditMode.AUDIT_DISTRIBUTION
- Audit that
at least one node is defined in the query scope and that
distribution is active. Inactive nodes are ignored when the query
is performed.
AuditMode.AUDIT_NODES_ACTIVE
- Audit that
at least one node is defined in the query scope, distribution is
active, and all nodes are active. An error is reported if there
inactive nodes are included in the query scope when the query is
performed.
Query scopes are defined using the following methods:
ManagedObject.extent(...)
for extent
queries.
ManagedObject.cardinality(...)
for extent
cardinality.
KeyQuery.setQueryScope(...)
for all key
queries.
Example 5.19, “Cluster Extent Query” demonstrates a cluster wide extent query.
Example 5.19. Cluster Extent Query
// $Revision: 1.1.2.4 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.LockMode; import com.kabira.platform.ManagedObject; import com.kabira.platform.QueryScope; import com.kabira.platform.Transaction; import com.kabira.platform.Transaction.Rollback; import com.kabira.platform.annotation.Managed; import com.kabira.platform.property.Status; /** * Distributed query * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainname</b> = Development * </ul> */ public class DistributedQuery { @Managed private static class Node { public Node(String name) { this.name = name; } final String name; } /** * Run the distributed query snippet * @param args None supported */ public static void main(String [ ] args) throws InterruptedException { // // Create a node object in the local node // new Transaction("Create Node Objects") { @Override protected void run() throws Rollback { new Node(System.getProperty(Status.NODE_NAME)); } }.execute(); // // Sleep some to let other nodes run // Thread.sleep(1000); // // Find all node objects created in the cluster // // N.B. - an indeterminate number of nodes will // be returned from the extent query in this snippet // because there is no synchronization of the object creation // above across the nodes. // new Transaction("Query Objects") { @Override protected void run() throws Rollback { for (Node n : ManagedObject.extent( Node.class, QueryScope.QUERY_CLUSTER, LockMode.READLOCK)) { System.out.println("INFO: Node " + n.name + lockType(n)); } } }.execute(); } private static String lockType(final Node node) { String lockType = " ("; if (Transaction.hasReadLock(node) == true) { lockType += "READ"; } if (Transaction.hasWriteLock(node) == true) { lockType += " WRITE"; } if (Transaction.createdInTransaction(node) == true) { lockType += " CREATED"; } lockType += ")"; return lockType; } }
When this snippet is run it outputs:
// // Notice that the local objects are only READ locked // Objects read from remote nodes are both WRITE locked and // CREATED in the current transaction // [B] INFO: Node B (READ) [B] INFO: Node C (READ WRITE CREATED) [B] INFO: Node A (READ WRITE CREATED) [A] INFO: Node B (READ WRITE CREATED) [A] INFO: Node A (READ) [A] INFO: Node C (READ WRITE CREATED) [C] INFO: Node B (READ WRITE CREATED) [C] INFO: Node C (READ) [C] INFO: Node A (READ WRITE CREATED)
Example 5.20, “Unique Query” demonstrates how to define and use a single unique key to perform a query.
Example 5.20. Unique Query
// $Revision: 1.1.2.1 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.KeyFieldValueList; import com.kabira.platform.KeyManager; import com.kabira.platform.KeyQuery; import com.kabira.platform.LockMode; import com.kabira.platform.ManagedObject; import com.kabira.platform.Transaction; import com.kabira.platform.Transaction.Rollback; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.Managed; /** * Unique key query * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ public class UniqueKey { /** * A managed object with a unique key */ @Managed @Key ( name = "ByNumber", fields = { "number" }, unique = true, ordered = false ) private static class MyObject { /** * Create MyObject * * @param number Initialize number field * @param description Initialize description field */ public MyObject(int number, String description) { this.number = number; this.description = description; } final int number; final String description; } /** * Main entry point * @param args Not used */ public static void main(String [] args) { new Transaction("Unique Key") { @Override protected void run() throws Rollback { // // Create some keyed objects // new MyObject(1, "one"); new MyObject(2, "two"); // // Create required query objects // KeyManager<MyObject> keyManager = new KeyManager<MyObject>(); KeyQuery<MyObject> keyQuery = keyManager.createKeyQuery( MyObject.class, "ByNumber"); KeyFieldValueList keyFieldValueList = new KeyFieldValueList(); // // Define the query to find object 1 // keyFieldValueList.add("number", 1); keyQuery.defineQuery(keyFieldValueList); System.out.println(keyQuery); // // Perform the query - the returned object has a write lock // MyObject myObject = keyQuery.getSingleResult(LockMode.WRITELOCK); System.out.println("\t" + myObject.description); ManagedObject.delete(myObject); // // Redefine the query to find object 2. Clear the previous value // before reusing the keyFieldValueList // keyFieldValueList.clear(); keyFieldValueList.add("number", 2); keyQuery.defineQuery(keyFieldValueList); System.out.println(keyQuery); // // Perform the query - the returned object has a write lock // myObject = keyQuery.getSingleResult(LockMode.WRITELOCK); System.out.println("\t" + myObject.description); ManagedObject.delete(myObject); } }.execute(); } }
When this snippet is run it outputs:
[A] select obj from com.kabira.snippets.managedobjects.UniqueKey$MyObject using ByNumber where (number == 1); Java constructor: (none) [A] one [A] select obj from com.kabira.snippets.managedobjects.UniqueKey$MyObject using ByNumber where (number == 2); Java constructor: (none) [A] two
Example 5.21, “Non-Unique Query” demonstrates how to define and use a single non-unique key to perform a query. Non-unique keys must use a for loop to iterate over all returned instances.
Example 5.21. Non-Unique Query
// $Revision: 1.1.2.1 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.KeyFieldValueList; import com.kabira.platform.KeyManager; import com.kabira.platform.KeyQuery; import com.kabira.platform.LockMode; import com.kabira.platform.ManagedObject; import com.kabira.platform.Transaction; import com.kabira.platform.Transaction.Rollback; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.Managed; /** * Non-unique key query * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ public class NonUniqueKey { /** * A managed object with a non-unique key */ @Managed @Key(name = "ByGroup", fields = { "group" }, unique = false, ordered = false) private static class MyObject { /** * A non-unique, unordered object * @param group Group key * @param description Description */ public MyObject(int group, String description) { this.group = group; this.description = description; } final int group; final String description; } /** * Main entry point * @param args Not used */ public static void main(String[] args) { new Transaction("Non-Unique Key") { @Override protected void run() throws Rollback { // // Create some keyed objects. Notice that a duplicate // key value is specified. // new MyObject(1, "first one"); new MyObject(1, "second one"); new MyObject(2, "third one"); // // Create required query objects // KeyManager<MyObject> keyManager = new KeyManager<MyObject>(); KeyQuery<MyObject> keyQuery = keyManager.createKeyQuery( MyObject.class, "ByGroup"); KeyFieldValueList keyFieldValueList = new KeyFieldValueList(); // // Define the query to find all objects with a key value of 1 // keyFieldValueList.add("group", 1); keyQuery.defineQuery(keyFieldValueList); System.out.println(keyQuery); // // Perform the query - we need to use a for loop to iterate over // the objects. We take a write lock as we execute the query. // for (MyObject myObject : keyQuery.getResults(LockMode.WRITELOCK)) { System.out.println("\t" + myObject.description); ManagedObject.delete(myObject); } } }.execute(); } }
When this snippet is run it outputs:
[A] for obj in com.kabira.snippets.managedobjects.NonUniqueKey$MyObject using ByGroup where (group == 1) { } [A] second one [A] first one
Ordered indexes allow sorting of selected objects in either ascending or descending order. They also support finding the minimum and maximum values in an index. Ordered queries can be done on unique and non-unique keys. They are also supported on multi-field keys. This allows the result set to be selectively ordered based on partial key values.
Table 5.1, “Sort order” summarizes the sort order for all supported Managed Object field types.
![]() | |
Lexicographical sorting is only supported for single-byte character sets. Multi-byte characters are treated as single-byte characters when sorting. |
Table 5.1. Sort order
Type | Sort Order |
boolean and
Boolean | false before
true |
char and
Character | Lexicographical order. |
byte and
Byte | Numeric order. |
Integer Types (short and
Short , int and
Integer , long and
Long ) | Numeric order. |
Floating Point Types (float and
Float , double and
Double ) | Numeric order. |
java.lang.String | Lexicographical order. |
java.lang.Date | Date order. |
Enumerations | Enumeration ordinal value order. |
Managed object references | Undefined. |
Example 5.22, “Ordered Query” demonstrates how to define a multi-field unique key to perform ordered queries.
Example 5.22. Ordered Query
// $Revision: 1.1.2.1 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.KeyFieldValueList; import com.kabira.platform.KeyManager; import com.kabira.platform.KeyOrderedBy; import com.kabira.platform.KeyQuery; import com.kabira.platform.LockMode; import com.kabira.platform.ManagedObject; import com.kabira.platform.Transaction; import com.kabira.platform.Transaction.Rollback; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.Managed; /** * Unique ordered key query * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ public class Ordered { /** * A managed object with a unique multi-part key */ @Managed @Key(name = "ByGroupDescription", fields = { "group", "description" }, unique = true, ordered = true) private static class MyObject { /** * A non-unique ordered object * @param group Group * @param description Description */ public MyObject(int group, String description) { this.group = group; this.description = description; } final int group; final String description; } /** * Main entry point * @param args Not used */ public static void main(String[] args) { new Transaction("Ordered Query") { @Override protected void run() throws Rollback { // // Create some keyed objects. These objects are unique because // both the group and description field are part of the key // new MyObject(1, "a"); new MyObject(1, "b"); new MyObject(1, "c"); new MyObject(2, "d"); new MyObject(2, "e"); new MyObject(2, "f"); // // Create required query objects // KeyManager<MyObject> keyManager = new KeyManager<MyObject>(); KeyQuery<MyObject> keyQuery = keyManager.createKeyQuery( MyObject.class, "ByGroupDescription"); KeyFieldValueList keyFieldValueList = new KeyFieldValueList(); // // Define the query to find all objects with a key value of 1 // Notice that no field value is specified for the description // field. This will cause all objects with a group value of // 1 to be sorted on the description field. // keyFieldValueList.add("group", 1); keyQuery.defineQuery(keyFieldValueList); System.out.println(keyQuery); // // Get the maximum value in group 1 // System.out.println("Maximum Group 1 Value:"); MyObject mo = keyQuery.getMaximumResult(LockMode.READLOCK); System.out.println("\t" + mo.description); // // Get the minimum value in group 1 // System.out.println("Minimum Group 1 Value:"); mo = keyQuery.getMinimumResult(LockMode.READLOCK); System.out.println("\t" + mo.description); // // Perform an ordered descending query on group 1 taking // a write lock // System.out.println("Descending Group 1 Query:"); for (MyObject myObject : keyQuery.getResults( KeyOrderedBy.DESCENDING, LockMode.WRITELOCK)) { System.out.println("\t" + myObject.description); } // // Perform an ordered ascending query on group 1 taking // a write lock // System.out.println("Ascending Group 1 Query:"); for (MyObject myObject : keyQuery.getResults( KeyOrderedBy.ASCENDING, LockMode.WRITELOCK)) { System.out.println("\t" + myObject.description); } // // Repeat the queries for Group 2 // keyFieldValueList.clear(); keyFieldValueList.add("group", 2); keyQuery.defineQuery(keyFieldValueList); System.out.println(keyQuery); System.out.println("Maximum Group 2 Value:"); mo = keyQuery.getMaximumResult(LockMode.READLOCK); System.out.println("\t" + mo.description); System.out.println("Minimum Group 2 Value:"); mo = keyQuery.getMinimumResult(LockMode.READLOCK); System.out.println("\t" + mo.description); System.out.println("Descending Group 2 Query:"); for (MyObject myObject : keyQuery.getResults( KeyOrderedBy.DESCENDING, LockMode.WRITELOCK)) { System.out.println("\t" + myObject.description); } System.out.println("Ascending Group 2 Query:"); for (MyObject myObject : keyQuery.getResults( KeyOrderedBy.ASCENDING, LockMode.WRITELOCK)) { System.out.println("\t" + myObject.description); } // // Remove all of the objects // for (MyObject myObject : ManagedObject.extent(MyObject.class)) { ManagedObject.delete(myObject); } } }.execute(); } }
When this snippet is run it outputs:
[A] for obj in com.kabira.snippets.managedobjects.Ordered$MyObject using ByGroupDescription where (group == 1) { } [A] Maximum Group 1 Value: [A] c [A] Minimum Group 1 Value: [A] a [A] Descending Group 1 Query: [A] c [A] b [A] a [A] Ascending Group 1 Query: [A] a [A] b [A] c [A] for obj in com.kabira.snippets.managedobjects.Ordered$MyObject using ByGroupDescription where (group == 2) { } [A] Maximum Group 2 Value: [A] f [A] Minimum Group 2 Value: [A] d [A] Descending Group 2 Query: [A] f [A] e [A] d [A] Ascending Group 2 Query: [A] d [A] e [A] f
The snippet shows how to perform range queries on an ordered index.
Example 5.23. Range Query
// $Revision: 1.1.2.1 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.KeyComparisonOperator; import com.kabira.platform.KeyFieldValueRangeList; import com.kabira.platform.KeyManager; import com.kabira.platform.KeyOrderedBy; import com.kabira.platform.KeyQuery; import com.kabira.platform.LockMode; import com.kabira.platform.ManagedObject; import com.kabira.platform.Transaction; import com.kabira.platform.Transaction.Rollback; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.Managed; /** * Range query * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ public class Range { /** * A managed object with a unique multi-part key */ @Managed @Key ( name = "ByNumberDescription", fields = { "number", "description" }, unique = true, ordered = true ) private static class MyObject { /** * Create MyObject * * @param number Initialize number field * @param description Initialize description field */ public MyObject(int number, String description) { this.number = number; this.description = description; } final int number; final String description; } /** * Main entry point * @param args Not used */ public static void main(String [] args) { new Transaction("Range Query") { @Override protected void run() throws Rollback { // // Create some keyed objects. These objects are unique because // both the number and description field are part of the key // new MyObject(1, "a"); new MyObject(1, "b"); new MyObject(1, "c"); new MyObject(2, "a"); new MyObject(2, "b"); new MyObject(3, "a"); new MyObject(4, "a"); new MyObject(5, "a"); // // Create required query objects // KeyManager<MyObject> keyManager = new KeyManager<MyObject>(); KeyQuery<MyObject> keyQuery = keyManager.createKeyQuery( MyObject.class, "ByNumberDescription"); KeyFieldValueRangeList keyFieldValueRangeList = new KeyFieldValueRangeList(); // // Define the query to find all objects with a number value < 3 // keyFieldValueRangeList.add("number", 3, KeyComparisonOperator.LT); keyQuery.defineQuery(keyFieldValueRangeList); System.out.println(keyQuery); for (MyObject myObject : keyQuery.getResults(LockMode.NOLOCK)) { System.out.println( "\t" + myObject.number + ":" + myObject.description); } // // Define the query to find all objects with a number between 1 and 5, // then sort them in descending order // keyFieldValueRangeList.clear(); keyFieldValueRangeList.add("number", 1, KeyComparisonOperator.GT); keyFieldValueRangeList.add("number", 5, KeyComparisonOperator.LT); keyQuery.defineQuery(keyFieldValueRangeList); System.out.println(keyQuery); for (MyObject myObject : keyQuery.getResults( KeyOrderedBy.DESCENDING, LockMode.NOLOCK)) { System.out.println( "\t" + myObject.number + ":" + myObject.description); } for (MyObject myObject : ManagedObject.extent(MyObject.class)) { ManagedObject.delete(myObject); } } }.execute(); } }
When this snippet is run it outputs:
[A] for obj in com.kabira.snippets.managedobjects.Range$MyObject using ByNumberDescription where (number < 3) { } [A] 1:a [A] 1:b [A] 1:c [A] 2:a [A] 2:b [A] for obj in com.kabira.snippets.managedobjects.Range$MyObject using ByNumberDescription where (number > 1 && number < 5) { } [A] 4:a [A] 3:a [A] 2:b [A] 2:a
Often an application wants to atomically select a unique object
if it exists, and if it does not exist, to create the instance. The
query interface provides a mechanism to do this using the
method. Query scope (see the section called “Query Scope”) can be
used to select or create a unique instance on only the local node, a
set of nodes in the cluster, or all nodes in the cluster. The
KeyQuery<T>.getOrCreateSingleResult
method always returns an object to the caller.KeyQuery<T>.getOrCreateSingleResult
The KeyQuery<T>.getOrCreateSingleResult
method works even when multiple threads may be selecting the same
instance simultaneously. If multiple threads are attempting to perform
a select for an object that does not exist, one of the threads will
create the new instance, and all other threads will block until the
transaction in which the object was created commits. The other threads
are released after the transaction completes and the select will
return the new instance.
The lock mode specified in the the
KeyQuery<T>.getOrCreateSingleResult
method is
only used if the object already exists. It is ignored if the object
was created. Newly created objects always have a transactional write
lock.
See the section called “Constructors” for details on
constructor execution when an object is created by the
KeyQuery<T>.getOrCreateSingleResult
method.
Example 5.24, “Atomic Create of Unique Keyed Object”
demonstrates how to locate a unique instance on the local node, either
by selecting or instantiating the object. It also shows how to determine whether the object was
created or selected using the Transaction.createdInTransaction(Object
object)
and Transaction.modifiedInTransaction(Object
object)
methods.
Example 5.24. Atomic Create of Unique Keyed Object
// $Revision: 1.1.2.4 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.KeyFieldValueList; import com.kabira.platform.KeyManager; import com.kabira.platform.KeyQuery; import com.kabira.platform.LockMode; import com.kabira.platform.Transaction; import com.kabira.platform.ManagedObject; import com.kabira.platform.QueryScope; import com.kabira.platform.Transaction.Rollback; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.Managed; import com.kabira.platform.annotation.KeyField; /** * Atomically creating a uniquely keyed object * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ public class AtomicCreate { /** * A managed object with a unique key */ @Managed @Key(name = "ByNumber", fields = { "number" }, unique = true, ordered = false) private static class MyObject { /** * Create MyObject and initialize description * * @param aNumber Initialize number field * @param aDescription Initialize description field */ public MyObject( @KeyField(fieldName = "number") int aNumber, @KeyField(fieldName = "description") String aDescription) { this.number = aNumber; this.description = aDescription; } /** * Create MyObject with default description * * @param number Initialize number field */ public MyObject( @KeyField(fieldName = "number") int number) { this.number = number; this.description = "default description"; } final int number; final String description; } /** * Main entry point * * @param args Not used */ public static void main(String[] args) { // // Create an object // new Transaction("Create Object") { @Override protected void run() throws Rollback { // // Create object with a key value of 1 // MyObject myObject = new MyObject(1, "start of world"); displayStatus("Create with constructor", myObject); } }.execute(); new Transaction("Atomic Create") { @Override protected void run() throws Rollback { // // Create required query objects // KeyManager<MyObject> keyManager = new KeyManager<MyObject>(); KeyQuery<MyObject> keyQuery = keyManager.createKeyQuery( MyObject.class, "ByNumber"); KeyFieldValueList keyFieldValueList = new KeyFieldValueList(); KeyFieldValueList additionalFields = new KeyFieldValueList(); // // Set the query scope to local node only (default value) // This could also be set to cluster wide, or a sub-set of // the nodes in the cluster. // keyQuery.setQueryScope(QueryScope.QUERY_LOCAL); // // Define the query to find object 1 which was created above // keyFieldValueList.add("number", 1); keyQuery.defineQuery(keyFieldValueList); // // Set up additionalFields // additionalFields.add("description", "create new object"); // // Atomically select or create a new object. This will return // the original object created above since a key value of 1 is // being used. The Transaction.createdInTransaction // and Transaction.modifiedInTransaction methods // can be used to determine whether the object was // created or selected. Also note that the // description field is the value that was set when the object // was created. // // This method always returns a result and never throws // an ObjectNotUniqueError exception. // System.out.println(keyQuery); MyObject myObject = keyQuery.getOrCreateSingleResult( LockMode.WRITELOCK, additionalFields); displayStatus("Original", myObject); // // Delete the object // ManagedObject.delete(myObject); // // Reset the query to use a value of 2 // keyFieldValueList.clear(); keyFieldValueList.add("number", 2); keyQuery.defineQuery(keyFieldValueList); // // This will return a new object instance because no object // exists with a key value of 2. The description field is // set to the value defined in additionalFields parameter. // // The object is created using the constructor that passes // in description. // System.out.println(keyQuery); myObject = keyQuery.getOrCreateSingleResult( LockMode.WRITELOCK, additionalFields); displayStatus("New Key Value 2", myObject); ManagedObject.delete(myObject); // // Reset the query to use a value of 3 // keyFieldValueList.clear(); keyFieldValueList.add("number", 3); keyQuery.defineQuery(keyFieldValueList); // // This will return a new object instance because no object // exists with a key value of 3. // // The object is created using the constructor without a // description parameter. // System.out.println(keyQuery); myObject = keyQuery.getOrCreateSingleResult(LockMode.WRITELOCK, null); displayStatus("New Key Value 3", myObject); ManagedObject.delete(myObject); } }.execute(); } private static void displayStatus(final String label, final MyObject myObject) { System.out.println("==== " + label + " ==="); System.out.println("Created : " + Transaction.createdInTransaction(myObject)); System.out.println("Modified : " + Transaction.modifiedInTransaction(myObject)); System.out.println(""); } }
When this snippet is run it outputs (annotation added):
# # Object created using new # [A] ==== Create with constructor === [A] Created : true [A] Modified : true [A] Deleted : false [A] Description: start of world # # Select of original object, notice that # created is false and description is # initial value # [A] select obj from com.kabira.snippets.managedobjects.AtomicCreate$MyObject using ByNumber where (number == 1); Java constructor: public com.kabira.snippets.managedobjects.AtomicCreate$MyObject(int) [A] ==== Original === [A] Created : false [A] Modified : false [A] Deleted : false [A] Description: start of world # # Object is deleted. Notice deleted is true # [A] ==== Deleted === [A] Created : false [A] Modified : false [A] Deleted : true [A] # # Object created with getOrCreateSingleResult # using constructor with description parameter # Notice description value was set # [A] select obj from com.kabira.snippets.managedobjects.AtomicCreate$MyObject using ByNumber where (number == 2); Java constructor: public com.kabira.snippets.managedobjects.AtomicCreate$MyObject(int) [A] ==== New Key Value 2 === [A] Created : true [A] Modified : true [A] Deleted : false [A] Description: create new object # # Object created with getOrCreateSingleResult # using constructor without description parameter. # Notice default description value. # [A] select obj from com.kabira.snippets.managedobjects.AtomicCreate$MyObject using ByNumber where (number == 3); Java constructor: public com.kabira.snippets.managedobjects.AtomicCreate$MyObject(int) [A] ==== New Key Value 3 === [A] Created : true [A] Modified : true [A] Deleted : false [A] Description: default description
The
KeyQuery<T>.getOrCreateSingleResult
method
may create a new object. To create the object a constructor is
called on the object being created. Keyed objects have constructors
that at a minimum must have parameters to initialize the key fields.
The constructor may also take additional parameters for other
application specific initialization.
The @KeyField
annotation is used to
map constructor parameters to object fields. If the
@KeyField
annotation is specified, it must be
specified for all parameters in the constructor. If it is not
specified for all parameters an exception is thrown at
runtime.
[A] com.kabira.platform.KeyConstructor$IncompleteAnnotations: Constructor public sandbox.KeyConstructor(java.lang.Integer,java.lang.String) is missing one of more KeyField annotations [A] at com.kabira.platform.KeyConstructor.findAnnotatedConstructor(KeyConstructor.java:275) [A] at com.kabira.platform.KeyConstructor.createObject(KeyConstructor.java:205) [A] at com.kabira.platform.KeyConstructor.invokeConstructor(KeyConstructor.java:105) [A] at com.kabira.platform.KeyQuery.getOrCreateSingleResult(KeyQuery.java:305) [A] at sandbox.DoIt$1.run(KeyConstructor.java:53) [A] at com.kabira.platform.Transaction.execute(Transaction.java:303) [A] at sandbox.DoIt.main(KeyConstructor.java:36) [A] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [A] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) [A] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) [A] at java.lang.reflect.Method.invoke(Method.java:597) [A] at com.kabira.platform.MainWrapper.invokeMain(MainWrapper.java:49)
Here is the definition of the
annotation:@KeyField
package com.kabira.platform.annotation; import java.lang.annotation.*; /** This annotation describes the mapping between constructor arguments * and the key field that the argument sets. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface KeyField { /** The field name this parameter maps to. */ String fieldName(); }
The @KeyField
annotation is not inherited.
It must be specified on every constructor that will be called when
the
KeyQuery<T>.getOrCreateSingleResult(...)
method is used to create objects. The
KeyQuery<T>.getOrCreateSingleResult(...)
only attempts to execute constructors on the actual object type
being created. It does not search for, or excecute, any constructors
inherited from parent types.
![]() | |
If the |
See Example 5.24, “Atomic Create of Unique Keyed Object”
for an example of the @KeyField
annotation.
The
method on a partitioned object, when the query scope is the local
node, has different behavior depending on whether the object being
selected is mapped to a partition definition on the local node (see
Chapter 7, High availability for details on
partitioning) or not.KeyQuery<T>.getOrCreateSingleResult
When the
KeyQuery<T>.getOrCreateSingleResult
method
is called using a local node query scope, if the key does not exist
on the local node, an instance is created with the key. If the
instance is mapped to partition information on the local node, the
create is dispatched to the active node for the partition. If the
key value is found on the active node, the instance found on the
active node is returned to the caller. This is a case where a local
node query scope, actually performs a remote dispatch to ensure
duplicate keys do not occur.
![]() | |
Executing the
|
When a key is inherited by a child type in a class
hierarchy it is possible that a duplicate key exception will be
reported by
KeyQuery<T>.getOrCreateSingleResult
. This
is an application defect, since the same key value is being used for
different child object types with a common shared parent key. Example 5.25, “Duplicate keys with atomic create or select”
demonstrates the problem.
Example 5.25. Duplicate keys with atomic create or select
// $Revision: 1.1.2.1 $ package com.kabira.snippets.managedobjects; import com.kabira.platform.KeyFieldValueList; import com.kabira.platform.KeyManager; import com.kabira.platform.KeyQuery; import com.kabira.platform.LockMode; import com.kabira.platform.Transaction; import com.kabira.platform.annotation.Key; import com.kabira.platform.annotation.KeyField; import com.kabira.platform.annotation.KeyList; import com.kabira.platform.annotation.Managed; /** * Duplicate keys with atomic selects * <p> * <h2> Target Nodes</h2> * <ul> * <li> <b>domainnode</b> = A * </ul> */ public class AtomicCreateDuplicateKey { @Managed @KeyList(keys = { @Key(name = "ByName", unique = true, ordered = false, fields = { "m_name" } ) }) private static class Parent { private final String m_name; Parent(@KeyField(fieldName = "m_name") String name) { this.m_name = name; } @Override public String toString() { return getClass().getSimpleName() + ": " + m_name; } } private static class ChildOne extends Parent { ChildOne(@KeyField(fieldName = "m_name") String name) { super(name); } } private static class ChildTwo extends Parent { ChildTwo(@KeyField(fieldName = "m_name") String name) { super(name); } } /** * Main entry point * * @param args Not used */ public static void main(String [ ] args) { new Transaction("create objects") { @Override public void run() { // // Create an instance using smith as the key // ChildOne c1 = new ChildOne("smith"); System.out.println("INFO: " + c1); // // Uniquely select on key smith, but use type ChildTwo // KeyManager<ChildTwo> km = new KeyManager<>(); KeyQuery<ChildTwo> kq = km.createKeyQuery(ChildTwo.class, "ByName"); KeyFieldValueList fvl = new KeyFieldValueList(); fvl.add("m_name", "smith"); kq.defineQuery(fvl); // // Duplicate key will be thrown because no ChildTwo instance // exists with key smith, but attempting to create instance // causes a duplicate key with ChildOne // ChildTwo c2 = kq.getOrCreateSingleResult(LockMode.WRITELOCK, null); System.out.println("INFO: " + c2); } }.execute(); } }
When this snippet is run it outputs (with annotation added):
# # ChildOne instance created # INFO: ChildOne: smith # # Duplicate key exception when attempting to create ChildTwo with same name # Java main class com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey.main exited with an exception. com.kabira.platform.ObjectNotUniqueError: Duplicate found for key 'com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$Parent::ByName' in com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$ChildTwo, instance 3465071219:692692576:1421967247573317000:125. Duplicate is com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$ChildOne, instance 3465071219:2981644168:1421967247573317000:51. Key data: [ m_name = "smith" ] at com.kabira.platform.NativeRuntime.completeSMObject(Native Method) at com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$ChildTwo.$_init_Impl(AtomicCreateDuplicateKey.java:65) at com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$ChildTwo.<init>(AtomicCreateDuplicateKey.java:64) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526) at com.kabira.platform.KeyConstructor.createObject(KeyConstructor.java:245) at com.kabira.platform.KeyConstructor.invokeConstructor(KeyConstructor.java:104) at com.kabira.platform.KeyQuery.getOrCreateSingleResult(KeyQuery.java:364) at com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$1.run(AtomicCreateDuplicateKey.java:101) at com.kabira.platform.Transaction.execute(Transaction.java:484) at com.kabira.platform.Transaction.execute(Transaction.java:542) at com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey.main(AtomicCreateDuplicateKey.java:104)