/*- * 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.Cursor; import com.sleepycat.db.CursorConfig; import com.sleepycat.db.Database; import com.sleepycat.db.DatabaseEntry; import com.sleepycat.db.DatabaseException; import com.sleepycat.db.Environment; import com.sleepycat.db.LockMode; import com.sleepycat.db.OperationStatus; import com.sleepycat.db.Transaction; import com.sleepycat.persist.impl.PersistEntityBinding; import com.sleepycat.persist.impl.PersistKeyAssigner; import com.sleepycat.persist.model.Entity; import com.sleepycat.persist.model.PrimaryKey; /** * The primary index for an entity class and its primary key. * *
{@code PrimaryIndex} objects are thread-safe. Multiple threads may * safely call the methods of a shared {@code PrimaryIndex} object.
* *{@code PrimaryIndex} implements {@link EntityIndex} to map the primary * key type (PK) to the entity type (E).
* *The {@link Entity} annotation may be used to define an entity class and * the {@link PrimaryKey} annotation may be used to define a primary key as * shown in the following example.
* ** {@literal @Entity} * class Employee { * * {@literal @PrimaryKey} * long id; * * String name; * * Employee(long id, String name) { * this.id = id; * this.name = name; * } * * private Employee() {} // For bindings * }* *
To obtain the {@code PrimaryIndex} for a given entity class, call {@link * EntityStore#getPrimaryIndex EntityStore.getPrimaryIndex}, passing the * primary key class and the entity class. For example:
* ** EntityStore store = new EntityStore(...); * * {@code PrimaryIndex* * *} primaryIndex = * store.getPrimaryIndex(Long.class, Employee.class);
Note that {@code Long.class} is passed as the primary key class, but the * primary key field has the primitive type {@code long}. When a primitive * primary key field is used, the corresponding primitive wrapper class is used * to access the primary index. For more information on key field types, see * {@link PrimaryKey}.
* *The {@code PrimaryIndex} provides the primary storage and access methods * for the instances of a particular entity class. Entities are inserted and * updated in the {@code PrimaryIndex} by calling a method in the family of * {@link #put} methods. The {@link #put} method will insert the entity if no * entity with the same primary key already exists. If an entity with the same * primary key does exist, it will update the entity and return the existing * (old) entity. For example:
* ** Employee oldEntity; * oldEntity = primaryIndex.put(new Employee(1, "Jane Smith")); // Inserts an entity * assert oldEntity == null; * oldEntity = primaryIndex.put(new Employee(2, "Joan Smith")); // Inserts an entity * assert oldEntity == null; * oldEntity = primaryIndex.put(new Employee(2, "Joan M. Smith")); // Updates an entity * assert oldEntity != null;* *
The {@link #putNoReturn} method can be used to avoid the overhead of * returning the existing entity, when the existing entity is not important to * the application. The return type of {@link #putNoReturn} is void. For * example:
* ** primaryIndex.putNoReturn(new Employee(1, "Jane Smith")); // Inserts an entity * primaryIndex.putNoReturn(new Employee(2, "Joan Smith")); // Inserts an entity * primaryIndex.putNoReturn(new Employee(2, "Joan M. Smith")); // Updates an entity* *
The {@link #putNoOverwrite} method can be used to ensure that an existing * entity is not overwritten. {@link #putNoOverwrite} returns true if the * entity was inserted, or false if an existing entity exists and no action was * taken. For example:
* *
* boolean inserted; * inserted = primaryIndex.putNoOverwrite(new Employee(1, "Jane Smith")); // Inserts an entity * assert inserted; * inserted = primaryIndex.putNoOverwrite(new Employee(2, "Joan Smith")); // Inserts an entity * assert inserted; * inserted = primaryIndex.putNoOverwrite(new Employee(2, "Joan M. Smith")); // No action was taken! * assert !inserted;* *
Primary key values must be unique, in other words, each instance of a * given entity class must have a distinct primary key value. Rather than * assigning the unique primary key values yourself, a sequence can be * used to assign sequential integer values automatically, starting with the * value 1 (one). A sequence is defined using the {@link PrimaryKey#sequence} * annotation property. For example:
* ** {@literal @Entity} * class Employee { * * {@literal @PrimaryKey(sequence="ID")} * long id; * * String name; * * Employee(String name) { * this.name = name; * } * * private Employee() {} // For bindings * }* *
The name of the sequence used above is "ID". Any name can be used. If * the same sequence name is used in more than one entity class, the sequence * will be shared by those classes, in other words, a single sequence of * integers will be used for all instances of those classes. See {@link * PrimaryKey#sequence} for more information.
* *Any method in the family of {@link #put} methods may be used to insert * entities where the primary key is assigned from a sequence. When the {@link * #put} method returns, the primary key field of the entity object will be set * to the assigned key value. For example:
* ** Employee employee; * employee = new Employee("Jane Smith"); * primaryIndex.putNoReturn(employee); // Inserts an entity * assert employee.id == 1; * employee = new Employee("Joan Smith"); * primaryIndex.putNoReturn(employee); // Inserts an entity * assert employee.id == 2;* *
This begs the question: How do you update an existing entity, without * assigning a new primary key? The answer is that the {@link #put} methods * will only assign a new key from the sequence if the primary key field is * zero or null (for reference types). If an entity with a non-zero and * non-null key field is passed to a {@link #put} method, any existing entity * with that primary key value will be updated. For example:
* ** Employee employee; * employee = new Employee("Jane Smith"); * primaryIndex.putNoReturn(employee); // Inserts an entity * assert employee.id == 1; * employee = new Employee("Joan Smith"); * primaryIndex.putNoReturn(employee); // Inserts an entity * assert employee.id == 2; * employee.name = "Joan M. Smith"; * primaryIndex.putNoReturn(employee); // Updates an existing entity * assert employee.id == 2;* *
Since {@code PrimaryIndex} 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.
* *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 = new MyEntity(key, ...); * index.put(entity1); * MyEntity entity2 = index.get(key); * assert entity1 == entity2; // always fails! ** * @author Mark Hayes */ public class PrimaryIndex
EntityStore
.
*
* 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#getPrimaryIndex * getPrimaryIndex} is used instead.
* *Note that when this constructor is used directly, primary keys cannot
* be automatically assigned from a sequence. The key assignment feature
* requires knowledge of the primary key field, which is only available if
* an EntityStore
is used. Of course, primary keys may be
* assigned from a sequence manually before calling the put
* methods in this class.
If a {@link PrimaryKey#sequence} is used and the primary key field of * the given entity is null or zero, this method will assign the next value * from the sequence to the primary key field of the given entity.
* *Auto-commit is used implicitly if the store is transactional.
* * @param entity the entity to be inserted or updated. * * @return the existing entity that was updated, or null if the entity was * inserted. * * * @throws DatabaseException the base class for all BDB exceptions. */ public E put(E entity) throws DatabaseException { return put(null, entity); } /** * Inserts an entity and returns null, or updates it if the primary key * already exists and returns the existing entity. * *If a {@link PrimaryKey#sequence} is used and the primary key field of * the given entity is null or zero, this method will assign the next value * from the sequence to the primary key field of the given entity.
* * @param txn the transaction used to protect this operation, null to use * auto-commit, or null if the store is non-transactional. * * @param entity the entity to be inserted or updated. * * @return the existing entity that was updated, or null if the entity was * inserted. * * * @throws DatabaseException the base class for all BDB exceptions. */ public E put(Transaction txn, E entity) throws DatabaseException { DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry dataEntry = new DatabaseEntry(); assignKey(entity, keyEntry); boolean autoCommit = false; Environment env = db.getEnvironment(); if (transactional && txn == null && DbCompat.getThreadTransaction(env) == null) { txn = env.beginTransaction(null, null); autoCommit = true; } CursorConfig cursorConfig = null; if (concurrentDB) { cursorConfig = new CursorConfig(); DbCompat.setWriteCursor(cursorConfig, true); } boolean failed = true; Cursor cursor = db.openCursor(txn, cursorConfig); LockMode lockMode = locking ? LockMode.RMW : null; try { while (true) { OperationStatus status = cursor.getSearchKey(keyEntry, dataEntry, lockMode); if (status == OperationStatus.SUCCESS) { E existing = entityBinding.entryToObject(keyEntry, dataEntry); entityBinding.objectToData(entity, dataEntry); cursor.put(keyEntry, dataEntry); failed = false; return existing; } else { entityBinding.objectToData(entity, dataEntry); status = cursor.putNoOverwrite(keyEntry, dataEntry); if (status != OperationStatus.KEYEXIST) { failed = false; return null; } } } } finally { cursor.close(); if (autoCommit) { if (failed) { txn.abort(); } else { txn.commit(); } } } } /** * Inserts an entity, or updates it if the primary key already exists (does * not return the existing entity). This method may be used instead of * {@link #put(Object)} to save the overhead of returning the existing * entity. * *If a {@link PrimaryKey#sequence} is used and the primary key field of * the given entity is null or zero, this method will assign the next value * from the sequence to the primary key field of the given entity.
* *Auto-commit is used implicitly if the store is transactional.
* * @param entity the entity to be inserted or updated. * * * @throws DatabaseException the base class for all BDB exceptions. */ public void putNoReturn(E entity) throws DatabaseException { putNoReturn(null, entity); } /** * Inserts an entity, or updates it if the primary key already exists (does * not return the existing entity). This method may be used instead of * {@link #put(Transaction,Object)} to save the overhead of returning the * existing entity. * *If a {@link PrimaryKey#sequence} is used and the primary key field of * the given entity is null or zero, this method will assign the next value * from the sequence to the primary key field of the given entity.
* * @param txn the transaction used to protect this operation, null to use * auto-commit, or null if the store is non-transactional. * * @param entity the entity to be inserted or updated. * * * @throws DatabaseException the base class for all BDB exceptions. */ public void putNoReturn(Transaction txn, E entity) throws DatabaseException { DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry dataEntry = new DatabaseEntry(); assignKey(entity, keyEntry); entityBinding.objectToData(entity, dataEntry); db.put(txn, keyEntry, dataEntry); } /** * Inserts an entity and returns true, or returns false if the primary key * already exists. * *If a {@link PrimaryKey#sequence} is used and the primary key field of * the given entity is null or zero, this method will assign the next value * from the sequence to the primary key field of the given entity.
* *Auto-commit is used implicitly if the store is transactional.
* * @param entity the entity to be inserted. * * @return true if the entity was inserted, or false if an entity with the * same primary key is already present. * * * @throws DatabaseException the base class for all BDB exceptions. */ public boolean putNoOverwrite(E entity) throws DatabaseException { return putNoOverwrite(null, entity); } /** * Inserts an entity and returns true, or returns false if the primary key * already exists. * *If a {@link PrimaryKey#sequence} is used and the primary key field of * the given entity is null or zero, this method will assign the next value * from the sequence to the primary key field of the given entity.
* * @param txn the transaction used to protect this operation, null to use * auto-commit, or null if the store is non-transactional. * * @param entity the entity to be inserted. * * @return true if the entity was inserted, or false if an entity with the * same primary key is already present. * * * @throws DatabaseException the base class for all BDB exceptions. */ public boolean putNoOverwrite(Transaction txn, E entity) throws DatabaseException { DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry dataEntry = new DatabaseEntry(); assignKey(entity, keyEntry); entityBinding.objectToData(entity, dataEntry); OperationStatus status = db.putNoOverwrite(txn, keyEntry, dataEntry); return (status == OperationStatus.SUCCESS); } /** * If we are assigning primary keys from a sequence, assign the next key * and set the primary key field. */ private void assignKey(E entity, DatabaseEntry keyEntry) throws DatabaseException { if (keyAssigner != null) { if (!keyAssigner.assignPrimaryKey(entity, keyEntry)) { entityBinding.objectToKey(entity, keyEntry); } } else { entityBinding.objectToKey(entity, keyEntry); } } /* * Of the EntityIndex methods only get()/map()/sortedMap() are implemented * here. All other methods are implemented by BasicIndex. */ public E get(PK key) throws DatabaseException { return get(null, key, null); } public E get(Transaction txn, PK key, LockMode lockMode) throws DatabaseException { DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry dataEntry = new DatabaseEntry(); keyBinding.objectToEntry(key, keyEntry); OperationStatus status = db.get(txn, keyEntry, dataEntry, lockMode); if (status == OperationStatus.SUCCESS) { return entityBinding.entryToObject(keyEntry, dataEntry); } else { return null; } } public Map