/*- * 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