-
Notifications
You must be signed in to change notification settings - Fork 8
Db4oSessions
package com.db4odoc.container.sessions;
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectServer;
import com.db4o.ObjectSet;
import com.db4o.cs.Db4oClientServer;
import java.io.File;
public class Db4oSessions {
private static final String DATABASE_FILE_NAME = "database.db4o";
public static void main(String[] args) {
sessions();
sessionsIsolation();
sessionCache();
embeddedClient();
}
public static void sessions() {
cleanUp();
// #example: Session object container
ObjectContainer rootContainer = Db4oEmbedded.openFile(DATABASE_FILE_NAME);
// open the db4o-session. For example at the beginning for a web-request
ObjectContainer session = rootContainer.ext().openSession();
try {
// do the operations on the session-container
session.store(new Person("Joe"));
} finally {
// close the container. For example when the request ends
session.close();
}
// #end example
rootContainer.close();
}
private static void sessionsIsolation() {
cleanUp();
ObjectContainer rootContainer = Db4oEmbedded.openFile(DATABASE_FILE_NAME);
ObjectContainer session1 = rootContainer.ext().openSession();
ObjectContainer session2 = rootContainer.ext().openSession();
try {
// #example: Session are isolated from each other
session1.store(new Person("Joe"));
session1.store(new Person("Joanna"));
// the second session won't see the changes until the changes are committed
printAll(session2.query(Person.class));
session1.commit();
// new the changes are visiable for the second session
printAll(session2.query(Person.class));
// #end example
} finally {
// close the container. For example when the request ends
session1.close();
session2.close();
}
rootContainer.close();
}
private static void sessionCache() {
cleanUp();
ObjectContainer rootContainer = Db4oEmbedded.openFile(DATABASE_FILE_NAME);
ObjectContainer session1 = rootContainer.ext().openSession();
ObjectContainer session2 = rootContainer.ext().openSession();
try {
storeAPerson(session1);
// #example: Each session does cache the objects
Person personOnSession1 = session1.query(Person.class).get(0);
Person personOnSession2 = session2.query(Person.class).get(0);
personOnSession1.setName("NewName");
session1.store(personOnSession1);
session1.commit();
// the second session still sees the old value, because it was cached
System.out.println(personOnSession2.getName());
// you can explicitly refresh it
session2.ext().refresh(personOnSession2, Integer.MAX_VALUE);
System.out.println(personOnSession2.getName());
// #end example
} finally {
// close the container. For example when the request ends
session1.close();
session2.close();
}
rootContainer.close();
}
private static void storeAPerson(ObjectContainer session1) {
session1.store(new Person("Joe"));
session1.commit();
}
public static void embeddedClient() {
cleanUp();
// #example: Embedded client
ObjectServer server = Db4oClientServer.openServer(DATABASE_FILE_NAME, 0);
// open the db4o-embedded client. For example at the beginning for a web-request
ObjectContainer container = server.openClient();
try {
// do the operations on the session-container
container.store(new Person("Joe"));
} finally {
// close the container. For example when the request ends
container.close();
}
// #end example
server.close();
}
private static void printAll(ObjectSet<Person> persons) {
for (Person person : persons) {
System.out.println(person);
}
}
private static void cleanUp() {
new File(DATABASE_FILE_NAME).delete();
}
private static class Person {
private String name;
private Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
}
In an application there are often multiple operations running at the same time. A typical example is a web application which processes multiple requests at the same time. These operations should be isolated from each other. This means that for the database we want to have multiple transactions at the same time. Each transaction does some work and other transactions shouldn't interfere .
db4o supports this scenario with session containers. A session container is a lightweight object-container with its own transaction and reference cache, but shares the resources with its parent container. That means you can commit and rollback changes on such a session container without disturbing the work of other session containers. If you want to implement units of work, you might considers using a session container for each unit. You can create such a container with the open session call.
As previously mentioned session-containers are isolated from each other. Each session container has its own transaction and its own reference system. This isolation ensures that the different session container don't interfere witch each other.
They don't share the objects loaded and stored with each other. That means you need to load and store the a object with the same session container. When you try to load a object form one session-container and store it with another, you well end up with two separate copies of that object.
Since the transactions are isolated, changes are only visible for other session containers when you've committed. Before the commit call the changes are not visible to other session containers.
Note also that sessions also have their own reference cache. So when a object is already loaded, it wont be refreshed if another transaction updates the object. You explicitly need to refresh it.
How should you deal with concurrent access to a db4o database? This chapter gives an overview and guidelines for dealing with that.
There are some basic rules which you should never break, otherwise strange effects due to race-condition can happen:
Never share an object-container instance across threads, nor share the data-objects across threads. That will almost certainly create race-conditions. The reason is that when you change objects, while other threads read them, you will get inconsistent views on the state of your data model.
Now how do you deal with concurrent operations? Well you need to use some kind of synchronization strategy.
You can avoid synchronization when you are using multiple object containers for different units of work. However you need to be aware to the isolation level db4o guarantees. See "Object Container per Unit of Work"
This is often used in web applications, where you have an object container per request.
In a desktop/mobile application you usually want to have one consistent view on your data model. Therefore it makes sense to use the same object container in the whole application. That ensures that we always get the same objects throughout the whole application. As long as you don't load of work to different threads, everything is fine.
However as soon as you start to do manipulations on the data model in different threads you need to synchronize these operations. See "Share a Object Container Across Threads"
Isolation imposes rules which ensure that transactions do not interfere with each other even if they are executed at the same time. Read more about isolation levels on Wikipedia.
db4o uses the read committed isolation level, on an object level. That's means db4o has a very weak isolation. It ensures that you do not see uncommitted objects of other transactions. However it does not guarantee any consistency across different objects.
most traditional SQL database used Read-Commited as Default implementation, and use Optimistic-Read with version field, Cache-Result or Big-Lock to achieve other isolation levels.