476 lines
16 KiB
Java
476 lines
16 KiB
Java
|
/*-
|
|||
|
* Copyright (C) 2002, 2017, 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 je;
|
|||
|
|
|||
|
import java.io.File;
|
|||
|
import java.io.Serializable;
|
|||
|
import java.util.HashSet;
|
|||
|
import java.util.Iterator;
|
|||
|
import java.util.Set;
|
|||
|
|
|||
|
import com.sleepycat.bind.EntryBinding;
|
|||
|
import com.sleepycat.bind.serial.SerialBinding;
|
|||
|
import com.sleepycat.bind.serial.StoredClassCatalog;
|
|||
|
import com.sleepycat.bind.tuple.StringBinding;
|
|||
|
import com.sleepycat.je.Database;
|
|||
|
import com.sleepycat.je.DatabaseConfig;
|
|||
|
import com.sleepycat.je.DatabaseEntry;
|
|||
|
import com.sleepycat.je.DatabaseException;
|
|||
|
import com.sleepycat.je.Environment;
|
|||
|
import com.sleepycat.je.EnvironmentConfig;
|
|||
|
import com.sleepycat.je.ForeignKeyDeleteAction;
|
|||
|
import com.sleepycat.je.ForeignMultiKeyNullifier;
|
|||
|
import com.sleepycat.je.OperationStatus;
|
|||
|
import com.sleepycat.je.SecondaryConfig;
|
|||
|
import com.sleepycat.je.SecondaryCursor;
|
|||
|
import com.sleepycat.je.SecondaryDatabase;
|
|||
|
import com.sleepycat.je.SecondaryMultiKeyCreator;
|
|||
|
import com.sleepycat.je.Transaction;
|
|||
|
|
|||
|
/**
|
|||
|
* An example of using many-to-many and one-to-many secondary indices.
|
|||
|
*/
|
|||
|
public class ToManyExample {
|
|||
|
|
|||
|
private final Environment env;
|
|||
|
private Database catalogDb;
|
|||
|
private Database animalDb;
|
|||
|
private Database personDb;
|
|||
|
private SecondaryDatabase personByEmail;
|
|||
|
private SecondaryDatabase personByAnimal;
|
|||
|
private EntryBinding<String> keyBinding;
|
|||
|
private EntryBinding<Person> personBinding;
|
|||
|
private EntryBinding<Animal> animalBinding;
|
|||
|
|
|||
|
/**
|
|||
|
* Runs the example program, given a single "-h HOME" argument.
|
|||
|
*/
|
|||
|
public static void main(String[] args) {
|
|||
|
|
|||
|
if (args.length != 2 || !"-h".equals(args[0])) {
|
|||
|
System.out.println("Usage: java " +
|
|||
|
ToManyExample.class.getName() +
|
|||
|
" -h ENV_HOME");
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
String homeDir = args[1];
|
|||
|
|
|||
|
try {
|
|||
|
ToManyExample example = new ToManyExample(homeDir);
|
|||
|
example.exec();
|
|||
|
example.close();
|
|||
|
} catch (DatabaseException e) {
|
|||
|
e.printStackTrace();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Opens the environment and all databases.
|
|||
|
*/
|
|||
|
private ToManyExample(String homeDir) throws DatabaseException {
|
|||
|
|
|||
|
/* Open the environment. */
|
|||
|
EnvironmentConfig envConfig = new EnvironmentConfig();
|
|||
|
envConfig.setAllowCreate(true);
|
|||
|
envConfig.setTransactional(true);
|
|||
|
env = new Environment(new File(homeDir), envConfig);
|
|||
|
|
|||
|
/* Open/create all databases in a transaction. */
|
|||
|
Transaction txn = env.beginTransaction(null, null);
|
|||
|
try {
|
|||
|
/* A standard (no duplicates) database config. */
|
|||
|
DatabaseConfig dbConfig = new DatabaseConfig();
|
|||
|
dbConfig.setAllowCreate(true);
|
|||
|
dbConfig.setTransactional(true);
|
|||
|
|
|||
|
/* The catalog is used for the serial binding. */
|
|||
|
catalogDb = env.openDatabase(txn, "catalog", dbConfig);
|
|||
|
StoredClassCatalog catalog = new StoredClassCatalog(catalogDb);
|
|||
|
personBinding = new SerialBinding(catalog, null);
|
|||
|
animalBinding = new SerialBinding(catalog, null);
|
|||
|
keyBinding = new StringBinding();
|
|||
|
|
|||
|
/* Open the person and animal primary DBs. */
|
|||
|
animalDb = env.openDatabase(txn, "animal", dbConfig);
|
|||
|
personDb = env.openDatabase(txn, "person", dbConfig);
|
|||
|
|
|||
|
/*
|
|||
|
* A standard secondary config; duplicates, key creators and key
|
|||
|
* nullifiers are specified below.
|
|||
|
*/
|
|||
|
SecondaryConfig secConfig = new SecondaryConfig();
|
|||
|
secConfig.setAllowCreate(true);
|
|||
|
secConfig.setTransactional(true);
|
|||
|
|
|||
|
/*
|
|||
|
* Open the secondary database for personByEmail. This is a
|
|||
|
* one-to-many index because duplicates are not configured.
|
|||
|
*/
|
|||
|
secConfig.setSortedDuplicates(false);
|
|||
|
secConfig.setMultiKeyCreator(new EmailKeyCreator());
|
|||
|
personByEmail = env.openSecondaryDatabase(txn, "personByEmail",
|
|||
|
personDb, secConfig);
|
|||
|
|
|||
|
/*
|
|||
|
* Open the secondary database for personByAnimal. This is a
|
|||
|
* many-to-many index because duplicates are configured. Foreign
|
|||
|
* key constraints are specified to ensure that all animal keys
|
|||
|
* exist in the animal database.
|
|||
|
*/
|
|||
|
secConfig.setSortedDuplicates(true);
|
|||
|
secConfig.setMultiKeyCreator(new AnimalKeyCreator());
|
|||
|
secConfig.setForeignMultiKeyNullifier(new AnimalKeyNullifier());
|
|||
|
secConfig.setForeignKeyDatabase(animalDb);
|
|||
|
secConfig.setForeignKeyDeleteAction(ForeignKeyDeleteAction.NULLIFY);
|
|||
|
personByAnimal = env.openSecondaryDatabase(txn, "personByAnimal",
|
|||
|
personDb, secConfig);
|
|||
|
|
|||
|
txn.commit();
|
|||
|
} catch (DatabaseException e) {
|
|||
|
txn.abort();
|
|||
|
throw e;
|
|||
|
} catch (RuntimeException e) {
|
|||
|
txn.abort();
|
|||
|
throw e;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Closes all databases and the environment.
|
|||
|
*/
|
|||
|
private void close() throws DatabaseException {
|
|||
|
|
|||
|
if (personByEmail != null) {
|
|||
|
personByEmail.close();
|
|||
|
}
|
|||
|
if (personByAnimal != null) {
|
|||
|
personByAnimal.close();
|
|||
|
}
|
|||
|
if (catalogDb != null) {
|
|||
|
catalogDb.close();
|
|||
|
}
|
|||
|
if (personDb != null) {
|
|||
|
personDb.close();
|
|||
|
}
|
|||
|
if (animalDb != null) {
|
|||
|
animalDb.close();
|
|||
|
}
|
|||
|
if (env != null) {
|
|||
|
env.close();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Adds, updates, prints and deletes Person records with many-to-many and
|
|||
|
* one-to-many secondary indices.
|
|||
|
*/
|
|||
|
private void exec()
|
|||
|
throws DatabaseException {
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nInsert some animals.");
|
|||
|
Animal dogs = insertAndPrintAnimal("dogs", true);
|
|||
|
Animal fish = insertAndPrintAnimal("fish", false);
|
|||
|
Animal horses = insertAndPrintAnimal("horses", true);
|
|||
|
Animal donkeys = insertAndPrintAnimal("donkeys", true);
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nInsert a new empty person.");
|
|||
|
Person kathy = new Person();
|
|||
|
kathy.name = "Kathy";
|
|||
|
putPerson(kathy);
|
|||
|
printPerson("Kathy");
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nAdd favorites/addresses and update the record.");
|
|||
|
kathy.favoriteAnimals.add(horses.name);
|
|||
|
kathy.favoriteAnimals.add(dogs.name);
|
|||
|
kathy.favoriteAnimals.add(fish.name);
|
|||
|
kathy.emailAddresses.add("kathy@kathy.com");
|
|||
|
kathy.emailAddresses.add("kathy@yahoo.com");
|
|||
|
putPerson(kathy);
|
|||
|
printPerson("Kathy");
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nChange favorites and addresses and update the person record.");
|
|||
|
kathy.favoriteAnimals.remove(fish.name);
|
|||
|
kathy.favoriteAnimals.add(donkeys.name);
|
|||
|
kathy.emailAddresses.add("kathy@gmail.com");
|
|||
|
kathy.emailAddresses.remove("kathy@yahoo.com");
|
|||
|
putPerson(kathy);
|
|||
|
printPerson("Kathy");
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nInsert another person with some of the same favorites.");
|
|||
|
Person mark = new Person();
|
|||
|
mark.favoriteAnimals.add(dogs.name);
|
|||
|
mark.favoriteAnimals.add(horses.name);
|
|||
|
mark.name = "Mark";
|
|||
|
putPerson(mark);
|
|||
|
printPerson("Mark");
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nPrint by favorite animal index.");
|
|||
|
printByIndex(personByAnimal);
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nPrint by email address index.");
|
|||
|
printByIndex(personByEmail);
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nDelete 'dogs' and print again by favorite animal index.");
|
|||
|
deleteAnimal(dogs.name);
|
|||
|
printPerson("Kathy");
|
|||
|
printPerson("Mark");
|
|||
|
printByIndex(personByAnimal);
|
|||
|
|
|||
|
System.out.println
|
|||
|
("\nDelete both records and print again (should print nothing).");
|
|||
|
deletePerson("Kathy");
|
|||
|
deletePerson("Mark");
|
|||
|
printPerson("Kathy");
|
|||
|
printPerson("Mark");
|
|||
|
printByIndex(personByAnimal);
|
|||
|
printByIndex(personByEmail);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Inserts an animal record and prints it. Uses auto-commit.
|
|||
|
*/
|
|||
|
private Animal insertAndPrintAnimal(String name, boolean furry)
|
|||
|
throws DatabaseException {
|
|||
|
|
|||
|
Animal animal = new Animal();
|
|||
|
animal.name = name;
|
|||
|
animal.furry = furry;
|
|||
|
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
keyBinding.objectToEntry(name, key);
|
|||
|
|
|||
|
DatabaseEntry data = new DatabaseEntry();
|
|||
|
animalBinding.objectToEntry(animal, data);
|
|||
|
|
|||
|
OperationStatus status = animalDb.putNoOverwrite(null, key, data);
|
|||
|
if (status == OperationStatus.SUCCESS) {
|
|||
|
System.out.println(animal);
|
|||
|
} else {
|
|||
|
System.out.println("Animal was not inserted: " + name +
|
|||
|
" (" + status + ')');
|
|||
|
}
|
|||
|
|
|||
|
return animal;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Deletes an animal. Uses auto-commit.
|
|||
|
*/
|
|||
|
private boolean deleteAnimal(String name)
|
|||
|
throws DatabaseException {
|
|||
|
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
keyBinding.objectToEntry(name, key);
|
|||
|
|
|||
|
OperationStatus status = animalDb.delete(null, key);
|
|||
|
return status == OperationStatus.SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Gets a person by name and prints it.
|
|||
|
*/
|
|||
|
private void printPerson(String name)
|
|||
|
throws DatabaseException {
|
|||
|
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
keyBinding.objectToEntry(name, key);
|
|||
|
|
|||
|
DatabaseEntry data = new DatabaseEntry();
|
|||
|
|
|||
|
OperationStatus status = personDb.get(null, key, data, null);
|
|||
|
if (status == OperationStatus.SUCCESS) {
|
|||
|
Person person = personBinding.entryToObject(data);
|
|||
|
person.name = keyBinding.entryToObject(key);
|
|||
|
System.out.println(person);
|
|||
|
} else {
|
|||
|
System.out.println("Person not found: " + name);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Prints all person records by a given secondary index.
|
|||
|
*/
|
|||
|
private void printByIndex(SecondaryDatabase secDb)
|
|||
|
throws DatabaseException {
|
|||
|
|
|||
|
DatabaseEntry secKey = new DatabaseEntry();
|
|||
|
DatabaseEntry priKey = new DatabaseEntry();
|
|||
|
DatabaseEntry priData = new DatabaseEntry();
|
|||
|
|
|||
|
SecondaryCursor cursor = secDb.openSecondaryCursor(null, null);
|
|||
|
try {
|
|||
|
while (cursor.getNext(secKey, priKey, priData, null) ==
|
|||
|
OperationStatus.SUCCESS) {
|
|||
|
Person person = personBinding.entryToObject(priData);
|
|||
|
person.name = keyBinding.entryToObject(priKey);
|
|||
|
System.out.println("Index key [" +
|
|||
|
keyBinding.entryToObject(secKey) +
|
|||
|
"] maps to primary key [" +
|
|||
|
person.name + ']');
|
|||
|
}
|
|||
|
} finally {
|
|||
|
cursor.close();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Inserts or updates a person. Uses auto-commit.
|
|||
|
*/
|
|||
|
private void putPerson(Person person)
|
|||
|
throws DatabaseException {
|
|||
|
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
keyBinding.objectToEntry(person.name, key);
|
|||
|
|
|||
|
DatabaseEntry data = new DatabaseEntry();
|
|||
|
personBinding.objectToEntry(person, data);
|
|||
|
|
|||
|
personDb.put(null, key, data);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Deletes a person. Uses auto-commit.
|
|||
|
*/
|
|||
|
private boolean deletePerson(String name)
|
|||
|
throws DatabaseException {
|
|||
|
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
keyBinding.objectToEntry(name, key);
|
|||
|
|
|||
|
OperationStatus status = personDb.delete(null, key);
|
|||
|
return status == OperationStatus.SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* A person object.
|
|||
|
*/
|
|||
|
@SuppressWarnings("serial")
|
|||
|
private static class Person implements Serializable {
|
|||
|
|
|||
|
/** The primary key. */
|
|||
|
private transient String name;
|
|||
|
|
|||
|
/** A many-to-many set of keys. */
|
|||
|
private final Set<String> favoriteAnimals = new HashSet<String>();
|
|||
|
|
|||
|
/** A one-to-many set of keys. */
|
|||
|
private final Set<String> emailAddresses = new HashSet<String>();
|
|||
|
|
|||
|
@Override
|
|||
|
public String toString() {
|
|||
|
return "Person {" +
|
|||
|
"\n Name: " + name +
|
|||
|
"\n FavoriteAnimals: " + favoriteAnimals +
|
|||
|
"\n EmailAddresses: " + emailAddresses +
|
|||
|
"\n}";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* An animal object.
|
|||
|
*/
|
|||
|
@SuppressWarnings("serial")
|
|||
|
private static class Animal implements Serializable {
|
|||
|
|
|||
|
/** The primary key. */
|
|||
|
private transient String name;
|
|||
|
|
|||
|
/** A non-indexed property. */
|
|||
|
private boolean furry;
|
|||
|
|
|||
|
@Override
|
|||
|
public String toString() {
|
|||
|
return "Animal {" +
|
|||
|
"\n Name: " + name +
|
|||
|
"\n Furry: " + furry +
|
|||
|
"\n}";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns the set of email addresses for a person. This is an example
|
|||
|
* of a multi-key creator for a to-many index.
|
|||
|
*/
|
|||
|
private class EmailKeyCreator implements SecondaryMultiKeyCreator {
|
|||
|
|
|||
|
public void createSecondaryKeys(SecondaryDatabase secondary,
|
|||
|
DatabaseEntry primaryKey,
|
|||
|
DatabaseEntry primaryData,
|
|||
|
Set<DatabaseEntry> results) {
|
|||
|
Person person = personBinding.entryToObject(primaryData);
|
|||
|
copyKeysToEntries(person.emailAddresses, results);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns the set of favorite animals for a person. This is an example
|
|||
|
* of a multi-key creator for a to-many index.
|
|||
|
*/
|
|||
|
private class AnimalKeyCreator implements SecondaryMultiKeyCreator {
|
|||
|
|
|||
|
public void createSecondaryKeys(SecondaryDatabase secondary,
|
|||
|
DatabaseEntry primaryKey,
|
|||
|
DatabaseEntry primaryData,
|
|||
|
Set<DatabaseEntry> results) {
|
|||
|
Person person = personBinding.entryToObject(primaryData);
|
|||
|
copyKeysToEntries(person.favoriteAnimals, results);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* A utility method to copy a set of keys (Strings) into a set of
|
|||
|
* DatabaseEntry objects.
|
|||
|
*/
|
|||
|
private void copyKeysToEntries(Set<String> keys,
|
|||
|
Set<DatabaseEntry> entries) {
|
|||
|
|
|||
|
for (Iterator<String> i = keys.iterator(); i.hasNext();) {
|
|||
|
DatabaseEntry entry = new DatabaseEntry();
|
|||
|
keyBinding.objectToEntry(i.next(), entry);
|
|||
|
entries.add(entry);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Removes a given key from the set of favorite animals for a person. This
|
|||
|
* is an example of a nullifier for a to-many index. The nullifier is
|
|||
|
* called when an animal record is deleted because we configured this
|
|||
|
* secondary with ForeignKeyDeleteAction.NULLIFY.
|
|||
|
*/
|
|||
|
private class AnimalKeyNullifier implements ForeignMultiKeyNullifier {
|
|||
|
|
|||
|
public boolean nullifyForeignKey(SecondaryDatabase secondary,
|
|||
|
DatabaseEntry primaryKey,
|
|||
|
DatabaseEntry primaryData,
|
|||
|
DatabaseEntry secKey) {
|
|||
|
Person person = personBinding.entryToObject(primaryData);
|
|||
|
String key = keyBinding.entryToObject(secKey);
|
|||
|
if (person.favoriteAnimals.remove(key)) {
|
|||
|
personBinding.objectToEntry(person, primaryData);
|
|||
|
return true;
|
|||
|
} else {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|