Skip to content

Db4oSessions

Fast ACID NoSQL Application Database edited this page Sep 23, 2019 · 5 revisions
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 + '\'' +
                    '}';
        }
    }
}

Session Containers

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.

Transactions And Isolation

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.

Concurrency and db4o

How should you deal with concurrent access to a db4o database? This chapter gives an overview and guidelines for dealing with that.

Do Not Share an Object Containers Across Threads

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.

Use an Object Containers per Unit of Work

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.

Sharing an Object Container

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

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.