db4o (database for objects) ist eine Objektdatenbank für die Java-Plattform und das .Net-Framework, deren Weiterentwicklung im Jahr 2011 eingestellt wurde. Der Quellcode ist noch auf Github verfügbar.[1] Sie gehört zu den NoSQL-Datenbanken. Die db4o-Programmbibliothek zeichnet sich durch einen vergleichsweise geringen Speicherbedarf von etwa 600 kB aus. Dadurch eignet sie sich besonders für die Einbettung in andere Programme und für Umgebungen mit beschränkten Speicher-Ressourcen, wie beispielsweise PDAs. Db4o konnte entweder unter den Bedingungen der GPL oder mit kommerziellen Lizenzen verwendet werden.

Db4o

Db4o-Logo
Basisdaten

Entwickler Versant Corporation
Aktuelle Version 8.0
(1. Februar 2011)
Betriebssystem Unix-Derivate, Linux, Windows
Programmier­sprache C#, Java
Kategorie Datenbanksystem, Server
Lizenz Duales Lizenzsystem (Proprietär und GPL)
deutschsprachig nein
www.db4o.com im Web Archiv

db4o unterstützte Javas JDK 1.1.x bis 6.0 und lief nativ auf Jakarta EE, J2SE und allen J2ME-Konfigurationen, deren J2ME-Profil die Reflection-API bereitstellt. Daneben unterstützte db4o alle Sprachen, die auf der virtuellen Maschine CLR der .NET-Plattform aufsetzen, wie beispielsweise C# oder VB.NET. Der für die .NET-Plattform notwendige C#-Quellcode der db4o-Programmbibliothek wurde dabei größtenteils aus dem Java-Quellcode generiert und ist auch auf der Mono-Implementierung der .NET-Plattform lauffähig.

Historie

Bearbeiten

Das db4o-Projekt begann im Jahr 2000 unter Führung von Carl Rosenberger. Entwickelt wurde db4o von der Firma db4objects, Inc., die 2004 mit Hilfe von Investoren wie Mark Leslie (VERITAS Software), Vinod Khosla (Sun Microsystems) und Jerry Fiddler (Wind River) gegründet wurde.

Im Dezember 2008 wurde die Datenbanksparte an Versant verkauft.[2]

db4o wird derzeit nicht mehr weiterentwickelt. Die Webseite www.db4o.com ist nicht mehr erreichbar, und die letzte Änderung bei Sourceforge war 2015, weiterhin gehört Versant seit Dezember 2012 zu Actian.[3]

db4o wurde seit 2004 wahlweise unter der GPL als freie Software lizenziert oder unter einer kommerziellen Lizenz, die die Verwendung in nicht-GPL-Projekten erlaubt. Daneben gab es eine Classpath-ähnliche Lizenz, die es erlaubt, db4o in Open-Source-Projekte zu integrieren, die nicht db4o / GPL konform sind.

Systemeigenschaften und Objektmanagement

Bearbeiten
  • Neben dem kleinen Speicherbedarf für die Programmbibliothek ist der Bedarf an Arbeitsspeicher zur Laufzeit ebenfalls gering und überschreitet normalerweise 1 MB nicht.
  • db4o kann nicht von außen administriert werden. Sämtliche Einstellungen müssen über den Programmcode vorgenommen werden.
  • Die Datenbank kann in gemischten Umgebungen eingesetzt werden: Beispielsweise können zwei in C# oder Java programmierte Client-Anwendungen gemeinsam auf eine Datenbank zugreifen, die von einer in Java oder C# programmierten Server-Anwendung betrieben wird. Mit Hilfe des Alias-Features von db4o können dabei Klassennamen umbenannt werden, was beispielsweise wegen der unterschiedlichen Benennungskonventionen notwendig ist.
  • Eine der wichtigsten Vorteile von db4o: Objekte können gespeichert werden, ohne dass hierfür besondere Vorkehrungen getroffen werden müssen. Es ist weder notwendig, die zu speichernden Klassen von speziellen Oberklassen abzuleiten, noch müssen spezielle Schnittstellen implementiert werden. Auch eine Anreicherung des Bytecodes, wie sie beispielsweise die Aspektorientierte Programmierung verwendet, ist nicht nötig. Die Objekte können dabei beliebige Typen enthalten und zudem beliebig tief verschachtelt sein.
  • Mit dieser Eigenschaft ist es möglich, Persistenz mit wenig Aufwand umzusetzen.

Grundlegende API

Bearbeiten

API (application programming interface) bezeichnet die Programmierschnittstelle, mit der die Datenbank programmiert wird. Dabei zeigt sich, dass der Unterschied zu anderen Programmen wie Hibernate oder JPA / EJB nicht allzu groß ist.

Ein einfaches Codebeispiel, das zusammenhängend ausgeführt werden sollte:

   // Öffne eine Datenbank
   ObjectContainer db = Db4o.openFile("C:/beispiel.db");
   try {
      // Speichere zwei Personen
      db.store(new Person("John Doe","TOP INC",45));
      db.store(new Person("Clara Himmel","BB Radio",30));
      // Iteriere über alle Personen
      ObjectSet result = db.queryByExample(new Person());
      while (result.hasNext()) {
          System.out.println(result.next());
      }
      // Verändere eine Person
      result = db.queryByExample(new Person("Clara Himmel"));
      Person found = (Person) result.next();
      found.setAge(25);
      db.store(found);
      // Lösche eine Person
      db.delete(found);
      // Schreibe die Änderungen fest
      db.commit();
   }
   finally {
      // Schließe die Datenbank
      db.close();
   }
  • Ähnlich zu EJB / JPA (dort EntityManager) wird zuerst ein ObjectContainer als Objektmanager erstellt. Dieser Container kann entweder auf eine Datei verweisen oder in einer Client-Server-Architektur auf eine von einem Server bereitgestellte Datenbank. Im ersten Fall werden alle Daten in einer Datei gespeichert.
    • Nachteil: Die Daten sind also nicht komplett unabhängig von der Klasse der Programmiersprache. Das Format ist proprietär und kann gegebenenfalls von nachfolgenden Programmiersprachen nicht mehr so einfach gelesen werden.
    • Vorteil: Diese kann sehr leicht zwischen Firmen ausgetauscht werden. Der Datenzugriff ist sehr schnell.

Danach werden alle CRUD-Operationen (Create, Read, Update und Delete) ausgeführt:

CREATE
Mit db.store(object) wird ein beliebiges Objekt gespeichert. Dies ist analog zum JPA-Standard, wo em.persist(object) aufgerufen wird. Allerdings muss in letzterem Fall das Domain-Objekt mindestens mit @Entity annotiert oder in XML registriert worden sein.
READ
Es folgt eine einfache Suche mit der einfachsten Abfragesprache Query by Example. Eine Personen-Instanz wird als Muster erstellt und beliebige Felder werden gesetzt. Zurückgegeben werden alle Objekte in der Datenbank, bei denen die Felder passen. found enthält daher das vollständige Objekt, das oben gespeichert wurde.
UPDATE
Dieses found-Objekt wird jetzt intern referenziert und kann daher verändert .setAge(25)und nochmals gespeichert werden. Dann ist es aktualisiert. Im JPA Standard entspricht dieses einem em.merge(object), wobei das Objekt immer attached, also von der Session referenziert (gemerkt) werden muss.
DELETE
db.delete(object) löscht ein Objekt, das dem Objektmanager bekannt sein muss.

Abfragesprachen

Bearbeiten

db4o bietet drei Abfragesprachen, die alle nicht allzu weit von dem entfernt sind, was Hibernate und JPA in EJB 3.0 bieten:

Query By Example (QBE)

Bearbeiten

Query by Example steht für eine Suche anhand von Beispielen. Die Grundlagen hierfür wurden bereits in den 1970er Jahren von Moshé M. Zloof in einigen Artikeln gelegt. Hibernate und db4o bieten diesen Mechanismus in einer leicht abgeschwächten Version an.

Bei db4o kann, wie oben gezeigt, ein beliebiges Objekt erstellt werden. Danach wird entweder:

  • Kein Feld gesetzt, so dass alle Felder null sind, oder es wird direkt eine Referenz auf die Klasse übergeben (z. B. Person.class): In diesem Fall werden alle in der Datenbank gespeicherten Instanzen der Klasse zurückgeliefert.
  • Es werden beliebig viele Felder gesetzt: Dann werden alle gespeicherten Objekte zurückgeliefert, bei denen die im Muster gefüllten Felder mit dem Wert im Objekt übereinstimmen.
  • Es wird null übergeben: Dieses liefert alle Objekte aus der Datenbank zurück.

Komplexe Abfragekriterien lassen sich bei Verwendung dieser Art der Abfrage nicht ausdrücken.

SODA / Criteria Queries

Bearbeiten

Intern werden alle Abfragen bei db4o auf SODA abgebildet. SODA ist die Programmierschnittstelle des auf SourceForge gehosteten Projekts S.O.D.A. – Simple Object Database Access, das teilweise von den Entwicklern von db4o betreut, aber unabhängig von db4o entwickelt wird.

Die SODA-Abfragen ähneln denen von Hibernate-Criteria-Abfragen stark. Ein Beispiel:

  Query query=db.query();
  query.constrain(Person.class);
  query.descend("alter").constrain(50).greater();
  ObjectSet result=query.execute();

Hier wird die Abfrage zunächst auf die Klasse Person eingeschränkt, danach weiter auf alle Personen, die älter als 50 sind.

Es handelt sich bei dieser Abfrageart um ein Beispiel für Query by API, bei der Bäume von Objekten erstellt werden, mit denen Abfragen eingeschränkt werden. Sie ist daher mächtiger als die anderen Abfragemechanismen, allerdings auch schwerer zu erstellen, da der Code schwerer zu lesen ist. Durch die Verwendung von Zeichenketten als Abfrageparameter wird die Typsicherheit zum Kompilationszeitpunkt nicht gewährleistet.

Native Abfragen

Bearbeiten

Native Abfragen (engl. native queries) sind Abfragen, die in der Programmiersprache der Client-Anwendung formuliert sind, also bei db4o beispielsweise in Java oder C#.

Der Code von nativen Abfragen wird bei db4o normalerweise nicht ausgeführt, sondern zur Laufzeit analysiert. Basierend auf dem Ergebnis der Analyse wird eine SODA-Criteria-Query erstellt. Dieses hat den Vorteil, dass die persistierten Objekte nicht zeitaufwändig instanziiert werden müssen. Nur wenn die Analyse aufgrund der Komplexität der Abfrage fehlschlägt, wird die Objektinstanz erzeugt und der Programmcode der nativen Abfrage muss ausgeführt werden.

Zum Vergleich von nativen Abfragen und JPQL oder HQL sei hier auf Microsofts LINQ verwiesen. Microsoft konnte die Sprache verändern und so mächtige Abfragekonstrukte direkt in C# einbetten.

Vorteile

Bearbeiten
  • Der Programmierer muss keine spezielle Abfragesprache, wie z. B. SQL, lernen.
  • Die Abfragen verwenden die Elemente der Programmiersprache und sind daher typsicher.
  • Bei der Umgestaltung (Refactoring) des Programms können die Abfragen berücksichtigt werden.

Fehlerhafte Abfragebefehle werden aus diesen Gründen frühzeitiger erkannt oder durch die Vermeidung häufiger Ursachen eher vermieden, als dieses bei Verwendung spezieller Abfragensprachen der Fall ist.

Nachteile

Bearbeiten
  • Die Erstellung der Abfrage ist komplett in der Hand der Entwickler und nicht unter Kontrolle von Administratoren, die beispielsweise bei Verwendung von Stored Procedures Sicherheitsaspekte der Datenbank angemessen berücksichtigen können.
  • Native Abfragen sind nicht so mächtig wie entsprechende Konstrukte in speziellen Abfragesprachen wie HQL oder JPQL. In diesen Sprachen können besondere Abfragekriterien, wie beispielsweise Verdichtung, explizit ausgedrückt werden.

Beispiele

Bearbeiten

Entscheidend für den komfortablen Einsatz ist, ob und wie die verwendete Sprache Closures unterstützt. Ohne Closure-Unterstützung muss mit inneren Klassen oder ungebundenen Methodenzeigern beziehungsweise Delegaten gearbeitet werden, um die Abfrage als „freischwebender Code“ einer Methode zu übergeben.

C# .NET 3.0 (Verwendung eines Lambda-Ausdrucks);

  var persons = db.Query<Person>( s => s.Age > 50 );

C# .NET 2.0 (Verwendung eines ungebundenen Methodenzeigers):

  IList<Person> persons = db.Query<Person>( delegate(Person person){
    return person.Age > 50;
  });

Java JDK 5 (Verwendung einer generischen anonymen Klasse):

  List<Person> personen = db.query(new Predicate<Person>() {
    public boolean match(Person person) {
      return person.getAge() > 50;
    }
  });

Java JDK 1.2 bis 1.4 (Verwendung einer anonymen Klasse):

  List personen = db.query(new Predicate() {
    public boolean match(Object object) {
      Person person = (Person)object;
      return person.getAge() > 50;
    }
  });

Java JDK 1.1 (Verwendung einer inneren Klasse):

  ObjectSet personen = db.query(new PersonFilter());

  public static class PersonFilter extends Predicate {
    public boolean match(Object object) {
      Person person = (Person)object;
      return person.getAge() > 50;
    }
  }

Transaktionen

Bearbeiten

Mit dem Öffnen des Containers aus obigem Beispiel wird eine Transaktion erstellt. Diese kann jederzeit mit db.commit() oder db.rollback() auf die Datenbank geschrieben oder verworfen werden. db4o garantiert dabei ein ACID-Verhalten. Alles innerhalb einer Transaktion entspricht den ACID-Anforderungen. Intern werden dabei alle Zugriffe auf die Datenbank innerhalb der Transaktion serialisiert. Dies hat starke Konsequenzen für das Datenbanksystem:

  • Durch das proprietäre Datenformat ist der Zugriff für einen Thread zwar meistens schneller als andere Kombinationen aus relationaler Datenbank und objektrelationalem Mapping.
  • Dennoch skaliert ein „multithreaded“ Zugriff bei db4o noch nicht, wie er beispielsweise für den Bau von großen Web-Portalen nötig ist.

Erfahrungen zufolge sind normalerweise bis zu einem Dutzend Zugriffe pro Sekunde realisierbar, was für viele Anwendungsfälle ausreichen kann. db4o arbeitet jedoch wegen der obigen Punkte an einer Serverversion, die auch bei nebenläufigen Zugriffen skaliert.

Client- / Server-Modes

Bearbeiten

db4o bietet mehrere Arbeitsmodi an:

  1. Embedded Mode: Es wird im Anwendungscode ein Objektcontainer geöffnet, wie in obigem Beispiel gezeigt. Basis ist hier ein dateibasierter Zugriff.
  2. Client / Server Mode: db4o kann als Server gestartet werden und dadurch von Clients Anfragen entgegennehmen.

Beispiel für den Server:

ObjectServer server = Db4oClientServer.openServer("c:/liveserver.db", 8732);
server.grantAccess("user1", "password");
server.grantAccess("user2", "password");

Hier wird ein Thread geöffnet, der auf Clientanfragen auf dem Port 8732 wartet. Der Server selbst kann über Monitore warten und beispielsweise Statusausgaben machen. Zugriffsrechte können nicht direkt von außen mittels eines Tools verwaltet werden. Entweder werden diese im Code angelegt oder per Konsole etc. eingelesen.

Ein Client könnte initial so eingerichtet werden:

aClient = Db4oClientServer.openClient("196.42.103.142", 8732, "user2", "password");

Mittels eines sogenannten „Out-Of-Band Signalling“-Kommunikationsprotokolls können dem Server beliebige Objekte gesendet werden, mit denen ein Server administriert werden kann. Beispielsweise kann die Aufforderung zum Defragmentieren der Datei übermittelt werden.

Replikation

Bearbeiten

Mittels weniger Zeilen können beliebig viele Server repliziert (geclustert) werden. Dies geschieht mit dem db4o-dRS-Modul.

Die Objekte bekommen dazu eine eindeutige UUID. Dadurch können einzelne Objekte oder die gesamte Datenbank uni- oder bidirektional verteilt werden, d. h., jedes neue Objekt wird an alle anderen Datenbanken gesendet. Dies erlaubt es große und redundante Architekturen aufzusetzen.

Sehr interessant ist ebenfalls die Option, eine db4o-Datenbank in relationale Datenbanken zu replizieren. Dies bedeutet eine Brücke / Verbindung zwischen beiden Welten, die oft als getrennt angesehen werden. db4o verwendet dazu Hibernate. Nach der Konfiguration der dRS-Bridge können ebenfalls einzelne Objekte oder ganze Datenbanken per Trigger wahlweise in eine oder beide Richtungen transferiert werden. In vielen Fällen können in der Industrie so die Vorteile beider Welten gut ausgenutzt werden.

Ladeverhalten

Bearbeiten

Bei db4o gibt es drei Modi für das Laden von Objekten:

IMMEDIATE
Hier werden bei einer Abfrage alle gesuchten Objekte unmittelbar ermittelt. Bei großen Ergebnismengen kann dies viel Zeit und Arbeitsspeicher in Anspruch nehmen.
LAZY
Hier wird sofort ein ObjectSet zurückgeliefert, jedoch ist noch kein Objekt ermittelt worden. Ein Thread sucht alle weiteren Ergebnisse. Dieses Verhalten ist ideal, wenn die ersten Ergebnisse schnell angezeigt werden sollen. Bei diesem Lademodus muss beachtet werden, dass die Abfrage nicht zu einem definierten Zeitpunkt ausgewertet wird. Hierdurch kann es zu Nebeneffekten kommen, wenn zwischen dem Absetzen und dem vollständigen Auswerten der Anfrage Änderungen am Datenbestand vorgenommen werden.
SNAPSHOT
In diesem Zwischenmodus wird die Auswertung aller indizierten Bestandteile der Abfrage zu einem definierten Zeitpunkt vorgenommen. Anschließend wird der Zustand der möglichen Ergebnismenge in einem Snapshot aufgezeichnet. Die weitere Verarbeitung der nichtindizierten Abfragebestandteile findet in einem nebenläufigen Thread statt. Hierdurch werden die Vorteile der verzögerter Auswertung unter Vermeidung ihrer Nebeneffekte bereitgestellt. Für den erzeugten Snapshot wird jedoch Arbeitsspeicher verwendet, der erst mit Freigabe des ObjectSet freigegeben werden kann.

Objekte bilden normalerweise tiefe Referenzstrukturen. In db4o kann beim Laden von Objekten angegeben werden, bis zu welcher Tiefe referenzierte Objekte implizit mit dem Objekt geladen werden sollen. Zusätzlich besteht die Möglichkeit, diese referenzierten Objekte explizit zu einem späteren Zeitpunkt zu aktivieren. Nähere Informationen zu Aktivierung, Deaktivierung, Abfrage und Aktualisierung referenzierter Objekte können in der db4o-Referenz gefunden werden.

Callbacks

Bearbeiten

Bei db4o gibt es interne und externe Rückruffunktionen (callbacks). Diese ähneln in ihrer Bedeutung den Datenbanktriggern anderer Datenbanken. Rückrufmechanismen gibt es in vielen Produkten zur Datenbankanbindung. Sie sind ein mächtiges Werkzeug um beispielsweise Überprüfungen durchzuführen, Standardwerte zu setzen oder Sicherheitsaspekte zu berücksichtigen.

Interne Callbacks

Bearbeiten

Interne Callbacks werden den Klassen der zu speichernden Objekte als Methoden hinzugefügt. Diese werden von db4o dynamisch unter Verwendung der Reflexion erkannt und aufgerufen. Hierzu ist es nicht notwendig, die Klassen von speziellen Schnittstellen abzuleiten. Vergleichbar mit den Join-Points der aspektorientierten Programmierung sind Aufrufe interner Callbacks vor und nach datenbankbezogenen Ereignissen möglich. Der Aufrufzeitpunkt wird hierbei durch das Präfix des Methodennamens festgelegt: objectCan...–Methoden werden vor und objectOn...–Methoden werden nach dem Ereignis aufgerufen. Durch den Rückgabewert der objectCan...–Methoden kann gesteuert werden, ob die mit dem Ereignis verbundene Aktion stattfinden soll. Für die internen Callbacks stehen folgende Ereignisse zur Verfügung: New, Update, Delete, Activate und Deactivate.

Externe Callbacks

Bearbeiten

Externe Callbacks sind keinen Klassen zugeordnet. Stattdessen werden sie beim ObjectContainer registriert. Zusätzlich zu den bei den internen Callbacks möglichen Ereignissen stehen für diese Callbacks QueryStarted und QueryFinished zur Verfügung. Die Verwendung externer Callback ermöglicht eine Ereignisbehandlung ohne Abänderung der zu speichernden Klassen. Dieses kann den Vorteil haben, dass Aspekte der Persistenz nicht mit der Geschäftslogik vermischt werden müssen. Wenn der Quelltext der zu speichernden Klassen nicht zur Verfügung steht, muss mit externen Callbacks gearbeitet werden.

Literatur

Bearbeiten
  • Jim Paterson, Stefan Edlich, Henrik Hörning, Reidar Hörning: The Definitive Guide to db4o Apress Inc., Berkeley CA 2006, ISBN 978-1-59059-656-2.
  • Patrick Römer, Larysa Visengeriyeva: db4o. schnell + kompakt. Entwickler.press, Frankfurt 2006, ISBN 978-3-939084-03-7.
  • William Cook, Carl Rosenberger: Native Queries for Persistent Objects. In Dr. Dobb’s Journal. Februar 2006 (online; englisch; 132 kB).
  • William Cook, Siddhartha Rai: Safe Query Objects. In Proceedings of the International Conference on Software Engineering (ICSE). 2005. S. 97–106 (online; englisch; PDF; 143 kB).
Bearbeiten

Einzelnachweise

Bearbeiten
  1. lytico: db4o (database for objects). 3. Januar 2018, abgerufen am 14. Februar 2018.
  2. Alexander Neumann: db4objects verkauft sein Datenbankengeschäft an Versant. In: Heise Newsticker. Heise Verlag, abgerufen am 17. Dezember 2008 (deutsch).
  3. Actian Corporation Completes Acquisition of Versant. Abgerufen am 15. Februar 2018 (englisch).