You can configure JE to use serializable isolation (see Transactions and Concurrency). Serializable isolation prevents transactions from seeing phantoms. Phantoms occur when a transaction obtains inconsistent results when performing a given query.
Suppose a transaction performs a search, S, and as a result of that search NOTFOUND is returned. If you are using only repeatable read isolation (the default isolation level), it is possible for the same transaction to perform S at a later point in time and return SUCCESS instead of NOTFOUND. This can occur if another thread of control modified the database in such a way as to cause S to successfully locate data, where before no data was found. When this situation occurs, the results returned by S are said to be a phantom.
To prevent phantoms, you can use serializable isolation. Note that this causes JE to perform additional locking in order to prevent keys from being inserted until the transaction ends. However, this additional locking can also result in reduced concurrency for your application, which means that your database access can be slowed.
You configure serializable isolation for all transactions in your environment by using EnvironmentConfig.setTxnSerializableIsolation():
package com.sleepycat.examples.je.gettingStarted; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.Environment; import com.sleepycat.je.EnvironmentConfig; import com.sleepycat.je.Transaction; import com.sleepycat.je.LockMode; ... Database myDb = null; Environment myEnv = null; Transaction txn = null; try { // Open an environment EnvironmentConfig envConfig = new EnvironmentConfig(); envConfig.setAllowCreate(true); envConfig.setTransactional(true); // Use serializable isolation envConfig.setTxnSerializableIsolation(true); myEnv = new Environment(myHomeDirectory, envConfig); // Database open omitted ... txn = myEnv.beginTransaction(null, null); DatabaseEntry theKey = new DatabaseEntry((new String("theKey")).getBytes("UTF-8")); DatabaseEntry theData = new DatabaseEntry(); myDb.get(txn, theKey, theData, LockMode.DEFAULT); } catch (Exception e) { // Exception handling goes here }
If you do not configure serializable isolation for all transactions, you can configure serializable isolation for a specific transaction using TransactionConfig.setSerializableIsolation():
package com.sleepycat.examples.je.gettingStarted; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.Environment; import com.sleepycat.je.LockMode; import com.sleepycat.je.Transaction; import com.sleepycat.je.TransactionConfig; ... Database myDb = null; Environment myEnv = null; Transaction txn = null; try { // Environment and database open omitted ... TransactionConfig tc = new TransactionConfig(); tc.setSerializableIsolation(true); // Use serializable isolation txn = myEnv.beginTransaction(null, tc); DatabaseEntry theKey = new DatabaseEntry((new String("theKey")).getBytes("UTF-8")); DatabaseEntry theData = new DatabaseEntry(); myDb.get(txn, theKey, theData, LockMode.DEFAULT); } catch (Exception e) { // Exception handling goes here }