/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002, 2010 Oracle and/or its affiliates. All rights reserved. * */ 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.db.Database; import com.sleepycat.db.DatabaseConfig; import com.sleepycat.db.DatabaseEntry; import com.sleepycat.db.DatabaseException; import com.sleepycat.db.LockMode; import com.sleepycat.db.OperationStatus; import com.sleepycat.db.SecondaryDatabase; import com.sleepycat.db.Transaction; 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. * *
{@code SecondaryIndex} objects are thread-safe. Multiple threads may * safely call the methods of a shared {@code SecondaryIndex} object.
* *{@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.
* *The {@link SecondaryKey} annotation may be used to define a secondary key * as shown in the following example.
* *
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE)}
* String department;
*
* String name;
*
* private Employee() {}
* }
*
* 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:
* *
* EntityStore store = new EntityStore(...);
*
* {@code PrimaryIndex} primaryIndex =
* store.getPrimaryIndex(Long.class, Employee.class);
*
* {@code SecondaryIndex} secondaryIndex =
* store.getSecondaryIndex(primaryIndex, String.class, "department");
*
* 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.
* *{@code SecondaryIndex} does not provide methods for inserting * and updating entities. That must be done using the {@link * PrimaryIndex}.
* *Note that a {@code SecondaryIndex} has three type parameters {@code
| ID | Department | Name |
|---|---|---|
| 1 | Engineering | Jane Smith |
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.
* *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
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.
* *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.
* *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.
* *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.
** MyKey key = ...; * MyEntity entity1 = index.get(key); * MyEntity entity2 = index.get(key); * assert entity1 == entity2; // always fails! ** *
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:
* *
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=ONE_TO_ONE)}
* String ssn;
*
* String name;
*
* private Employee() {}
* }
*
* {@code SecondaryIndex} employeeBySsn =
* store.getSecondaryIndex(primaryIndex, String.class, "ssn");
*
* 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.
* *Because the secondary key is unique, it is useful to lookup entities by * secondary key using {@link EntityIndex#get}. For example:
* ** Employee employee = employeeBySsn.get(mySsn);* *
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:
* *
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE)}
* String department;
*
* String name;
*
* private Employee() {}
* }
*
* {@code SecondaryIndex} employeeByDepartment =
* store.getSecondaryIndex(primaryIndex, String.class, "department");
*
* 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.
* *The most convenient way to access the employees in a given department is * by using a sub-index. For example:
* *
* {@code EntityIndex} subIndex = employeeByDepartment.subIndex(myDept);
* {@code EntityCursor} cursor = subIndex.entities();
* try {
* for (Employee entity : cursor) {
* // Do something with the entity...
* }
* } finally {
* cursor.close();
* }
*
* 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:
* *
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=ONE_TO_MANY)}
* {@literal Set emailAddresses = new HashSet;}
*
* String name;
*
* private Employee() {}
* }
*
* {@code SecondaryIndex} employeeByEmail =
* store.getSecondaryIndex(primaryIndex, String.class, "emailAddresses");
*
* 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.
* *Because the secondary key is unique, it is useful to lookup entities by * secondary key using {@link EntityIndex#get}. For example:
* ** Employee employee = employeeByEmail.get(myEmailAddress);* *
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:
* ** 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* *
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:
* *
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_MANY)}
* {@literal Set organizations = new HashSet;}
*
* String name;
*
* private Employee() {}
* }
*
* {@code SecondaryIndex} employeeByOrganization =
* store.getSecondaryIndex(primaryIndex, String.class, "organizations");
*
* 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.
* *The most convenient way to access the employees in a given organization * is by using a sub-index. For example:
* *
* {@code EntityIndex} subIndex = employeeByOrganization.subIndex(myOrg);
* {@code EntityCursor} cursor = subIndex.entities();
* try {
* for (Employee entity : cursor) {
* // Do something with the entity...
* }
* } finally {
* cursor.close();
* }
*
* 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:
* ** 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* *
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:
* *
* {@literal @Entity}
* class Department {
*
* {@literal @PrimaryKey}
* String name;
*
* String missionStatement;
*
* private Department() {}
* }
*
* 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.
* *You can implement this constraint yourself by validating the department * field before you store an Employee. For example:
* *
* {@code PrimaryIndex} 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);
* }
* }
*
* 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:
* *
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_ONE, relatedEntity=Department.class)}
* String department;
*
* String name;
*
* private Employee() {}
* }
*
* 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).
* *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.
* *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.
* *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:
* *
* {@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() {}
* }
*
* 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:
* *
* {@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() {}
* }
*
* 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.
* *For more information, see {@link SecondaryKey#onRelatedEntityDelete}.
* *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.
* *Here is the Many-to-One approach.
* *
* {@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() {}
* }
*
* And here is the One-to-Many approach.
* *
* {@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 employees = new HashSet;}
*
* private Department() {}
* }
*
* 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.
* *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).
* *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:
* *
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=ONE_TO_MANY)}
* {@literal Set emailAddresses = new HashSet;}
*
* String name;
*
* private Employee() {}
* }
*
* 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:
* *
* {@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() {}
* }
*
* 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.
* *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.
* *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.
* *
* {@literal @Entity}
* class Employee {
*
* {@literal @PrimaryKey}
* long id;
*
* {@literal @SecondaryKey(relate=MANY_TO_MANY, relatedEntity=Organization.class)}
* {@literal Set organizations = new HashSet;}
*
* String name;
*
* private Employee() {}
* }
*
* {@literal @Entity}
* class Organization {
*
* {@literal @PrimaryKey}
* String name;
*
* String description;
* }
*
* If instead a {@code Set
If you have a Many-to-Many relationship with a large number of entities * on both sides of the relationship, you can avoid the potential * performance problems by using a relationship entity. A * relationship entity defines the relationship between two other entities * using two Many-to-One relationships.
* *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:
* *
* {@literal @Entity}
* class Car {
*
* {@literal @PrimaryKey}
* String licenseNumber;
*
* {@literal @SecondaryKey(relate=MANY_TO_MANY, relatedEntity=Truck.class)}
* {@literal Set trucksPassed = new HashSet;}
*
* String color;
*
* private Car() {}
* }
*
* {@literal @Entity}
* class Truck {
*
* {@literal @PrimaryKey}
* String licenseNumber;
*
* int tons;
*
* private Truck() {}
* }
*
* With the Many-to-Many approach above, the {@code trucksPassed} set could * potentially have a large number of elements and performance problems could * result.
* *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.
* *
* {@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() {}
* }
*
* The CarPassedTruck entity can be used to access the relationship by car * license or by truck license.
* *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.
* *
* {@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() {}
* }
*
* 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.
* * @author Mark Hayes */ public class SecondaryIndexEntityStore.
* When using an {@link EntityStore}, call {@link
* EntityStore#getSecondaryIndex getSecondaryIndex} instead.
*
* 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.
* * @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, PrimaryIndexNote the following in the unusual case that you are not
* using an EntityStore: 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 not using an
* EntityStore, then you are responsible for closing the
* database returned by {@link #getKeysDatabase} before closing the
* environment. If you are using an EntityStore, the
* keys database will be closed automatically by {@link
* EntityStore#close}.
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.
* * @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