This article will show how to use BDB JE (BerkeleyDB Java Edition) on VMS.
BDB JE is an excellent NoSQL Key Value Store database for JVM languages.
BDB JE 7.5 should run on VMS Itanium with Java 8. BDB JE 5.0 runs on VMS Alpha with Java 5.
There is not actually any VMS specific Java code in this article. The VMS aspect is that it tries to relate BDB JE to VMS index-sequential files. And there are VMS scripts to build and run the code.
Many of the examples will be borrowed from the BDB JE section in my NoSQL KVS article.
Besides Java examples there will also be a few Jython (JVM Python) examples to show that BDB JE works fine for other JVM languages as well.
Let us assume that we have an older VMS application written in native language (Pascal/Cobol/Basic) using index-sequential files and it has been decided to migrate to a JVM language (Java/Kotlin/Groovy/Scala) to get to a newer language but wanting to stay on VMS.
For persistence there are a few options:
Relational database is the obvious choice. I have already written about that choice numerous times:
But maybe there are reasons for not to go that route and sticking to the Key Value Store / ISAM model.
Index-sequential files can be used from JVM languages. I have also written about that:
But there are some drawbacks from that approach:
So if one decide to go for another NoSQL Key Value Store database for Java, then BDB JE is a pretty obvious choice.
Be sure to check whether the license for the BDB JE version you use is compatible with your usage. Old versions are under AGPL. Newer versions are under Apache license. AGPL may be a problem for you.
BDB JE expose a low level API where both keys and values are just byte arrays. One can store a chunk of bytes (a BLOB) and later retrieve the same chunk of bytes (BLOB).
This is somewhat equivalent of the RMS API where one just pass address of data and length of data.
For an example see the C code here.
Note that this approach is easier in a native language than in Java as you in a native language can just take the address of the original data structure and pass while you in Java have to serialize an object and pass the result of that.
Example:
import java.io.Serializable;
public class Data implements Serializable {
private static final long serialVersionUID = 1L;
private int iv;
private double xv;
private String sv;
public Data() {
this(0, 0.0, "");
}
public Data(int iv, double xv, String sv) {
this.iv = iv;
this.xv = xv;
this.sv = sv;
}
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
@Override
public String toString() {
return String.format("{iv: %d, xv: %f, sv: %s}", iv, xv, sv);
}
}
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.OperationStatus;
public class Test {
private static DatabaseEntry prep(String key) throws UnsupportedEncodingException {
return new DatabaseEntry(key.getBytes("UTF-8"));
}
private static DatabaseEntry serialize(Data o) throws IOException {
ByteBuffer bb = ByteBuffer.allocate(1000);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(o.getIv());
bb.putDouble(o.getXv());
byte[] b = o.getSv().getBytes("UTF-8");
bb.putShort((short)b.length);
bb.put(b);
int n = bb.position();
byte[] res = new byte[n];
bb.rewind();
bb.get(res);
return new DatabaseEntry(res);
}
private static Data deserialize(DatabaseEntry b) throws IOException, ClassNotFoundException {
ByteBuffer bb = ByteBuffer.wrap(b.getData());
bb.order(ByteOrder.LITTLE_ENDIAN);
int iv = bb.getInt();
double xv = bb.getDouble();
int len = bb.getShort();
byte[] temp = new byte[len];
bb.get(temp);
String sv = new String(temp, "UTF-8");
Data res = new Data(iv, xv, sv);
return res;
}
private static void dump(Database db, String key) throws ClassNotFoundException, IOException {
System.out.printf("Key=%s\n", key);
DatabaseEntry rawvalue = new DatabaseEntry();
if(db.get(null, prep(key), rawvalue, null) == OperationStatus.SUCCESS) {
Data value = deserialize(rawvalue);
System.out.println(value);
} else {
System.out.println("Not found");
}
}
private static final int NREC = 1000;
public static void main(String[] args) throws IOException, ClassNotFoundException {
// open database
EnvironmentConfig envcfg = new EnvironmentConfig();
envcfg.setAllowCreateVoid(true);
Environment env = new Environment(new File("data/"), envcfg);
DatabaseConfig dbcfg = new DatabaseConfig();
dbcfg.setAllowCreate(true);
Database db = env.openDatabase(null, "testdb", dbcfg);
// put data
for(int i = 0; i < NREC; i++) {
String key = "Key#" + (1000 + i + 1);
Data value = new Data();
value.setIv(i + 1);
value.setXv(i + 1.0);
value.setSv(String.format("This is value %d", i + 1));
db.put(null, prep(key), serialize(value));
}
//
String key;
key = "Key#" + 1077;
// get
dump(db, key);
// delete
db.delete(null, prep(key));
// get non existing
dump(db, key);
//
key = "Key#" + 1088;
// update and get
dump(db, key);
DatabaseEntry rawvalue = new DatabaseEntry();
db.get(null, prep(key), rawvalue, null);
Data value = (Data)deserialize(rawvalue);
value.setIv(value.getIv() + 1);
value.setXv(value.getXv() + 0.1);
value.setSv(value.getSv() + " updated");
db.put(null, prep(key), serialize(value));
dump(db, key);
// list all
Cursor it = db.openCursor(null, new CursorConfig());
DatabaseEntry itrawkey = new DatabaseEntry();
DatabaseEntry itrawvalue = new DatabaseEntry();
OperationStatus itstat = it.getFirst(itrawkey, itrawvalue, null);
int n = 0;
while(itstat == OperationStatus.SUCCESS) {
String itkey = new String(itrawkey.getData(), "UTF-8");
if(!itkey.startsWith("Key#")) {
System.out.println("Unexpected key: " + itkey);
}
Data itvalue = (Data)deserialize(itrawvalue);
if(itvalue.getIv() < 1 || NREC < itvalue.getIv()) {
System.out.println("Unexpected value :" + itvalue);
}
n++;
itstat = it.getNext(itrawkey, itrawvalue, null);
}
it.close();
System.out.println(n);
// list keys where "Key#1075" <= key < "Key#1085"
Cursor it2 = db.openCursor(null, new CursorConfig());
DatabaseEntry it2rawkey = prep("Key#" + 1075);
DatabaseEntry it2rawvalue = new DatabaseEntry();
OperationStatus it2stat = it2.getSearchKey(it2rawkey, it2rawvalue, null);
int n2 = 0;
while(it2stat == OperationStatus.SUCCESS) {
String it2key = new String(it2rawkey.getData(), "UTF-8");
if(it2key.compareTo("Key#" + 1085) >= 0) break;
n2++;
it2stat = it2.getNext(it2rawkey, it2rawvalue, null);
}
it2.close();
System.out.println(n2);
// close database
db.close();
env.close();
}
}
Build and run:
$ javac -classpath /javalib/je-5_0_73.jar Test.java Data.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ java -classpath .:/javalib/je-5_0_73.jar "Test"
BDB JE also expose a high level API where one operates on classes/objects.
For the Java/C# people then that is a bit like ORM for RDBMS.
For the VMS people that is a bit like how some VMS languages has support for mapping records to VMS index-sequential files.
For an example of the latter see the Pascal code here.
Example:
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.PrimaryKey;
@Entity
public class DataO {
@PrimaryKey
private String key;
private int iv;
private double xv;
private String sv;
public DataO() {
this("", 0, 0.0, "");
}
public DataO(String key, int iv, double xv, String sv) {
this.key = key;
this.iv = iv;
this.xv = xv;
this.sv = sv;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
@Override
public String toString() {
return String.format("{iv: %d, xv: %f, sv: %s}", iv, xv, sv);
}
}
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.StoreConfig;
public class TestO {
private static void dump(PrimaryIndex<String, DataO> pk, String key) throws ClassNotFoundException, IOException {
System.out.printf("Key=%s\n", key);
if(pk.contains(key)) {
DataO o = pk.get(key);
System.out.println(o);
} else {
System.out.println("Not found");
}
}
private static final int NREC = 1000;
public static void main(String[] args) throws IOException, ClassNotFoundException {
// open database
EnvironmentConfig envcfg = new EnvironmentConfig();
envcfg.setAllowCreateVoid(true);
Environment env = new Environment(new File("data/"), envcfg);
StoreConfig stcfg = new StoreConfig();
stcfg.setAllowCreate(true);
EntityStore est = new EntityStore(env, "testdb", stcfg);
PrimaryIndex<String, DataO> pk = est.getPrimaryIndex(String.class, DataO.class);
// put data
for(int i = 0; i < NREC; i++) {
String key = "Key#" + (1000 + i + 1);
DataO o = new DataO();
o.setKey(key);
o.setIv(i + 1);
o.setXv(i + 1.0);
o.setSv(String.format("This is value %d", i + 1));
pk.put(o);
}
//
String key;
key = "Key#" + 1077;
// get
dump(pk, key);
// delete
pk.delete(key);
// get non existing
dump(pk, key);
//
key = "Key#" + 1088;
// update and get
dump(pk, key);
DataO o = pk.get(key);
o.setIv(o.getIv() + 1);
o.setXv(o.getXv() + 0.1);
o.setSv(o.getSv() + " updated");
pk.put(o);
dump(pk, key);
// list all
EntityCursor<DataO> c = pk.entities();
Iterator<DataO> it = c.iterator();
int n = 0;
while(it.hasNext()) {
DataO ito = it.next();
if(!ito.getKey().startsWith("Key#")) {
System.out.println("Unexpected key: " + ito.getKey());
}
if(ito.getIv() < 1 || NREC < ito.getIv()) {
System.out.println("Unexpected value :" + ito);
}
n++;
}
c.close();
System.out.println(n);
// list keys where "Key#1075" <= key < "Key#1085"
EntityCursor<DataO> c2 = pk.entities("Key#1075", true, "Key#1085", false);
Iterator<DataO> it2 = c2.iterator();
int n2 = 0;
while(it2.hasNext()) {
@SuppressWarnings("unused")
DataO ito2 = it2.next();
n2++;
}
c2.close();
System.out.println(n2);
// close database
est.close();
env.close();
}
}
Build and run:
$ javac -classpath /javalib/je-5_0_73.jar TestO.java DataO.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ java -classpath .:/javalib/je-5_0_73.jar "TestO"
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.PrimaryKey;
@Entity
public class DataO {
@PrimaryKey
private String key;
private int iv;
private double xv;
private String sv;
public DataO() {
this("", 0, 0.0, "");
}
public DataO(String key, int iv, double xv, String sv) {
this.key = key;
this.iv = iv;
this.xv = xv;
this.sv = sv;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
@Override
public String toString() {
return String.format("{iv: %d, xv: %f, sv: %s}", iv, xv, sv);
}
}
from java.lang import String
from java.io import File
from com.sleepycat.je import Environment
from com.sleepycat.je import EnvironmentConfig
from com.sleepycat.persist import EntityStore
from com.sleepycat.persist import PrimaryIndex
from com.sleepycat.persist import StoreConfig
import DataO
def dump(pk, key):
print('Key=%s' % (key))
if pk.contains(key):
o = pk.get(key)
print(o)
else:
print('Not found')
NREC = 1000
# open database
envcfg = EnvironmentConfig()
envcfg.setAllowCreateVoid(True)
env = Environment(File("data/"), envcfg)
stcfg = StoreConfig()
stcfg.setAllowCreate(True)
est = EntityStore(env, "testdb", stcfg)
pk = est.getPrimaryIndex(String, DataO);
# put data
for i in range(NREC):
o = DataO('Key#' + str(1000 + i + 1), i + 1, i + 1.0, 'This is value %d' % (i + 1))
pk.put(o)
#
key = "Key#" + str(1077)
# get
dump(pk, key)
# delete
pk.delete(key)
# get non existing
dump(pk, key)
#
key = "Key#" + str(1088)
# update and get
dump(pk, key)
o = pk.get(key)
o.iv = o.iv + 1
o.xv = o.xv + 0.1
o.sv = o.sv + ' updated'
pk.put(o)
dump(pk, key)
# list all
c = pk.entities()
n = 0
for o in c.iterator():
if not o.key.startswith('Key#'):
print('Unexpected key: ' + o.id)
if o.iv < 1 or NREC < o.iv:
print('Unexpected value: ' + d)
n = n + 1
c.close()
print(n)
# list keys where "Key#1075" <= key < "Key#1085"
c2 = pk.entities('Key#1075', True, 'Key#1085', False)
n2 = 0
for o in c2.iterator():
n2 = n2 + 1
c2.close()
print(n2)
# close database
est.close()
env.close()
Run:
$ javac -classpath /javalib/je-5_0_73.jar DataO.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ define/nolog jython_libs "/javalib/je-5_0_73.jar"
$ jython testo.py
An important capability of VMS index-sequential files is the support for multiple keys.
This is easy to do on BDB JE with the high level record API.
Example:
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.PrimaryKey;
import com.sleepycat.persist.model.Relationship;
import com.sleepycat.persist.model.SecondaryKey;
@Entity
public class BaseData {
@PrimaryKey
private int id;
@SecondaryKey(relate=Relationship.MANY_TO_ONE)
private String name;
@SecondaryKey(relate=Relationship.MANY_TO_ONE)
private String address;
private int value;
public BaseData() {
this(0, "", "", 0);
}
public BaseData(int id, String name, String address, int value) {
super();
this.id = id;
this.name = name;
this.address = address;
this.value = value;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
import java.io.File;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.SecondaryIndex;
import com.sleepycat.persist.StoreConfig;
public class MultiKey {
public static void main(String[] args) {
// open database
EnvironmentConfig envcfg = new EnvironmentConfig();
envcfg.setAllowCreateVoid(true);
envcfg.setTransactional(true);
Environment env = new Environment(new File("data/"), envcfg);
StoreConfig stcfg = new StoreConfig();
stcfg.setAllowCreate(true);
stcfg.setTransactional(true);
EntityStore est = new EntityStore(env, "mulkeydb", stcfg);
PrimaryIndex<Integer, BaseData> idix = est.getPrimaryIndex(Integer.class, BaseData.class);
SecondaryIndex<String, Integer, BaseData> namix = est.getSecondaryIndex(idix, String.class, "name");
SecondaryIndex<String, Integer, BaseData> addrix = est.getSecondaryIndex(idix, String.class, "address");
// put 3 records
idix.put(new BaseData(1, "A", "A A A", 123));
idix.put(new BaseData(2, "B", "B B B", 456));
idix.put(new BaseData(3, "C", "C C C", 789));
// lookup by id
System.out.println(idix.get(2).getValue());
System.out.println(idix.get(3).getValue());
// lookup by name
System.out.println(namix.get("B").getValue());
System.out.println(namix.get("C").getValue());
// lookup by address
System.out.println(addrix.get("B B B").getValue());
System.out.println(addrix.get("C C C").getValue());
// close database
est.close();
env.close();
}
}
Build and run:
$ javac -classpath /javalib/je-5_0_73.jar MultiKey.java BaseData.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ java -classpath .:/javalib/je-5_0_73.jar "MultiKey"
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.PrimaryKey;
import com.sleepycat.persist.model.Relationship;
import com.sleepycat.persist.model.SecondaryKey;
@Entity
public class BaseData {
@PrimaryKey
private int id;
@SecondaryKey(relate=Relationship.MANY_TO_ONE)
private String name;
@SecondaryKey(relate=Relationship.MANY_TO_ONE)
private String address;
private int value;
public BaseData() {
this(0, "", "", 0);
}
public BaseData(int id, String name, String address, int value) {
super();
this.id = id;
this.name = name;
this.address = address;
this.value = value;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
from java.lang import Integer
from java.lang import String
from java.io import File
from com.sleepycat.je import Environment
from com.sleepycat.je import EnvironmentConfig
from com.sleepycat.persist import EntityStore
from com.sleepycat.persist import PrimaryIndex
from com.sleepycat.persist import SecondaryIndex
from com.sleepycat.persist import StoreConfig
import BaseData
# open database
envcfg = EnvironmentConfig()
envcfg.setAllowCreateVoid(True)
env = Environment(File("data/"), envcfg)
stcfg = StoreConfig()
stcfg.setAllowCreate(True)
est = EntityStore(env, "mulkeydb", stcfg)
idix = est.getPrimaryIndex(Integer, BaseData)
namix = est.getSecondaryIndex(idix, String, "name")
addrix = est.getSecondaryIndex(idix, String, "address")
# put 3 records
idix.put(BaseData(1, "A", "A A A", 123))
idix.put(BaseData(2, "B", "B B B", 456))
idix.put(BaseData(3, "C", "C C C", 789))
# lookup by id
print(idix.get(2).value)
print(idix.get(3).value)
# lookup by name
print(namix.get("B").value)
print(namix.get("C").value)
# lookup by address
print(addrix.get("B B B").value)
print(addrix.get("C C C").value)
# close database
est.close()
env.close()
Run:
$ javac -classpath /javalib/je-5_0_73.jar BaseData.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ define/nolog jython_libs "/javalib/je-5_0_73.jar"
$ jython multikey.py
relate=Relationship.MANY_TO_ONE means that duplicates are allowed for the secondary key.
relate=Relationship.ONE_TO_ONE would enforce unique values for the secondary key.
BDB JE does not have a 32 KB limit on records like RMS index-sequential files.
No problems with big records.
Here is an example working with 1.5 MB texts:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
public class Big {
private static DatabaseEntry toEntry(String s) {
return new DatabaseEntry(s.getBytes());
}
private static String fromEntry(DatabaseEntry dbe) {
return new String(dbe.getData());
}
private static String load(String fnm) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new FileReader(fnm));
String line;
while((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
return sb.toString();
}
private static void store(String txt, String fnm) throws IOException {
PrintWriter pw = new PrintWriter(new FileWriter(fnm));
pw.print(txt);
pw.close();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// open database
EnvironmentConfig envcfg = new EnvironmentConfig();
envcfg.setAllowCreateVoid(true);
Environment env = new Environment(new File("data/"), envcfg);
DatabaseConfig dbcfg = new DatabaseConfig();
dbcfg.setAllowCreate(true);
Database db = env.openDatabase(null, "bigdb", dbcfg);
db.put(null, toEntry("copy1"), toEntry(load("4300-0.txt")));
db.put(null, toEntry("copy2"), toEntry(load("4300-0.txt")));
DatabaseEntry copy1 = new DatabaseEntry();
db.get(null, toEntry("copy1"), copy1, null);
store(fromEntry(copy1), "copy1.txt");
DatabaseEntry copy2 = new DatabaseEntry();
db.get(null, toEntry("copy2"), copy2, null);
store(fromEntry(copy2), "copy2.txt");
db.close();
env.close();
}
}
Build and run:
$ javac -classpath /javalib/je-5_0_73.jar Big.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ java -classpath .:/javalib/je-5_0_73.jar "Big"
BDB JE supports traditional transactions:
BEGIN operation 1 operation 2 ... operation n COMMIT or ROLLBACK
Two benefits:
The first is handled in VMS index-sequential files using explicit locking.
For more info about traditional transactions and transaction isolation level see here.
This can be very convenient.
Here is a classic example.
BLOB style:
import java.io.File;
import java.math.BigDecimal;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
public class TestTx {
public static class Account {
private BigDecimal amount;
public Account() {
this.amount = new BigDecimal("0.00");
}
public Account(BigDecimal amount) {
this.amount = amount;
}
public BigDecimal getAmount() {
return amount;
}
public void transfer(BigDecimal delta) {
amount = amount.add(delta);
}
// used for deserialization and serialization
public Account(byte[] b) {
this.amount = new BigDecimal(new String(b));
}
public byte[] toBytes() {
return amount.toPlainString().getBytes();
}
}
public static void saveAccount(Database db, String id, Account acct) {
TransactionConfig txcfg = new TransactionConfig();
txcfg.setReadCommitted(true);
Transaction tx = db.getEnvironment().beginTransaction(null, txcfg);
db.put(null, new DatabaseEntry(id.getBytes()), new DatabaseEntry(acct.toBytes()));
tx.commitSync();
}
public static Account getAccount(Database db, String id) {
DatabaseEntry acctdata = new DatabaseEntry();
db.get(null, new DatabaseEntry(id.getBytes()), acctdata, null);
return new Account(acctdata.getData());
}
public static void transfer(Database db, String fromid, String toid, BigDecimal amount) {
TransactionConfig txcfg = new TransactionConfig();
txcfg.setReadCommitted(true);
Transaction tx = db.getEnvironment().beginTransaction(null, txcfg);
DatabaseEntry acctdata = new DatabaseEntry();
db.get(tx, new DatabaseEntry(fromid.getBytes()), acctdata, null);
Account fromacct = new Account(acctdata.getData());
db.get(tx, new DatabaseEntry(toid.getBytes()), acctdata, null);
Account toacct = new Account(acctdata.getData());
fromacct.transfer(BigDecimal.ZERO.subtract(amount));
toacct.transfer(amount);
db.put(tx, new DatabaseEntry(fromid.getBytes()), new DatabaseEntry(fromacct.toBytes()));
db.put(tx, new DatabaseEntry(toid.getBytes()), new DatabaseEntry(toacct.toBytes()));
tx.commitSync();
//tx.abort();
}
public static void main(String[] args) {
// open database
EnvironmentConfig envcfg = new EnvironmentConfig();
envcfg.setAllowCreateVoid(true);
envcfg.setTransactional(true);
Environment env = new Environment(new File("data/"), envcfg);
DatabaseConfig dbcfg = new DatabaseConfig();
dbcfg.setAllowCreate(true);
dbcfg.setTransactional(true);
// setup accounts
Database db = env.openDatabase(null, "acctdb", dbcfg);
saveAccount(db, "A", new Account(new BigDecimal("100.00")));
saveAccount(db, "B", new Account(new BigDecimal("0.00")));
// display accounts
Account acct_a = getAccount(db, "A");
System.out.printf("%s : %s\n", "A", acct_a.getAmount());
Account acct_b = getAccount(db, "B");
System.out.printf("%s : %s\n", "B", acct_b.getAmount());
// transfer money
transfer(db, "A", "B", new BigDecimal("20.00"));
// display accounts
acct_a = getAccount(db, "A");
System.out.printf("%s : %s\n", "A", acct_a.getAmount());
acct_b = getAccount(db, "B");
System.out.printf("%s : %s\n", "B", acct_b.getAmount());
// close database
db.close();
env.close();
}
}
$ javac -classpath /javalib/je-5_0_73.jar TestTx.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ java -classpath .:/javalib/je-5_0_73.jar "TestTx"
Record style:
import java.io.File;
import java.math.BigDecimal;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.StoreConfig;
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.PrimaryKey;
public class TestTxO {
@Entity
public static class Account {
@PrimaryKey
private String name;
private BigDecimal amount;
public Account() {
this("", new BigDecimal("0.00"));
}
public Account(String name, BigDecimal amount) {
this.name = name;
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getAmount() {
return amount;
}
public void transfer(BigDecimal delta) {
amount = amount.add(delta);
}
}
public static void saveAccount(PrimaryIndex<String, Account> pk, Account acct) {
TransactionConfig txcfg = new TransactionConfig();
txcfg.setReadCommitted(true);
Transaction tx = pk.getDatabase().getEnvironment().beginTransaction(null, txcfg);
pk.put(acct);
tx.commitSync();
}
public static void transfer(PrimaryIndex<String, Account> pk, String fromid, String toid, BigDecimal amount) {
TransactionConfig txcfg = new TransactionConfig();
txcfg.setReadCommitted(true);
Transaction tx = pk.getDatabase().getEnvironment().beginTransaction(null, txcfg);
Account fromacct = pk.get(fromid);
Account toacct = pk.get(toid);
fromacct.transfer(BigDecimal.ZERO.subtract(amount));
toacct.transfer(amount);
toacct.transfer(amount);
pk.put(fromacct);
pk.put(toacct);
tx.commitSync();
//tx.abort();
}
public static void main(String[] args) {
// open database
EnvironmentConfig envcfg = new EnvironmentConfig();
envcfg.setAllowCreateVoid(true);
envcfg.setTransactional(true);
Environment env = new Environment(new File("data/"), envcfg);
StoreConfig stcfg = new StoreConfig();
stcfg.setAllowCreate(true);
stcfg.setTransactional(true);
EntityStore est = new EntityStore(env, "acctdb", stcfg);
PrimaryIndex<String, Account> pk = est.getPrimaryIndex(String.class, Account.class);
// setup accounts
saveAccount(pk, new Account("A", new BigDecimal("100.00")));
saveAccount(pk, new Account("B", new BigDecimal("0.00")));
// display accounts
Account acct_a = pk.get("A");
System.out.printf("%s : %s\n", acct_a.getName(), acct_a.getAmount());
Account acct_b = pk.get("B");
System.out.printf("%s : %s\n", acct_b.getName(), acct_b.getAmount());
// transfer money
transfer(pk, "A", "B", new BigDecimal("20.00"));
// display accounts
acct_a = pk.get("A");
System.out.printf("%s : %s\n", acct_a.getName(), acct_a.getAmount());
acct_b = pk.get("B");
System.out.printf("%s : %s\n", acct_b.getName(), acct_b.getAmount());
// close database
est.close();
env.close();
}
}
$ javac -classpath /javalib/je-5_0_73.jar TestTxO.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ java -classpath .:/javalib/je-5_0_73.jar "TestTxO"
But to really illustrate the power we need to see the example with some failures between the two updates to show that we end up in a consistent state.
BLOB style:
import java.io.File;
import java.math.BigDecimal;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
public class TestTx2 {
public static class Account {
private BigDecimal amount;
public Account() {
this.amount = new BigDecimal("0.00");
}
public Account(BigDecimal amount) {
this.amount = amount;
}
public BigDecimal getAmount() {
return amount;
}
public void transfer(BigDecimal delta) {
amount = amount.add(delta);
}
// used for deserialization and serialization
public Account(byte[] b) {
this.amount = new BigDecimal(new String(b));
}
public byte[] toBytes() {
return amount.toPlainString().getBytes();
}
}
public static void saveAccount(Database db, String id, Account acct) {
TransactionConfig txcfg = new TransactionConfig();
txcfg.setReadCommitted(true);
Transaction tx = db.getEnvironment().beginTransaction(null, txcfg);
db.put(null, new DatabaseEntry(id.getBytes()), new DatabaseEntry(acct.toBytes()));
tx.commitSync();
}
public static Account getAccount(Database db, String id) {
DatabaseEntry acctdata = new DatabaseEntry();
db.get(null, new DatabaseEntry(id.getBytes()), acctdata, null);
return new Account(acctdata.getData());
}
public static void transfer(Database db, String fromid, String toid, BigDecimal amount, boolean failflag) {
TransactionConfig txcfg = new TransactionConfig();
txcfg.setReadCommitted(true);
Transaction tx = db.getEnvironment().beginTransaction(null, txcfg);
try {
DatabaseEntry acctdata = new DatabaseEntry();
db.get(tx, new DatabaseEntry(fromid.getBytes()), acctdata, null);
Account fromacct = new Account(acctdata.getData());
db.get(tx, new DatabaseEntry(toid.getBytes()), acctdata, null);
Account toacct = new Account(acctdata.getData());
fromacct.transfer(BigDecimal.ZERO.subtract(amount));
toacct.transfer(amount);
db.put(tx, new DatabaseEntry(fromid.getBytes()), new DatabaseEntry(fromacct.toBytes()));
if(failflag) throw new RuntimeException("Something happended");
db.put(tx, new DatabaseEntry(toid.getBytes()), new DatabaseEntry(toacct.toBytes()));
tx.commitSync();
} catch(Exception ex) {
tx.abort();
}
}
public static void main(String[] args) {
// open database
EnvironmentConfig envcfg = new EnvironmentConfig();
envcfg.setAllowCreateVoid(true);
envcfg.setTransactional(true);
Environment env = new Environment(new File("data/"), envcfg);
DatabaseConfig dbcfg = new DatabaseConfig();
dbcfg.setAllowCreate(true);
dbcfg.setTransactional(true);
// setup accounts
Database db = env.openDatabase(null, "acctdb", dbcfg);
saveAccount(db, "A", new Account(new BigDecimal("100.00")));
saveAccount(db, "B", new Account(new BigDecimal("0.00")));
// display accounts
Account acct_a = getAccount(db, "A");
System.out.printf("%s : %s\n", "A", acct_a.getAmount());
Account acct_b = getAccount(db, "B");
System.out.printf("%s : %s\n", "B", acct_b.getAmount());
// transfer money
transfer(db, "A", "B", new BigDecimal("20.00"), false);
transfer(db, "A", "B", new BigDecimal("20.00"), true);
transfer(db, "A", "B", new BigDecimal("20.00"), false);
transfer(db, "A", "B", new BigDecimal("20.00"), true);
transfer(db, "A", "B", new BigDecimal("20.00"), false);
// display accounts
acct_a = getAccount(db, "A");
System.out.printf("%s : %s\n", "A", acct_a.getAmount());
acct_b = getAccount(db, "B");
System.out.printf("%s : %s\n", "B", acct_b.getAmount());
// close database
db.close();
env.close();
}
}
$ javac -classpath /javalib/je-5_0_73.jar TestTx2.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ java -classpath .:/javalib/je-5_0_73.jar "TestTx2"
Record style:
import java.io.File;
import java.math.BigDecimal;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.StoreConfig;
import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.PrimaryKey;
public class TestTxO2 {
@Entity
public static class Account {
@PrimaryKey
private String name;
private BigDecimal amount;
public Account() {
this("", new BigDecimal("0.00"));
}
public Account(String name, BigDecimal amount) {
this.name = name;
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getAmount() {
return amount;
}
public void transfer(BigDecimal delta) {
amount = amount.add(delta);
}
}
public static void saveAccount(PrimaryIndex<String, Account> pk, Account acct) {
TransactionConfig txcfg = new TransactionConfig();
txcfg.setReadCommitted(true);
Transaction tx = pk.getDatabase().getEnvironment().beginTransaction(null, txcfg);
pk.put(acct);
tx.commitSync();
}
public static void transfer(PrimaryIndex<String, Account> pk, String fromid, String toid, BigDecimal amount, boolean failflag) {
TransactionConfig txcfg = new TransactionConfig();
txcfg.setReadCommitted(true);
Transaction tx = pk.getDatabase().getEnvironment().beginTransaction(null, txcfg);
try {
Account fromacct = pk.get(fromid);
Account toacct = pk.get(toid);
fromacct.transfer(BigDecimal.ZERO.subtract(amount));
toacct.transfer(amount);
toacct.transfer(amount);
pk.put(fromacct);
if(failflag) throw new RuntimeException("Something happended");
pk.put(toacct);
tx.commitSync();
} catch(Exception ex) {
tx.abort();
}
}
public static void main(String[] args) {
// open database
EnvironmentConfig envcfg = new EnvironmentConfig();
envcfg.setAllowCreateVoid(true);
envcfg.setTransactional(true);
Environment env = new Environment(new File("data/"), envcfg);
StoreConfig stcfg = new StoreConfig();
stcfg.setAllowCreate(true);
stcfg.setTransactional(true);
EntityStore est = new EntityStore(env, "acctdb", stcfg);
PrimaryIndex<String, Account> pk = est.getPrimaryIndex(String.class, Account.class);
// setup accounts
saveAccount(pk, new Account("A", new BigDecimal("100.00")));
saveAccount(pk, new Account("B", new BigDecimal("0.00")));
// display accounts
Account acct_a = pk.get("A");
System.out.printf("%s : %s\n", acct_a.getName(), acct_a.getAmount());
Account acct_b = pk.get("B");
System.out.printf("%s : %s\n", acct_b.getName(), acct_b.getAmount());
// transfer money
// transfer money
transfer(pk, "A", "B", new BigDecimal("20.00"), false);
transfer(pk, "A", "B", new BigDecimal("20.00"), true);
transfer(pk, "A", "B", new BigDecimal("20.00"), false);
transfer(pk, "A", "B", new BigDecimal("20.00"), true);
transfer(pk, "A", "B", new BigDecimal("20.00"), false);
// display accounts
acct_a = pk.get("A");
System.out.printf("%s : %s\n", acct_a.getName(), acct_a.getAmount());
acct_b = pk.get("B");
System.out.printf("%s : %s\n", acct_b.getName(), acct_b.getAmount());
// close database
est.close();
env.close();
}
}
$ javac -classpath /javalib/je-5_0_73.jar TestTxO2.java
$ define/nolog java$filename_controls 8
$ define/nolog decc$efs_charset true
$ define/nolog java$filename_match_list "*.jdb=shr=get,put"
$ java -classpath .:/javalib/je-5_0_73.jar "TestTxO2"
BDB JE is a very capable NOSQL Key Value Store database. And it works fine on VMS.
But I doubt that it will be used much on VMS. JVM languages has never been widely used on VMS.
Version | Date | Description |
---|---|---|
1.0 | January 2nd 2023 | Initial version |
See list of all articles here
Please send comments to Arne Vajhøj