blob: 33dc24687da9e2b076135cfe8707cc56f97b8359 [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.persist;
import java.util.Map;
import java.util.SortedMap;
import com.sleepycat.bind.EntityBinding;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.collections.StoredSortedMap;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
/* <!-- begin JE only --> */
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Get;
/* <!-- end JE only --> */
import com.sleepycat.je.LockMode;
/* <!-- begin JE only --> */
import com.sleepycat.je.OperationResult;
/* <!-- end JE only --> */
import com.sleepycat.je.OperationStatus;
/* <!-- begin JE only --> */
import com.sleepycat.je.ReadOptions;
/* <!-- end JE only --> */
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.persist.model.DeleteAction;
import com.sleepycat.persist.model.Relationship;
import com.sleepycat.persist.model.SecondaryKey;
/**
* The secondary index for an entity class and a secondary key.
*
* <p>{@code SecondaryIndex} objects are thread-safe. Multiple threads may
* safely call the methods of a shared {@code SecondaryIndex} object.</p>
*
* <p>{@code SecondaryIndex} implements {@link EntityIndex} to map the
* secondary key type (SK) to the entity type (E). In other words, entities
* are accessed by secondary key values.</p>
*
* <p>The {@link SecondaryKey} annotation may be used to define a secondary key
* as shown in the following example.</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE)}
* String department;
*
* String name;
*
* private Employee() {}
* }</pre>
*
* <p>Before obtaining a {@code SecondaryIndex}, the {@link PrimaryIndex} must
* be obtained for the entity class. To obtain the {@code SecondaryIndex} call
* {@link EntityStore#getSecondaryIndex EntityStore.getSecondaryIndex}, passing
* the primary index, the secondary key class and the secondary key name. For
* example:</p>
*
* <pre class="code">
* EntityStore store = new EntityStore(...);
*
* {@code PrimaryIndex<Long, Employee>} primaryIndex =
* store.getPrimaryIndex(Long.class, Employee.class);
*
* {@code SecondaryIndex<String, Long, Employee>} secondaryIndex =
* store.getSecondaryIndex(primaryIndex, String.class, "department");</pre>
*
* <p>Since {@code SecondaryIndex} implements the {@link EntityIndex}
* interface, it shares the common index methods for retrieving and deleting
* entities, opening cursors and using transactions. See {@link EntityIndex}
* for more information on these topics.</p>
*
* <p>{@code SecondaryIndex} does <em>not</em> provide methods for inserting
* and updating entities. That must be done using the {@link
* PrimaryIndex}.</p>
*
* <p>Note that a {@code SecondaryIndex} has three type parameters {@code <SK,
* PK, E>} or in the example {@code <String, Long, Employee>} while a {@link
* PrimaryIndex} has only two type parameters {@code <PK, E>} or {@code <Long,
* Employee>}. This is because a {@code SecondaryIndex} has an extra level of
* mapping: It maps from secondary key to primary key, and then from primary
* key to entity. For example, consider this entity:</p>
*
* <div><table class="code" border="1" summary="">
* <tr><th>ID</th><th>Department</th><th>Name</th></tr>
* <tr><td>1</td><td>Engineering</td><td>Jane Smith</td></tr>
* </table></div>
*
* <p>The {@link PrimaryIndex} maps from id directly to the entity, or from
* primary key 1 to the "Jane Smith" entity in the example. The {@code
* SecondaryIndex} maps from department to id, or from secondary key
* "Engineering" to primary key 1 in the example, and then uses the {@code
* PrimaryIndex} to map from the primary key to the entity.</p>
*
* <p>Because of this extra type parameter and extra level of mapping, a {@code
* SecondaryIndex} can provide more than one mapping, or view, of the entities
* in the primary index. The main mapping of a {@code SecondaryIndex} is to
* map from secondary key (SK) to entity (E), or in the example, from the
* String department key to the Employee entity. The {@code SecondaryIndex}
* itself, by implementing {@code EntityIndex<SK, E>}, provides this
* mapping.</p>
*
* <p>The second mapping provided by {@code SecondaryIndex} is from secondary
* key (SK) to primary key (PK), or in the example, from the String department
* key to the Long id key. The {@link #keysIndex} method provides this
* mapping. When accessing the keys index, the primary key is returned rather
* than the entity. When only the primary key is needed and not the entire
* entity, using the keys index is less expensive than using the secondary
* index because the primary index does not have to be accessed.</p>
*
* <p>The third mapping provided by {@code SecondaryIndex} is from primary key
* (PK) to entity (E), for the subset of entities having a given secondary key
* (SK). This mapping is provided by the {@link #subIndex} method. A
* sub-index is convenient when you are interested in working with the subset
* of entities having a particular secondary key value, for example, all
* employees in a given department.</p>
*
* <p>All three mappings, along with the mapping provided by the {@link
* PrimaryIndex}, are shown using example data in the {@link EntityIndex}
* interface documentation. See {@link EntityIndex} for more information.</p>
*
* <p>Note that when using an index, keys and values are stored and retrieved
* by value not by reference. In other words, if an entity object is stored
* and then retrieved, or retrieved twice, each object will be a separate
* instance. For example, in the code below the assertion will always
* fail.</p>
* <pre class="code">
* MyKey key = ...;
* MyEntity entity1 = index.get(key);
* MyEntity entity2 = index.get(key);
* assert entity1 == entity2; // always fails!
* </pre>
*
* <h3>One-to-One Relationships</h3>
*
* <p>A {@link Relationship#ONE_TO_ONE ONE_TO_ONE} relationship, although less
* common than other types of relationships, is the simplest type of
* relationship. A single entity is related to a single secondary key value.
* For example:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=ONE_TO_ONE)}
* String ssn;
*
* String name;
*
* private Employee() {}
* }
*
* {@code SecondaryIndex<String, Long, Employee>} employeeBySsn =
* store.getSecondaryIndex(primaryIndex, String.class, "ssn");</pre>
*
* <p>With a {@link Relationship#ONE_TO_ONE ONE_TO_ONE} relationship, the
* secondary key must be unique; in other words, no two entities may have the
* same secondary key value. If an attempt is made to store an entity having
* the same secondary key value as another existing entity, a {@link
* DatabaseException} will be thrown.</p>
*
* <p>Because the secondary key is unique, it is useful to lookup entities by
* secondary key using {@link EntityIndex#get}. For example:</p>
*
* <pre class="code">
* Employee employee = employeeBySsn.get(mySsn);</pre>
*
* <h3>Many-to-One Relationships</h3>
*
* <p>A {@link Relationship#MANY_TO_ONE MANY_TO_ONE} relationship is the most
* common type of relationship. One or more entities is related to a single
* secondary key value. For example:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE)}
* String department;
*
* String name;
*
* private Employee() {}
* }
*
* {@code SecondaryIndex<String, Long, Employee>} employeeByDepartment =
* store.getSecondaryIndex(primaryIndex, String.class, "department");</pre>
*
* <p>With a {@link Relationship#MANY_TO_ONE MANY_TO_ONE} relationship, the
* secondary key is not required to be unique; in other words, more than one
* entity may have the same secondary key value. In this example, more than
* one employee may belong to the same department.</p>
*
* <p>The most convenient way to access the employees in a given department is
* by using a sub-index. For example:</p>
*
* <pre class="code">
* {@code EntityIndex<Long, Entity>} subIndex = employeeByDepartment.subIndex(myDept);
* {@code EntityCursor<Employee>} cursor = subIndex.entities();
* try {
* for (Employee entity : cursor) {
* // Do something with the entity...
* }
* } finally {
* cursor.close();
* }</pre>
*
* <h3>One-to-Many Relationships</h3>
*
* <p>In a {@link Relationship#ONE_TO_MANY ONE_TO_MANY} relationship, a single
* entity is related to one or more secondary key values. For example:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=ONE_TO_MANY)}
* {@literal Set<String> emailAddresses = new HashSet<String>;}
*
* String name;
*
* private Employee() {}
* }
*
* {@code SecondaryIndex<String, Long, Employee>} employeeByEmail =
* store.getSecondaryIndex(primaryIndex, String.class, "emailAddresses");</pre>
*
* <p>With a {@link Relationship#ONE_TO_MANY ONE_TO_MANY} relationship, the
* secondary key must be unique; in other words, no two entities may have the
* same secondary key value. In this example, no two employees may have the
* same email address. If an attempt is made to store an entity having the
* same secondary key value as another existing entity, a {@link
* DatabaseException} will be thrown.</p>
*
* <p>Because the secondary key is unique, it is useful to lookup entities by
* secondary key using {@link EntityIndex#get}. For example:</p>
*
* <pre class="code">
* Employee employee = employeeByEmail.get(myEmailAddress);</pre>
*
* <p>The secondary key field for a {@link Relationship#ONE_TO_MANY
* ONE_TO_MANY} relationship must be an array or collection type. To access
* the email addresses of an employee, simply access the collection field
* directly. For example:</p>
*
* <pre class="code">
* Employee employee = primaryIndex.get(1); // Get the entity by primary key
* employee.emailAddresses.add(myNewEmail); // Add an email address
* primaryIndex.putNoReturn(1, employee); // Update the entity</pre>
*
* <h3>Many-to-Many Relationships</h3>
*
* <p>In a {@link Relationship#MANY_TO_MANY MANY_TO_MANY} relationship, one
* or more entities is related to one or more secondary key values. For
* example:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_MANY)}
* {@literal Set<String> organizations = new HashSet<String>;}
*
* String name;
*
* private Employee() {}
* }
*
* {@code SecondaryIndex<String, Long, Employee>} employeeByOrganization =
* store.getSecondaryIndex(primaryIndex, String.class, "organizations");</pre>
*
* <p>With a {@link Relationship#MANY_TO_MANY MANY_TO_MANY} relationship, the
* secondary key is not required to be unique; in other words, more than one
* entity may have the same secondary key value. In this example, more than
* one employee may belong to the same organization.</p>
*
* <p>The most convenient way to access the employees in a given organization
* is by using a sub-index. For example:</p>
*
* <pre class="code">
* {@code EntityIndex<Long, Entity>} subIndex = employeeByOrganization.subIndex(myOrg);
* {@code EntityCursor<Employee>} cursor = subIndex.entities();
* try {
* for (Employee entity : cursor) {
* // Do something with the entity...
* }
* } finally {
* cursor.close();
* }</pre>
*
* <p>The secondary key field for a {@link Relationship#MANY_TO_MANY
* MANY_TO_MANY} relationship must be an array or collection type. To access
* the organizations of an employee, simply access the collection field
* directly. For example:</p>
*
* <pre class="code">
* Employee employee = primaryIndex.get(1); // Get the entity by primary key
* employee.organizations.remove(myOldOrg); // Remove an organization
* primaryIndex.putNoReturn(1, employee); // Update the entity</pre>
*
* <h3>Foreign Key Constraints for Related Entities</h3>
*
* <p>In all the examples above the secondary key is treated only as a simple
* value, such as a {@code String} department field. In many cases, that is
* sufficient. But in other cases, you may wish to constrain the secondary
* keys of one entity class to be valid primary keys of another entity
* class. For example, a Department entity may also be defined:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Department {
*
* {@literal @PrimaryKey}
* String name;
*
* String missionStatement;
*
* private Department() {}
* }</pre>
*
* <p>You may wish to constrain the department field values of the Employee
* class in the examples above to be valid primary keys of the Department
* entity class. In other words, you may wish to ensure that the department
* field of an Employee will always refer to a valid Department entity.</p>
*
* <p>You can implement this constraint yourself by validating the department
* field before you store an Employee. For example:</p>
*
* <pre class="code">
* {@code PrimaryIndex<String, Department>} departmentIndex =
* store.getPrimaryIndex(String.class, Department.class);
*
* void storeEmployee(Employee employee) throws DatabaseException {
* if (departmentIndex.contains(employee.department)) {
* primaryIndex.putNoReturn(employee);
* } else {
* throw new IllegalArgumentException("Department does not exist: " +
* employee.department);
* }
* }</pre>
*
* <p>Or, instead you could define the Employee department field as a foreign
* key, and this validation will be done for you when you attempt to store the
* Employee entity. For example:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Department.class)}
* String department;
*
* String name;
*
* private Employee() {}
* }</pre>
*
* <p>The {@code relatedEntity=Department.class} above defines the department
* field as a foreign key that refers to a Department entity. Whenever a
* Employee entity is stored, its department field value will be checked to
* ensure that a Department entity exists with that value as its primary key.
* If no such Department entity exists, then a {@link DatabaseException} is
* thrown, causing the transaction to be aborted (assuming that transactions
* are used).</p>
*
* <p>This begs the question: What happens when a Department entity is deleted
* while one or more Employee entities have department fields that refer to
* the deleted department's primary key? If the department were allowed to be
* deleted, the foreign key constraint for the Employee department field would
* be violated, because the Employee department field would refer to a
* department that does not exist.</p>
*
* <p>By default, when this situation arises the system does not allow the
* department to be deleted. Instead, a {@link DatabaseException} is thrown,
* causing the transaction to be aborted. In this case, in order to delete a
* department, the department field of all Employee entities must first be
* updated to refer to a different existing department, or set to null. This
* is the responsibility of the application.</p>
*
* <p>There are two additional ways of handling deletion of a Department
* entity. These alternatives are configured using the {@link
* SecondaryKey#onRelatedEntityDelete} annotation property. Setting this
* property to {@link DeleteAction#NULLIFY} causes the Employee department
* field to be automatically set to null when the department they refer to is
* deleted. This may or may not be desirable, depending on application
* policies. For example:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@code @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Department.class,
* onRelatedEntityDelete=NULLIFY)}
* String department;
*
* String name;
*
* private Employee() {}
* }</pre>
*
* <p>The {@link DeleteAction#CASCADE} value, on the other hand, causes the
* Employee entities to be automatically deleted when the department they refer
* to is deleted. This is probably not desirable in this particular example,
* but is useful for parent-child relationships. For example:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Order {
*
* {@literal @PrimaryKey}
* long id;
*
* String description;
*
* private Order() {}
* }
*
* {@literal @Entity}
* class OrderItem {
*
* {@literal @PrimaryKey}
* long id;
*
* {@code @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Order.class,
* onRelatedEntityDelete=CASCADE)}
* long orderId;
*
* String description;
*
* private OrderItem() {}
* }</pre>
*
* <p>The OrderItem orderId field refers to its "parent" Order entity. When an
* Order entity is deleted, it may be useful to automatically delete its
* "child" OrderItem entities.</p>
*
* <p>For more information, see {@link SecondaryKey#onRelatedEntityDelete}.</p>
*
* <h3>One-to-Many versus Many-to-One for Related Entities</h3>
*
* <p>When there is a conceptual Many-to-One relationship such as Employee to
* Department as illustrated in the examples above, the relationship may be
* implemented either as Many-to-One in the Employee class or as One-to-Many in
* the Department class.</p>
*
* <p>Here is the Many-to-One approach.</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Department.class)}
* String department;
*
* String name;
*
* private Employee() {}
* }
*
* {@literal @Entity}
* class Department {
*
* {@literal @PrimaryKey}
* String name;
*
* String missionStatement;
*
* private Department() {}
* }</pre>
*
* <p>And here is the One-to-Many approach.</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* String name;
*
* private Employee() {}
* }
*
* {@literal @Entity}
* class Department {
*
* {@literal @PrimaryKey}
* String name;
*
* String missionStatement;
*
* {@literal @SecondaryKey(relate=ONE_TO_MANY, relatedEntity=Employee.class)}
* {@literal Set<Long> employees = new HashSet<Long>;}
*
* private Department() {}
* }</pre>
*
* <p>Which approach is best? The Many-to-One approach better handles large
* number of entities on the to-Many side of the relationship because it
* doesn't store a collection of keys as an entity field. With Many-to-One a
* Btree is used to store the collection of keys and the Btree can easily
* handle very large numbers of keys. With One-to-Many, each time a related
* key is added or removed the entity on the One side of the relationship,
* along with the complete collection of related keys, must be updated.
* Therefore, if large numbers of keys may be stored per relationship,
* Many-to-One is recommended.</p>
*
* <p>If the number of entities per relationship is not a concern, then you may
* wish to choose the approach that is most natural in your application data
* model. For example, if you think of a Department as containing employees
* and you wish to modify the Department object each time an employee is added
* or removed, then you may wish to store a collection of Employee keys in the
* Department object (One-to-Many).</p>
*
* <p>Note that if you have a One-to-Many relationship and there is no related
* entity, then you don't have a choice -- you have to use One-to-Many because
* there is no entity on the to-Many side of the relationship where a
* Many-to-One key could be defined. An example is the Employee to email
* addresses relationship discussed above:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=ONE_TO_MANY)}
* {@literal Set<String> emailAddresses = new HashSet<String>;}
*
* String name;
*
* private Employee() {}
* }</pre>
*
* <p>For sake of argument imagine that each employee has thousands of email
* addresses and employees frequently add and remove email addresses. To
* avoid the potential performance problems associated with updating the
* Employee entity every time an email address is added or removed, you could
* create an EmployeeEmailAddress entity and use a Many-to-One relationship as
* shown below:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* String name;
*
* private Employee() {}
* }
*
* {@literal @Entity}
* class EmployeeEmailAddress {
*
* {@literal @PrimaryKey}
* String emailAddress;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Employee.class)}
* long employeeId;
*
* private EmployeeEmailAddress() {}
* }</pre>
*
* <h3>Key Placement with Many-to-Many for Related Entities</h3>
*
* <p>As discussed in the section above, one drawback of a to-Many relationship
* (One-to-Many was discussed above and Many-to-Many is discussed here) is that
* it requires storing a collection of keys in an entity. Each time a key is
* added or removed, the containing entity must be updated. This has potential
* performance problems when there are large numbers of entities on the to-Many
* side of the relationship, in other words, when there are large numbers of
* keys in each secondary key field collection.</p>
*
* <p>If you have a Many-to-Many relationship with a reasonably small number of
* entities on one side of the relationship and a large number of entities on
* the other side, you can avoid the potential performance problems by defining
* the secondary key field on the side with a small number of entities.</p>
*
* <p>For example, in an Employee-to-Organization relationship, the number of
* organizations per employee will normally be reasonably small but the number
* of employees per organization may be very large. Therefore, to avoid
* potential performance problems, the secondary key field should be defined in
* the Employee class as shown below.</p>
*
* <pre class="code">
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_MANY, relatedEntity=Organization.class)}
* {@literal Set<String> organizations = new HashSet<String>;}
*
* String name;
*
* private Employee() {}
* }
*
* {@literal @Entity}
* class Organization {
*
* {@literal @PrimaryKey}
* String name;
*
* String description;
* }</pre>
*
* <p>If instead a {@code Set<Long> members} key had been defined in the
* Organization class, this set could potentially have a large number of
* elements and performance problems could result.</p>
*
* <h3>Many-to-Many Versus a Relationship Entity</h3>
*
* <p>If you have a Many-to-Many relationship with a large number of entities
* on <em>both</em> sides of the relationship, you can avoid the potential
* performance problems by using a <em>relationship entity</em>. A
* relationship entity defines the relationship between two other entities
* using two Many-to-One relationships.</p>
*
* <p>Imagine a relationship between cars and trucks indicating whenever a
* particular truck was passed on the road by a particular car. A given car
* may pass a large number of trucks and a given truck may be passed by a large
* number of cars. First look at a Many-to-Many relationship between these two
* entities:</p>
*
* <pre class="code">
* {@literal @Entity}
* class Car {
*
* {@literal @PrimaryKey}
* String licenseNumber;
*
* {@literal @SecondaryKey(relate=MANY_TO_MANY, relatedEntity=Truck.class)}
* {@literal Set<String> trucksPassed = new HashSet<String>;}
*
* String color;
*
* private Car() {}
* }
*
* {@literal @Entity}
* class Truck {
*
* {@literal @PrimaryKey}
* String licenseNumber;
*
* int tons;
*
* private Truck() {}
* }</pre>
*
* <p>With the Many-to-Many approach above, the {@code trucksPassed} set could
* potentially have a large number of elements and performance problems could
* result.</p>
*
* <p>To apply the relationship entity approach we define a new entity class
* named CarPassedTruck representing a single truck passed by a single car. We
* remove the secondary key from the Car class and use two secondary keys in
* the CarPassedTruck class instead.</p>
*
* <pre class="code">
* {@literal @Entity}
* class Car {
*
* {@literal @PrimaryKey}
* String licenseNumber;
*
* String color;
*
* private Car() {}
* }
*
* {@literal @Entity}
* class Truck {
*
* {@literal @PrimaryKey}
* String licenseNumber;
*
* int tons;
*
* private Truck() {}
* }
*
* {@literal @Entity}
* class CarPassedTruck {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Car.class)}
* String carLicense;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Truck.class)}
* String truckLicense;
*
* private CarPassedTruck() {}
* }</pre>
*
* <p>The CarPassedTruck entity can be used to access the relationship by car
* license or by truck license.</p>
*
* <p>You may use the relationship entity approach because of the potential
* performance problems mentioned above. Or, you may choose to use this
* approach in order to store other information about the relationship. For
* example, if for each car that passes a truck you wish to record how much
* faster the car was going than the truck, then a relationship entity is the
* logical place to store that property. In the example below the
* speedDifference property is added to the CarPassedTruck class.</p>
*
* <pre class="code">
* {@literal @Entity}
* class CarPassedTruck {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Car.class)}
* String carLicense;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Truck.class)}
* String truckLicense;
*
* int speedDifference;
*
* private CarPassedTruck() {}
* }</pre>
*
* <p>Be aware that the relationship entity approach adds overhead compared to
* Many-to-Many. There is one additional entity and one additional secondary
* key. These factors should be weighed against its advantages and the
* relevant application access patterns should be considered.</p>
*
* @author Mark Hayes
*/
public class SecondaryIndex<SK, PK, E> extends BasicIndex<SK, E> {
private SecondaryDatabase secDb;
private Database keysDb;
private PrimaryIndex<PK, E> priIndex;
private EntityBinding<E> entityBinding;
private EntityIndex<SK, PK> keysIndex;
private SortedMap<SK, E> map;
/**
* Creates a secondary index without using an <code>EntityStore</code>.
* When using an {@link EntityStore}, call {@link
* EntityStore#getSecondaryIndex getSecondaryIndex} instead.
*
* <p>This constructor is not normally needed and is provided for
* applications that wish to use custom bindings along with the Direct
* Persistence Layer. Normally, {@link EntityStore#getSecondaryIndex
* getSecondaryIndex} is used instead.</p>
*
* @param database the secondary database used for all access other than
* via a {@link #keysIndex}.
*
* @param keysDatabase another handle on the secondary database, opened
* without association to the primary, and used only for access via a
* {@link #keysIndex}. If this argument is null and the {@link #keysIndex}
* method is called, then the keys database will be opened automatically;
* however, the user is then responsible for closing the keys database. To
* get the keys database in order to close it, call {@link
* #getKeysDatabase}.
*
* @param primaryIndex the primary index associated with this secondary
* index.
*
* @param secondaryKeyClass the class of the secondary key.
*
* @param secondaryKeyBinding the binding to be used for secondary keys.
*
* @throws DatabaseException the base class for all BDB exceptions.
*/
public SecondaryIndex(SecondaryDatabase database,
Database keysDatabase,
PrimaryIndex<PK, E> primaryIndex,
Class<SK> secondaryKeyClass,
EntryBinding<SK> secondaryKeyBinding)
throws DatabaseException {
super(database, secondaryKeyClass, secondaryKeyBinding,
new EntityValueAdapter(primaryIndex.getEntityClass(),
primaryIndex.getEntityBinding(),
true));
secDb = database;
keysDb = keysDatabase;
priIndex = primaryIndex;
entityBinding = primaryIndex.getEntityBinding();
}
/**
* Returns the underlying secondary database for this index.
*
* @return the secondary database.
*/
@Override
public SecondaryDatabase getDatabase() {
return secDb;
}
/**
* Returns the underlying secondary database that is not associated with
* the primary database and is used for the {@link #keysIndex}.
*
* @return the keys database.
*/
public Database getKeysDatabase() {
return keysDb;
}
/**
* Returns the primary index associated with this secondary index.
*
* @return the primary index.
*/
public PrimaryIndex<PK, E> getPrimaryIndex() {
return priIndex;
}
/**
* Returns the secondary key class for this index.
*
* @return the class.
*/
public Class<SK> getKeyClass() {
return keyClass;
}
/**
* Returns the secondary key binding for the index.
*
* @return the key binding.
*/
public EntryBinding<SK> getKeyBinding() {
return keyBinding;
}
/**
* Returns a read-only keys index that maps secondary key to primary key.
* When accessing the keys index, the primary key is returned rather than
* the entity. When only the primary key is needed and not the entire
* entity, using the keys index is less expensive than using the secondary
* index because the primary index does not have to be accessed.
*
* <p>Note the following in the unusual case that you are <em>not</em>
* using an <code>EntityStore</code>: This method will open the keys
* database, a second database handle for the secondary database, if it is
* not already open. In this case, if you are <em>not</em> using an
* <code>EntityStore</code>, then you are responsible for closing the
* database returned by {@link #getKeysDatabase} before closing the
* environment. If you <em>are</em> using an <code>EntityStore</code>, the
* keys database will be closed automatically by {@link
* EntityStore#close}.</p>
*
* @return the keys index.
*
* @throws DatabaseException the base class for all BDB exceptions.
*/
public synchronized EntityIndex<SK, PK> keysIndex()
throws DatabaseException {
if (keysIndex == null) {
if (keysDb == null) {
DatabaseConfig config = secDb.getConfig();
config.setReadOnly(true);
config.setAllowCreate(false);
config.setExclusiveCreate(false);
keysDb = DbCompat.openDatabase
(db.getEnvironment(), null /*txn*/,
DbCompat.getDatabaseFile(secDb),
secDb.getDatabaseName(),
config);
if (keysDb == null) {
throw new IllegalStateException
("Could not open existing DB, file: " +
DbCompat.getDatabaseFile(secDb) + " name: " +
secDb.getDatabaseName());
}
}
keysIndex = new KeysIndex<SK, PK>
(keysDb, keyClass, keyBinding,
priIndex.getKeyClass(), priIndex.getKeyBinding());
}
return keysIndex;
}
/**
* Returns an index that maps primary key to entity for the subset of
* entities having a given secondary key (duplicates). A sub-index is
* convenient when you are interested in working with the subset of
* entities having a particular secondary key value.
*
* <p>When using a {@link Relationship#MANY_TO_ONE MANY_TO_ONE} or {@link
* Relationship#MANY_TO_MANY MANY_TO_MANY} secondary key, the sub-index
* represents the left (MANY) side of a relationship.</p>
*
* @param key the secondary key that identifies the entities in the
* sub-index.
*
* @return the sub-index.
*
* @throws DatabaseException the base class for all BDB exceptions.
*/
public EntityIndex<PK, E> subIndex(SK key)
throws DatabaseException {
return new SubIndex(this, entityBinding, key);
}
/*
* Of the EntityIndex methods only get()/map()/sortedMap() are implemented
* here. All other methods are implemented by BasicIndex.
*/
public E get(SK key)
throws DatabaseException {
return get(null, key, null);
}
public E get(Transaction txn, SK key, LockMode lockMode)
throws DatabaseException {
/* <!-- begin JE only --> */
if (DbCompat.IS_JE) {
EntityResult<E> result = get(
txn, key, Get.SEARCH, DbInternal.getReadOptions(lockMode));
return result != null ? result.value() : null;
}
/* <!-- end JE only --> */
DatabaseEntry keyEntry = new DatabaseEntry();
DatabaseEntry pkeyEntry = new DatabaseEntry();
DatabaseEntry dataEntry = new DatabaseEntry();
keyBinding.objectToEntry(key, keyEntry);
OperationStatus status =
secDb.get(txn, keyEntry, pkeyEntry, dataEntry, lockMode);
if (status == OperationStatus.SUCCESS) {
return entityBinding.entryToObject(pkeyEntry, dataEntry);
} else {
return null;
}
}
/* <!-- begin JE only --> */
public EntityResult<E> get(Transaction txn,
SK key,
Get getType,
ReadOptions options)
throws DatabaseException {
checkGetType(getType);
DatabaseEntry keyEntry = new DatabaseEntry();
DatabaseEntry pkeyEntry = new DatabaseEntry();
DatabaseEntry dataEntry = new DatabaseEntry();
keyBinding.objectToEntry(key, keyEntry);
OperationResult result = secDb.get(
txn, keyEntry, pkeyEntry, dataEntry, getType, options);
if (result != null) {
return new EntityResult<>(
entityBinding.entryToObject(pkeyEntry, dataEntry),
result);
} else {
return null;
}
}
/* <!-- end JE only --> */
public Map<SK, E> map() {
return sortedMap();
}
public synchronized SortedMap<SK, E> sortedMap() {
if (map == null) {
map = new StoredSortedMap(db, keyBinding, entityBinding, true);
}
return map;
}
/**
* <!-- begin JE only -->
* @hidden
* <!-- end JE only -->
* For internal use only.
*
* Used for obtaining the auto-commit txn config from the store, which
* overrides this method to return it.
*/
/* <!-- begin JE only --> */
protected
/* <!-- end JE only --> */
TransactionConfig getAutoCommitTransactionConfig() {
return null;
}
boolean isUpdateAllowed() {
return false;
}
}