Home

Awesome

LMDB JNI

Build Status Coverage Status <a href="https://scan.coverity.com/projects/4017"> <img alt="Coverity Scan Build Status" src="https://scan.coverity.com/projects/4017/badge.svg"/> </a>

LMDB JNI provide a Java API to LMDB which is an ultra-fast, ultra-compact key-value embedded data store developed by Symas for the OpenLDAP Project. It uses memory-mapped files, so it has the read performance of a pure in-memory database while still offering the persistence of standard disk-based databases. Transactional with full ACID semantics and crash-proof by design. No corruption. No startup time. Zero-config cache tuning. No dependencies. Proven in production applications.

LMDB JNI is available for 64 bit Linux, OSX, Windows and Android.

Documentation

Presentations

Benchmarks

Maven

Windows, Android and OSX support has been discontinued in lmdbjni 0.4.7 (LMDB 0.9.19) and onward. Users can still build releases at their own convenience, but no artifacts will be published to Maven Central.

Please refer to lmdbjava.

<!-- required java classes -->

<dependency>
  <groupId>org.deephacks.lmdbjni</groupId>
  <artifactId>lmdbjni</artifactId>
  <version>${lmdbjni.version}</version>
</dependency>

<!-- prebuilt liblmdb platform packages -->

<dependency>
  <groupId>org.deephacks.lmdbjni</groupId>
  <artifactId>lmdbjni-linux64</artifactId>
  <version>${lmdbjni.version}</version>
</dependency>

<dependency>
  <groupId>org.deephacks.lmdbjni</groupId>
  <artifactId>lmdbjni-osx64</artifactId>
  <version>${lmdbjni.version}</version>
</dependency>

<dependency>
  <groupId>org.deephacks.lmdbjni</groupId>
  <artifactId>lmdbjni-win64</artifactId>
  <version>${lmdbjni.version}</version>
</dependency>

<!-- Android 5.0 (API level 21) 64-bit ARM -->
<dependency>
  <groupId>org.deephacks.lmdbjni</groupId>
  <artifactId>lmdbjni-android</artifactId>
  <version>${lmdbjni.version}</version>
</dependency>

Usage

Recommended package imports.

 import org.fusesource.lmdbjni.*;
 import static org.fusesource.lmdbjni.Constants.*;

Opening and closing the database.

 try (Env env = new Env("/tmp/mydb")) {
   try (Database db = env.openDatabase()) {
     ... // use the db
   }
 }

Putting, getting, and deleting key/values.

 db.put(bytes("Tampa"), bytes("rocks"));
 String value = string(db.get(bytes("Tampa")));
 db.delete(bytes("Tampa"));

Iterating and seeking key/values forward and backward.

Transaction tx = env.createReadTransaction();
try (EntryIterator it = db.iterate(tx)) {
  for (Entry next : it.iterable()) {
  }
}

try (EntryIterator it = db.iterateBackward(tx)) {
  for (Entry next : it.iterable()) {
  }
}

byte[] key = bytes("London");
try (EntryIterator it = db.seek(tx, key)) {
  for (Entry next : it.iterable()) {
  }
}

try (EntryIterator it = db.seekBackward(tx, key))) {
  for (Entry next : it.iterable()) {
  }
}
tx.abort();

Performing transactional updates.

 try (Transaction tx = env.createWriteTransaction()) {
   db.delete(tx, bytes("Denver"));
   db.put(tx, bytes("Tampa"), bytes("green"));
   db.put(tx, bytes("London"), bytes("red"));
   tx.commit();  // if commit is not called, the transaction is aborted
 }

Working against a snapshot view of the database using cursors.

 // create a read-only transaction...
 try (Transaction tx = env.createReadTransaction()) {
   
   // All read operations will now use the same 
   // consistent view of the data.
   ... = db.openCursor(tx);
   ... = db.get(tx, bytes("Tampa"));
 }

A cursor in a write-transaction can be closed before its transaction ends, and will otherwise be closed when its transaction ends. A cursor must not be used after its transaction is closed. Both these try blocks are unsafe and may SIGSEGV.

 try (Transaction tx = env.createWriteTransaction();
      Cursor cursor = db.openCursor(tx)) {
   ...
   tx.commit();
 }

 try (Transaction tx = env.createWriteTransaction();
      EntryIterator it = db.iterate(tx)) {
   ...
   tx.commit();
 }

A cursor in a read-only transaction must be closed explicitly, before or after its transaction ends. Both these try blocks are safe.

 try (Transaction tx = env.createReadTransaction();
      Cursor cursor = db.openCursor(tx)) {
 }

 try (Transaction tx = env.createReadTransaction();
      EntryIterator it = db.iterate(tx)) {
 }

Set a custom key comparison function for a database.

 db.setComparator(tx, new Comparator<byte[]>() {
      @Override
      public int compare(byte[] key1, byte[] key2) {
        // do compare
      }
    });

Atomic hot backup.

 env.copy(backupPath);

Using a memory pool to make native memory allocations more efficient:

 Env.pushMemoryPool(1024 * 512);
 try {
     // .. work with the DB in here, 
 } finally {
     Env.popMemoryPool();
 }

Zero copy usage

The safest (and least efficient) approach for interacting with LMDB JNI is using buffer copy as shown above. BufferCursor is a more efficient, zero copy mode. This mode is not available on Android.

There are also methods that give access to DirectBuffer, but users should avoid interacting directly with these and use the BufferCursor API instead. Otherwise take extra care of buffer memory address+size and byte ordering. Mistakes may lead to SIGSEGV or unpredictable key ordering etc.

 // read only
 try (Transaction tx = env.createReadTransaction(); 
      BufferCursor cursor = db.bufferCursor(tx)) {
   // iterate from first item and forwards
   if (cursor.first()) {
     do {
       // read a position in buffer
       cursor.keyByte(0);
       cursor.valByte(0);
     } while (cursor.next());
   }

   // iterate from last item and backwards
   if (cursor.last()) {
     do {
       // copy entire buffer
       cursor.keyBytes();
       cursor.valBytes();
     } while (cursor.prev());
   }
   
   // find entry matching exactly the provided key
   cursor.keyWriteBytes(bytes("Paris"));
   if (cursor.seekKey()) {
     // read utf-8 string from position until NULL byte
     cursor.valUtf8(0);
   }

   // find first key greater than or equal to specified key.
   cursor.keyWriteBytes(bytes("London"));
   if (cursor.seekRange()) {
     // read utf-8 string from position until NULL byte
     cursor.keyUtf8(0);
     cursor.valUtf8(0);
   }
 }
 
 // open for write
 try (Transaction tx = env.createWriteTransaction()) {
   // cursors must close before write transactions!
   try (BufferCursor cursor = db.bufferCursor(tx)) {
     if (cursor.first()) {
       // write utf-8 ending with NULL byte
       cursor.keyWriteUtf8("England");
       cursor.valWriteUtf8("London");
       // overwrite existing item if any. Data is not written
       // into database before this operation is called and
       // no updates are visible outside this transaction until
       // the transaction is committed
       cursor.overwrite();
       cursor.first();
       // delete current cursor position
       cursor.delete();
     }
   }
   // commit changes or try-with-resources will auto-abort
   tx.commit();
 } 

License

This project is licensed under the Apache License, Version 2.0 but the binary jar it produces also includes liblmdb library of the OpenLDAP project which is licensed under the The OpenLDAP Public License.