All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.persist.SecondaryIndex Maven / Gradle / Ivy

The newest version!
/*-
 * 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;
/*  */
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Get;
/*  */
import com.sleepycat.je.LockMode;
/*  */
import com.sleepycat.je.OperationResult;
/*  */
import com.sleepycat.je.OperationStatus;
/*  */
import com.sleepycat.je.ReadOptions;
/*  */
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.
 *
 * 

{@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 } or in the example {@code } while a {@link * PrimaryIndex} has only two type parameters {@code } or {@code }. 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:

* *
* * *
IDDepartmentName
1EngineeringJane 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}, provides this * mapping.

* *

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

One-to-One Relationships

* *

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);
* *

Many-to-One Relationships

* *

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();
 * }
* *

One-to-Many Relationships

* *

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

Many-to-Many Relationships

* *

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

Foreign Key Constraints for Related Entities

* *

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}.

* *

One-to-Many versus Many-to-One for Related Entities

* *

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() {}
 * }
* *

Key Placement with Many-to-Many for Related Entities

* *

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 members} key had been defined in the * Organization class, this set could potentially have a large number of * elements and performance problems could result.

* *

Many-to-Many Versus a Relationship Entity

* *

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 SecondaryIndex extends BasicIndex { private SecondaryDatabase secDb; private Database keysDb; private PrimaryIndex priIndex; private EntityBinding entityBinding; private EntityIndex keysIndex; private SortedMap map; /** * Creates a secondary index without using an EntityStore. * 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, PrimaryIndex primaryIndex, Class secondaryKeyClass, EntryBinding 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 getPrimaryIndex() { return priIndex; } /** * Returns the secondary key class for this index. * * @return the class. */ public Class getKeyClass() { return keyClass; } /** * Returns the secondary key binding for the index. * * @return the key binding. */ public EntryBinding 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. * *

Note 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}.

* * @return the keys index. * * @throws DatabaseException the base class for all BDB exceptions. */ public synchronized EntityIndex 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 (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. * *

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 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 { /* */ if (DbCompat.IS_JE) { EntityResult result = get( txn, key, Get.SEARCH, DbInternal.getReadOptions(lockMode)); return result != null ? result.value() : null; } /* */ 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; } } /* */ public EntityResult 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; } } /* */ public Map map() { return sortedMap(); } public synchronized SortedMap sortedMap() { if (map == null) { map = new StoredSortedMap(db, keyBinding, entityBinding, true); } return map; } /** * * @hidden * * For internal use only. * * Used for obtaining the auto-commit txn config from the store, which * overrides this method to return it. */ /* */ protected /* */ TransactionConfig getAutoCommitTransactionConfig() { return null; } boolean isUpdateAllowed() { return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy