Traverses entity values or key values and allows deleting or updating the
entity at the current cursor position. The value type (V) is either an
entity class or a key class, depending on how the cursor was opened.
EntityCursor objects are not thread-safe. Cursors
should be opened, used and closed by a single thread.
Cursors are opened using the
EntityIndex.keys and
EntityIndex.entities family of methods. These methods are available for
objects of any class that implements
EntityIndex :
PrimaryIndex ,
SecondaryIndex , and the indices returned by
SecondaryIndex.keysIndex and
SecondaryIndex.subIndex . A
ForwardCursor , which implements a subset of cursor operations, is also
available via the
EntityJoin.keys and
EntityJoin.entities methods.
Values are always returned by a cursor in key order, where the key is
defined by the underlying
EntityIndex . For example, a cursor on a
SecondaryIndex returns values ordered by secondary key, while an
index on a
PrimaryIndex or a
SecondaryIndex.subIndex returns
values ordered by primary key.
WARNING: Cursors must always be closed to prevent resource leaks
which could lead to the index becoming unusable or cause an
OutOfMemoryError . To ensure that a cursor is closed in the
face of exceptions, call
EntityCursor.close in a finally block. For example,
the following code traverses all Employee entities and closes the cursor
whether or not an exception occurs:
class Employee {
long id;
String department;
String name;
private Employee() {}
}
EntityStore store = ...
PrimaryIndex primaryIndex =
store.getPrimaryIndex(Long.class, Employee.class);
EntityCursor cursor = primaryIndex.entities();
try {
for (Employee entity = cursor.first();
entity != null;
entity = cursor.next()) {
// Do something with the entity...
}
} finally {
cursor.close();
}
Initializing the Cursor Position
When it is opened, a cursor is not initially positioned on any value; in
other words, it is uninitialized. Most methods in this interface initialize
the cursor position but certain methods, for example,
EntityCursor.current and
EntityCursor.delete , throw
IllegalStateException when called for an
uninitialized cursor.
Note that the
EntityCursor.next and
EntityCursor.prev methods return the first or
last value respectively for an uninitialized cursor. This allows the loop
in the example above to be rewritten as follows:
EntityCursor cursor = primaryIndex.entities();
try {
Employee entity;
while ((entity = cursor.next()) != null) {
// Do something with the entity...
}
} finally {
cursor.close();
}
Cursors and Iterators
The
EntityCursor.iterator method can be used to return a standard Java
Iterator that returns the same values that the cursor returns. For
example:
EntityCursor cursor = primaryIndex.entities();
try {
Iterator i = cursor.iterator();
while (i.hasNext()) {
Employee entity = i.next();
// Do something with the entity...
}
} finally {
cursor.close();
}
The
Iterable interface is also extended by
EntityCursor to allow using the cursor as the target of a Java "foreach" statement:
EntityCursor cursor = primaryIndex.entities();
try {
for (Employee entity : cursor) {
// Do something with the entity...
}
} finally {
cursor.close();
}
The iterator uses the cursor directly, so any changes to the cursor
position impact the iterator and vice versa. The iterator advances the
cursor by calling
EntityCursor.next() when
Iterator.hasNext or
Iterator.next is called. Because of this interaction, to keep things
simple it is best not to mix the use of an
EntityCursor Iterator with the use of the
EntityCursor traversal methods
such as
EntityCursor.next() , for a single
EntityCursor object.
Key Ranges
A key range may be specified when opening the cursor, to restrict the
key range of the cursor to a subset of the complete range of keys in the
index. A
fromKey and/or
toKey parameter may be specified
when calling
EntityIndex.keys(ObjectbooleanObjectboolean) or
EntityIndex.entities(ObjectbooleanObjectboolean) . The key
arguments may be specified as inclusive or exclusive values.
Whenever a cursor with a key range is moved, the key range bounds will be
checked, and the cursor will never be positioned outside the range. The
EntityCursor.first cursor value is the first existing value in the range, and
the
EntityCursor.last cursor value is the last existing value in the range. For
example, the following code traverses Employee entities with keys from 100
(inclusive) to 200 (exclusive):
EntityCursor cursor = primaryIndex.entities(100, true, 200, false);
try {
for (Employee entity : cursor) {
// Do something with the entity...
}
} finally {
cursor.close();
}
Duplicate Keys
When using a cursor for a
SecondaryIndex , the keys in the index
may be non-unique (duplicates) if
SecondaryKey.relate is
Relationship.MANY_TO_ONE MANY_TO_ONE or
Relationship.MANY_TO_MANYMANY_TO_MANY . For example, a
MANY_TO_ONE
Employee.department secondary key is non-unique because there are multiple
Employee entities with the same department key value. The
EntityCursor.nextDup ,
EntityCursor.prevDup ,
EntityCursor.nextNoDup and
EntityCursor.prevNoDup methods may be
used to control how non-unique keys are returned by the cursor.
EntityCursor.nextDup and
EntityCursor.prevDup return the next or previous value
only if it has the same key as the current value, and null is returned when
a different key is encountered. For example, these methods can be used to
return all employees in a given department.
EntityCursor.nextNoDup and
EntityCursor.prevNoDup return the next or previous
value with a unique key, skipping over values that have the same key. For
example, these methods can be used to return the first employee in each
department.
For example, the following code will find the first employee in each
department with
EntityCursor.nextNoDup until it finds a department name that
matches a particular regular expression. For each matching department it
will find all employees in that department using
EntityCursor.nextDup .
SecondaryIndex secondaryIndex =
store.getSecondaryIndex(primaryIndex, String.class, "department");
String regex = ...;
EntityCursor cursor = secondaryIndex.entities();
try {
for (Employee entity = cursor.first();
entity != null;
entity = cursor.nextNoDup()) {
if (entity.department.matches(regex)) {
while (entity != null) {
// Do something with the matching entities...
entity = cursor.nextDup();
}
}
}
} finally {
cursor.close();
}
Updating and Deleting Entities with a Cursor
The
EntityCursor.update and
EntityCursor.delete methods operate on the entity at
the current cursor position. Cursors on any type of index may be used to
delete entities. For example, the following code deletes all employees in
departments which have names that match a particular regular expression:
SecondaryIndex secondaryIndex =
store.getSecondaryIndex(primaryIndex, String.class, "department");
String regex = ...;
EntityCursor cursor = secondaryIndex.entities();
try {
for (Employee entity = cursor.first();
entity != null;
entity = cursor.nextNoDup()) {
if (entity.department.matches(regex)) {
while (entity != null) {
cursor.delete();
entity = cursor.nextDup();
}
}
}
} finally {
cursor.close();
}
Note that the cursor can be moved to the next (or previous) value after
deleting the entity at the current position. This is an important property
of cursors, since without it you would not be able to easily delete while
processing multiple values with a cursor. A cursor positioned on a deleted
entity is in a special state. In this state,
EntityCursor.current will return
null,
EntityCursor.delete will return false, and
EntityCursor.update will return
false.
The
EntityCursor.update method is supported only if the value type is an
entity class (not a key class) and the underlying index is a
PrimaryIndex ; in other words, for a cursor returned by one of the
PrimaryIndex.entities methods. For example, the following code changes all
employee names to uppercase:
EntityCursor cursor = primaryIndex.entities();
try {
for (Employee entity = cursor.first();
entity != null;
entity = cursor.next()) {
entity.name = entity.name.toUpperCase();
cursor.update(entity);
}
} finally {
cursor.close();
}
author: Mark Hayes |