package de.huepattl.playground.berkeleydb;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.SequenceConfig;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.StoreConfig;
import de.huepattl.playground.berkeleydb.domain.model.Person;
import java.util.Date;
import java.time.LocalDate;
import java.time.Month;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.*;
/**
* Some more advanced persistence showcases demonstrating related entities etc.
*
* @author Timo Boewing (bjblazko@gmail.com)
* @see SimplePersistenceTest
* @see SearchMethodsTest
* @since 2013-04-29
*/
public class AdvancedPersistenceTest {
Logger LOG = LogManager.getLogger(AdvancedPersistenceTest.class.getName());
/**
* Takes care of allocating a temporary folder and cleaning it up after test execution.
* If you need to have a look at the data in this location, you need to set a breakpoint
* and run as debug.
*/
@Rule
public TemporaryFolder dbLocation = new TemporaryFolder();
/**
* Our persistence store. This is a JE specific feature that not exists in the non-JE Berkeley DB.
*/
private EntityStore entityStore;
/**
* Executed before test: bootstrap our database.
*
* @see com.sleepycat.je.EnvironmentConfig
* @see com.sleepycat.je.Environment
* @see com.sleepycat.persist.StoreConfig
*/
@Before
public void setupDatabase() {
/*
Create default database environment. You might configure stuff like locking behaviour,
caching, recovery etc.
*/
EnvironmentConfig environmentConfig = new EnvironmentConfig();
environmentConfig.setAllowCreate(true); // create if not exists
/*
The database environment itself. You can, for example, use this instance to get
statistics on current transactions etc.
*/
Environment environment = new Environment(dbLocation.getRoot(), environmentConfig);
LOG.info("Using DB location: " + dbLocation.getRoot().getAbsolutePath());
/*
Configure and create the entity-related store. It is thread-safe and handles entity handling.
*/
StoreConfig storeConfig = new StoreConfig();
storeConfig.setAllowCreate(true);
this.entityStore = new EntityStore(environment, "testDatabase", storeConfig);
SequenceConfig sequenceConfig = new SequenceConfig();
sequenceConfig.setAllowCreate(true);
sequenceConfig.setInitialValue(1);
this.entityStore.setSequenceConfig("ID", sequenceConfig);
}
/**
* Just the verify it is working, this is code similar to
* {@link de.huepattl.playground.berkeleydb.SimplePersistenceTest#simplePersistence()}.
*
* @throws Exception Arrrr...
* @see #setupDatabase()
*/
@Test
public void simplePersistence() throws Exception {
Person person;
PrimaryIndex<Long, Person> primaryIndex = entityStore.getPrimaryIndex(Long.class, Person.class);
person = new Person("Doe", "John");
// Birthday: Dec 31st 1977
person.setDateOfBirth(new Date(java.sql.Date.valueOf(LocalDate.of(1977, Month.DECEMBER, 9)).getTime()));
primaryIndex.put(person);
person = primaryIndex.get(person.getId());
LOG.info(person);
assertNotNull(person);
}
/**
* Entity relations are always expressed through ID references, so not direct references. Probably, this demands
* for a seperate design of domain model classes and persistence entities because you most likely want to express
* such relations differently in your business code.<p>
* In your entity models, relations are built by using the {@link com.sleepycat.persist.model.SecondaryKey}
* annotation. Just have a look at the {@link Person} entity.
*/
@Test
public void relations() {
final long id;
Person person;
Person friendA;
Person friendB;
Person friendC;
PrimaryIndex<Long, Person> primaryIndex = entityStore.getPrimaryIndex(Long.class, Person.class);
/*
Set up test data: one guy (John Doe) and three friends: Alfred, Berta and Charles.
*/
{
person = new Person("Doe", "John");
friendA = new Person("Friend", "Alfred");
friendB = new Person("Friend", "Bertha");
friendC = new Person("Friend", "Charles");
}
/*
Persist everything. First, we store the dependencies (the friends), then we save the main
persons referencing these friends.
*/
{
primaryIndex.put(friendA);
primaryIndex.put(friendB);
primaryIndex.put(friendC);
LOG.info("Friends (not yet related): " + friendA + ", " + friendB + ", " + friendC);
person.addFriend(friendA.getId());
person.addFriend(friendB.getId());
person.addFriend(friendC.getId());
primaryIndex.put(person);
id = person.getId();
}
/*
Let us see if everything was stored...
*/
{
person = primaryIndex.get(id);
LOG.info(person + ", has friends with IDs: " + person.getFriends());
for (final long friendId : person.getFriends()) {
final Person friend = primaryIndex.get(friendId);
LOG.info("-> " + friend);
}
}
}
/**
* Showcases the creation of some entities that are reference in other entities. When the entity is deleted, all
* references should be removed as well. This can be achieved by using the {@link
* com.sleepycat.persist.model.DeleteAction#CASCADE} option of the {@link com.sleepycat.persist.model.SecondaryKey}
* annotation.
*/
@Test
public void deleteAndCascade() {
final long friendToDeleteId;
Person person;
Person friendA;
Person friendB;
PrimaryIndex<Long, Person> primaryIndex = entityStore.getPrimaryIndex(Long.class, Person.class);
/*
Set up test data: one guy (John Doe) and two friends: Alfred and Berta.
*/
{
person = new Person("Doe", "John");
friendA = new Person("Friend", "Alfred");
friendB = new Person("Friend", "Bertha");
}
/*
Persist.
*/
{
primaryIndex.put(friendA);
friendToDeleteId = friendA.getId(); // remind ID (Alfred)
primaryIndex.put(friendB);
person.addFriend(friendA.getId());
person.addFriend(friendB.getId());
primaryIndex.put(person);
}
/*
Now, let us delete a friend entity and see if it was also removed from the friend
list (remember: we annotated the property so that deletions are cascaded through
all usages - otherwise an exception would be thrown. Funnily, the correct annotation
attribute is NULLIFY instead if CASCADE - seems that CASCADE would delete entities
that just reference the deleted entity - quite strange...
*/
{
if (primaryIndex.delete(friendToDeleteId)) {
LOG.info("Deleted entity with ID=" + friendToDeleteId);
assertNull(primaryIndex.get(friendToDeleteId));
/*
Ok, entity deleted - but what about the reference in friends list?
Reload person, iterate all friends and see that the ID of the deleted
one is not present any longer.
*/
person = primaryIndex.get(person.getId());
for (final long friendId : person.getFriends()) {
assertNotEquals(friendToDeleteId, friendId);
}
} else {
LOG.error("Failed to delete an entity...");
}
}
}
}