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

com.sleepycat.persist.PrimaryIndex 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.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
/*  */
import com.sleepycat.je.DbInternal;
/*  */
import com.sleepycat.je.Environment;
/*  */
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.Get;
/*  */
import com.sleepycat.je.LockMode;
/*  */
import com.sleepycat.je.OperationFailureException;
import com.sleepycat.je.OperationResult;
/*  */
import com.sleepycat.je.OperationStatus;
/*  */
import com.sleepycat.je.Put;
import com.sleepycat.je.ReadOptions;
/*  */
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
/*  */
import com.sleepycat.je.WriteOptions;
/*  */
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 extends BasicIndex { private Class entityClass; private EntityBinding entityBinding; private SortedMap map; private PersistKeyAssigner keyAssigner; /** * Creates a primary index without using an 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.

* * @param database the primary database. * * @param keyClass the class of the primary key. * * @param keyBinding the binding to be used for primary keys. * * @param entityClass the class of the entities stored in this index. * * @param entityBinding the binding to be used for entities. * * @throws DatabaseException the base class for all BDB exceptions. */ public PrimaryIndex(Database database, Class keyClass, EntryBinding keyBinding, Class entityClass, EntityBinding entityBinding) throws DatabaseException { super(database, keyClass, keyBinding, new EntityValueAdapter(entityClass, entityBinding, false)); this.entityClass = entityClass; this.entityBinding = entityBinding; if (entityBinding instanceof PersistEntityBinding) { keyAssigner = ((PersistEntityBinding) entityBinding).getKeyAssigner(); } } /** * Returns the primary key class for this index. * * @return the key class. */ public Class getKeyClass() { return keyClass; } /** * Returns the primary key binding for this index. * * @return the key binding. */ public EntryBinding getKeyBinding() { return keyBinding; } /** * Returns the entity class for this index. * * @return the entity class. */ public Class getEntityClass() { return entityClass; } /** * Returns the entity binding for this index. * * @return the entity binding. */ public EntityBinding getEntityBinding() { return entityBinding; } /** * 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.

* *

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 OperationFailureException if one of the Write * Operation Failures occurs. * * @throws EnvironmentFailureException if an unexpected, internal or * environment-wide failure occurs. * * * @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 OperationFailureException if one of the Write * Operation Failures occurs. * * @throws EnvironmentFailureException if an unexpected, internal or * environment-wide failure occurs. * * * @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, getAutoCommitTransactionConfig()); 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 OperationFailureException if one of the Write * Operation Failures occurs. * * @throws EnvironmentFailureException if an unexpected, internal or * environment-wide failure occurs. * * * @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 OperationFailureException if one of the Write * Operation Failures occurs. * * @throws EnvironmentFailureException if an unexpected, internal or * environment-wide failure occurs. * * * @throws DatabaseException the base class for all BDB exceptions. */ public void putNoReturn(Transaction txn, E entity) throws DatabaseException { /* */ if (DbCompat.IS_JE) { put(txn, entity, Put.OVERWRITE, null); return; } /* */ 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 OperationFailureException if one of the Write * Operation Failures occurs. * * @throws EnvironmentFailureException if an unexpected, internal or * environment-wide failure occurs. * * * @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 OperationFailureException if one of the Write * Operation Failures occurs. * * @throws EnvironmentFailureException if an unexpected, internal or * environment-wide failure occurs. * * * @throws DatabaseException the base class for all BDB exceptions. */ public boolean putNoOverwrite(Transaction txn, E entity) throws DatabaseException { /* */ if (DbCompat.IS_JE) { return put(txn, entity, Put.NO_OVERWRITE, null) != null; } /* */ 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); } /* */ /** * Inserts or updates an entity, using Put type and WriteOptions * parameters, and returning an OperationResult. * *

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. * * @param putType is {@link Put#OVERWRITE} or {@link Put#NO_OVERWRITE}. * * @param options the WriteOptions, or null to use default options. * * @return the OperationResult if the record is written, else null. If * {@code Put.NO_OVERWRITE} is used, null is returned if an entity with the * same primary key is already present. If {@code Put.OVERWRITE} is used, * null is never returned. * * @throws OperationFailureException if one of the Write * Operation Failures occurs. * * @throws EnvironmentFailureException if an unexpected, internal or * environment-wide failure occurs. * * @throws DatabaseException the base class for all BDB exceptions. * * @since 7.0 */ public OperationResult put(Transaction txn, E entity, Put putType, WriteOptions options) { if (putType != Put.OVERWRITE && putType != Put.NO_OVERWRITE) { throw new IllegalArgumentException( "putType not allowed: " + putType); } DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry dataEntry = new DatabaseEntry(); assignKey(entity, keyEntry); entityBinding.objectToData(entity, dataEntry); return db.put(txn, keyEntry, dataEntry, putType, options); } /* */ /** * 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 { /* */ 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 dataEntry = new DatabaseEntry(); keyBinding.objectToEntry(key, keyEntry); OperationStatus status = db.get(txn, keyEntry, dataEntry, lockMode); if (status == OperationStatus.SUCCESS) { return makeEntity(key, keyEntry, dataEntry); } else { return null; } } /* */ public EntityResult get(Transaction txn, PK key, Get getType, ReadOptions options) throws DatabaseException { checkGetType(getType); DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry dataEntry = new DatabaseEntry(); keyBinding.objectToEntry(key, keyEntry); OperationResult result = db.get( txn, keyEntry, dataEntry, getType, options); if (result != null) { return new EntityResult<>( makeEntity(key, keyEntry, dataEntry), result); } else { return null; } } /* */ private E makeEntity(PK key, DatabaseEntry keyEntry, DatabaseEntry dataEntry) { return (entityBinding instanceof PersistEntityBinding) ? (E)((PersistEntityBinding) entityBinding). entryToObjectWithPriKey(key, dataEntry) : entityBinding.entryToObject(keyEntry, dataEntry); } 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 true; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy