If you are using Db4o for persistence and you have to identify your objects using UUIDs, currently you have two possible choices.
- Use db4o UUID
- Use java.util.UUID
Db4o UUID
The first alternative is to use db4o's own solution, it is easy, doesn't need extra effort since it is all done by db4o out-of-the-box. The main drawback is that it is ugly from a design point of view.
Here is an
example provided by Db4o.
As you can see, it is not comfortable at all, you need extra db4o methods invocations such as
container.ext().getObjectInfo(obj).getUUID()
and
container.ext().getByUUID(idForObject)
. Also the objects are not activated so you have to do it manually.
Another thing I don't like about it, is the fact that you have to import a db4o-specific class in core classes which have to deal with uuids, and it is not acceptable.
Finally your persistent classes are not unit-testable outside the persistence context since Db4o is which generates the uuids at store-time.
For all the disadvantages pointed out above it is not a suitable solution.
java.util.UUID as String
So we have the second alternative, to use java.util.UUID.
It is an utility class for working with UUIDs which is included in the Java runtim
e, therefore, it is ok to import it everywhere you need, such as having an id field in your persistent objects.
But the bad news are that Db4o lacks native support for java UUIDs.
Here in this
example they propose to hold the UUID as a String field, so you can index it for speed up querying. It is a bad idea, since UUID in String format takes too much space and it is not nice to treat UUIDs as strings everywhere when you have a specialized and optimized class for it.
H
owever, it is even worst to hold it in an UUID field, since it is a regular object for Db4o and if you want to query by it, Db4o will populate all UUID instances to compare them against the desired value using the equals method. An as you can imagine, it is terrible for performance.
java.util.UUID with custom type handling
This one is the best choice, and is what this post is all about. A very powerful feature (although very hard to work with) is the TypeHandlers mechanism. The main idea is, by implementing a couple of interfaces, to control the way in which Db4o handles the persistence (writing, reading, indexing, etc) of objects of certain classes for which you want special treatment.
In my current project, I use UUIDs a lot, it is a main feature for me to find an element by its id (UUID), in some cases even without knowing the concrete class, so I have to query by id all the elements in the database. But lately I have experienced some performance issues all related to this key ability.
For solving it I decided to improve the UUID handling in my project. I searched a lot in the Db4o Developer's site and in the internet in general, but without any luck. So I opted for implementing my own type handler.
This time, with a little bit of better luck, I found an abstract class from which to extend mine. It is com.db4o.internal.handlers.BigNumberTypeHandler. Don't stop in the name, it is called this way because Db4o uses it as base class for BigDecimal and BigInteger handlers, but it could be renamed in the future.
This abstract class gives you a couple of hooks for you to convert your class to and back again from byte array, then it takes care of everything, writing, reading, indexing, comparing, etc. So it could be named ByteArrayTypeHandler or something similar.
Let's see how the type handler looks like
public class UuidTypeHandler extends BigNumberTypeHandler<UUID> {
@Override
protected int compare(UUID uuid1, UUID uuid2) {
return uuid1.compareTo(uuid2);
}
@Override
protected UUID fromByteArray(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
LongBuffer longBuffer = buffer.asLongBuffer();
return new UUID(longBuffer.get(0), longBuffer.get(1));
}
@Override
protected byte[] toByteArray(UUID uuid) {
byte[] byteArray = new byte[(Long.SIZE / Byte.SIZE) * 2];
ByteBuffer buffer = ByteBuffer.wrap(byteArray);
LongBuffer longBuffer = buffer.asLongBuffer();
longBuffer.put(new long[] { uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits() });
return byteArray;
}
}
As you can see I'm using ByteBuffer for doing the conversion, I create a byte array which size is the bytes needed to represent 2 Longs (Long.SIZE / Byte.SIZE) * 2), in Java a Long is represented using 64 bits and a byte uses 8 bits, so our array will be of size 16, 8 bytes per each Long. Finally I write first the most significant bits of the UUID and then the least significant bits.
Converting from byte array to UUID back again is just a matter of reading the two Longs in the right order.
I have only one more method to mention, compare, which is pretty trivial, I just delegate on UUID.compareTo(other).
Now I'll show you how to start using it. You will need to register the type handler in the Db4o configuration like this:
EmbeddedConfiguration configuration = Db4oEmbedded.newConfiguration();
// Simple predicate for matching all UUID instances
TypeHandlerPredicate predicate = new SingleClassTypeHandlerPredicate(UUID.class);
// Register the type handler for handling all UUID.class objects
configuration.common().registerTypeHandler(predicate, new UuidTypeHandler());
Firstly you have to create a new configuration instance. You will need a TypeHandlerPredicate as well, it is used to know when to used the TypeHandler we are registering, you can create your own implementation if the matching logic is not that simple, but for this case it is enough with SingleClassTypeHandlerPredicate. Finally, you have to invoke registerTypeHandler(...) method to in commonConfiguration.
Now that you have registered the TypeHandler you are able to index all UUID fields for better query performance. For trying it you can download the
example project. You will find the UuidTypeHandler class, as well as a dummy class which has an UUID field called id. Here you have it:
public class UniversalUniqueElement {
private static int COUNTER = 1;
private UUID id;
private String name;
public UniversalUniqueElement() {
this.id = UUID.randomUUID();
this.name = "Element #" + UniversalUniqueElement.COUNTER++;
}
public UUID getId() {
return this.id;
}
public String getName() {
return this.name;
}
@Override
public String toString() {
return String.format("UniversalUniqueElement [id=%s, name=%s]", this.id, this.name);
}
}
You can create the index for id field this way:
// Now we have registered the handler we are able to index the id field
configuration.common().objectClass(UniversalUniqueElement.class).objectField("id").indexed(true);
The example project also contains a test for you to try it in action. It runs two methods, with and without the type handler, with the very same loop which creates 1000 elements and then queries them by their ids. The test writes to console the spent time for each method, so you will see the huge difference.
Here you have an Eclipse/Maven project for trying it all together.
Download