You are here: Advanced Features > Concurrency and db4o > Object Container per Unit of Work

Object Container per Unit of Work

One possibility is to use an object container per unit of work and avoid sharing it across threads. A typical example is to use an object container per request. You can create a new session object container at any time.

Let's take a look at an example. This operation starts a background task and carries on doing other things:

// Schedule back-ground tasks
final Future<?> task = executor.submit(new Runnable() {
    @Override
    public void run() {
        updateSomePeople(container);
    }
});
// While doing other work
listAllPeople(container);
UnitsOfWork.java: Schedule back-ground tasks

In this example we use an object container for the background work:

private void updateSomePeople(ObjectContainer rootContainer) {
    ObjectContainer container = rootContainer.ext().openSession();
    try {
        final ObjectSet<Person> people = container.query(new Predicate<Person>() {
            @Override
            public boolean match(Person person) {
                return person.getName().equals("Joe");
            }
        });
        for (Person joe : people) {
            joe.setName("New Joe");
            container.store(joe);
        }
    } finally {
        container.close();
    }
}
UnitsOfWork.java: An object container for the background task

And another background container for the list task.

private void listAllPeople(ObjectContainer rootContainer) {
    ObjectContainer container = rootContainer.ext().openSession();
    try {
        for (Person person : container.query(Person.class)) {
            System.out.println(person.getName());
        }
    } finally {
        container.close();
    }
}
UnitsOfWork.java: An object container for this unit of work

Be Aware of the Isolation Level

When using multiple object containers you need to be aware of the transaction isolation. db4o has read committed isolation properties. This isolation applies per object level. Object are loaded individually, which means that the different object-states may are from different committed states.

Here's an example to demonstrate the isolation level issues. We have two bank accounts. One transaction lists the two bank accounts and sums up the total.

long moneyInOurAccounts = 0;
List<BankAccount> bankAccounts = container.query(BankAccount.class);
for (BankAccount account : bankAccounts) {
    System.out.println("This account has "+account.money());
    moneyInOurAccounts +=account.money();
    moveMoneyTransactionFinishes();
}
// We get the wrong answer here
System.out.println("The money total is "+moneyInOurAccounts
        +". Expected is "+INITIAL_MONEY_ON_ONE_ACCOUNT*bankAccounts.size());
InconsistentStateRead.java: We list the bank accounts and sum up the money

During that operation another transaction finishes a money transfer from one account to another and commits.

List<BankAccount> bankAccounts = container.query(BankAccount.class);
final BankAccount debitAccount = bankAccounts.get(0);
final BankAccount creditAccount = bankAccounts.get(1);

int moneyToTransfer = 200;
creditAccount.withdraw(moneyToTransfer);
debitAccount.deposit(moneyToTransfer);

container.store(debitAccount);
container.store(creditAccount);
container.commit();
InconsistentStateRead.java: Meanwhile we transfer money.

Now the other transaction sees one bank account previous transfer, the other account is in the last committed state. Therefore it sees an inconsistent view across these two objects.