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);
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(); } }
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(); } }
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());
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();
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.