Butterfly Persistence Programmatic Object Mapping

Jakob Jenkov
Last update: 2021-06-30

Sometimes the automatic object mapping and annotation based mapping are not sufficient. For instance, if you need more than one mapping of an object to the database, automatic and annotation based mapping is not enough. You will have to create the different mappings programmatically, or a least semi-programmatically. You can still combine mapping methods when doing programmatic mapping.

In Butterfly Persistence programmatic object mapping can be done either manually or semi-manually. In other words you can either create an object mapping from scratch yourself, or just modify the object mappings resulting from auto mapping and annotation based mapping.

The object mappings have to be in place before you can read or write objects. That means that the custom object mapping code has to be run before any read or write method calls. In a wep app you do not always know which code is run first. Well, you could put the custom object mapping code in a servlet's init method, and force the servlet to be loaded at startup time. But we found that to be an inelegant solution.

Object Mapping Keys

Remember that whenever you need to read or write an object using Butterfly Persistence you provide the API with an object mapping key. The purpose of the object mapping key is to identify the object mapping to use to read the object, in the internal object mapping cache. The object mapping key could be any object you choose, but if you want help from Butterfly Persistence to map a class to the database, the object mapping key should be either a java.lang.Class object, or an ObjectMappingKey instance.

The object mapping key is typically the first parameter of the IObjectDao method calls. For instance, Employee.class is the object mapping key in the method call below:

IObjectDao dao = daos.getObjectDao();

Employee employee = (Employee)
    dao.readByPrimaryKey(Employee.class, new Long(2));

To enable easy, always-in-time custom object mapping Butterfly Persistence provides the ObjectMappingKey class for advanced object mapping keys. An ObjectMappingKey can contain an ICustomObjectMapper instance, which can be used to provide custom mapping. Creating an ObjectMappingKey instance is done using the ObjectMappingKey class factory methods. The simplest object mapping key can be created like this:

    public static final ObjectMappingKey EMPLOYEE =
        ObjectMappingKey.createInstance(Employee.class);

Notice how the object mapping key is made a constant. That is because the ObjectMappingKey's factory methods gives each ObjectMappingKey instance a unique id, an int, which is used by its equals() and hashCode() methods. This speeds up object mapping cache lookups because the hashCode() method returns this int id, and because equals() also only compares int id's, instead of Class objects, table names etc.

The unique id assigned at creation also means that two ObjectMappingKey instances created with the exact same parameters will NOT identify the same object mapping in the object mapping cache. They will not have the same id. Therefore, once an object mapping key is created, reuse it throughout the life time of the application. In other words, make the object mapping key a singleton or constant. If you create a new ObjectMappingKey instance every time you need it, Butterfly Persistence will have to re-map the object to the database every time too, because a fresh ObjectMappingKey instance does not match any existing object mappings.

Custom Object Mappers

As mentioned earlier you can attach an ICustomObjectMapper instance to an ObjectMappingKey instance. It is done at creation time, like this:

public static final ObjectMappingKey EMPLOYEE =
    ObjectMappingKey.createInstance(Employee.class,
        new MyCustomObjectMapper());

Instead of the class MyCustomObjectMapper you should use your own class. The class has to implement the ICustomObjectMapper interface. The interface looks like this:

public interface ICustomObjectMapper {

    public IObjectMapping getObjectMapping(
        Object objectMappingKey) throws PersistenceException;

    public String getTableName(
        Object objectMappingKey) throws PersistenceException;

    public void modify(
        Object objectMappingKey, IObjectMapping mapping)
            throws PersistenceException;
}

When Butterfly Persistence sees that the object mapping key provided is an ObjectMappingKey instance, it will try to obtain the keys ICustomObjectMapper. If the custom object mapper is not null, Butterfly Persistence does the following:

  1. Calls getObjectMapping()
    First Butterfly Persistence calls the ICustomObjectMapper's getObjectMapping() method. If this method returns a non-null IObjectMapping instance, then this instance is stored in the internal cache using this ObjectMappingKey as key. The IObjectMapping instance will be used for any subsequent reads or writes where the same ObjectMappingKey instance is provided as object mapping key.

  2. Calls getTableName()
    If null is returned from the getObjectMapping() method Butterfly Persistence will try to auto-generate an object mapping. First Butterfly Persistence will call the ICustomObjectMapper's getTableName() method to see if the custom object mapper wants to provide the name of the table to map the class to. If a table name was returned by the getTableName() method Butterfly Persistence will try to map the class to that table. Otherwise Butterfly Persistence will try to guess the table name from the class name.

  3. Calls modify()
    Butterfly Persistence now auto-maps the getter and setters of the class to the columns of the table, using Butterfly Persistences name guessing algorithm (described elsewhere). When the auto-generated IObjectMapping instance is ready, Butterfly Persistence calls the ICustomObjectMappers modify() method, with the auto-generated object mapping as parameter. If you need to add getter / setter mappings that were not auto-detected, or need to mark columns as auto-generated, do it in the modify() method.

Below is an example of an ObjectMappingKey implementation that contains an ICustomObjectMapper that adds a getter / setter mapping to the auto-generated mapping, and marks the primary key getter as auto-generated.

public class CustomObjectMapper extends CustomObjectMapperBase {

    public void modify(Object objectMappingKey,
        IObjectMapping mapping)
    throws PersistenceException {

        mapping.getGetterMapping("id").setAutoGenerated(true);

        try {
          mapping.addGetterMapping(
              MyApp.PERSISTENCE_MANAGER.getObjectMappingFactory()
                .createGetterMapping(Employee.class
                    .getMethod("getValue", null), "VAL", true));
        } catch (NoSuchMethodException e) {
            throw new PersistenceException(
                "Error creating custom mapping", e);
        }
    }
}

The extended class CustomObjectMapperBase is an empty implementation of the ICustomObjectMapper interface. Extending CustomObjectMapperBase is easier if you only need to implement one of the ICustomObjectMapper interface's methods.

Removing Method Mappings

The example above only shows how to add new method mappings to an object mapping. It is, however, also possible to remove method mappings. Just call the
IObjectMapping.removeGetterMapping(String name) or
IObjectMapping.removeSetterMapping(String name) method to do so.

Removing method mappings can be useful when you need to do partial object reading or writing.

Auto-generated Table Columns

If a table column is auto-generated by the database, for instance a primary key or a timestamp, Butterfly Persistence should never try to write values to the column from the object. Since it cannot be determined via JDBC's DatabaseMetaData whether a column is auto-generated or not, you will have to tell Butterfly Persistence using an ICustomObjectMapper (or via annotations).

NOTE:
From the ResultSetMetaData it IS seemingly possible to see if a column is auto-generated or not, but this isn't much of a help until you have actually created a ResultSet during a read-operation. If the first operation your application does is a write, Butterfly Persistence would not have had a chance to add the "auto-generated" information to the object mapping. Therefore you must provide it manually.

You only have to mark the getter mapping as auto-generated. It is the getter mapping Butterfly Persistence uses to extract the value from the object in order to insert it into a PreparedStatement. If a getter mapping is marked as auto-generated Butterfly Persistence will leave it out of inserts and updates. The correspondning setter mapping is unaffected by this. The setter mapping is used when setting the value from the ResultSet into the object. Auto-generated columns should still be read. Therefore the setter mapping cannot be marked as auto-generated.

Here is a custom object mapper that marks the "id" getter mapping as auto-generated (the getter mapping to the "id" column that is).

public class CustomObjectMapper extends CustomObjectMapperBase {

  public void modify(Object objectMappingKey, IObjectMapping mapping)
  throws PersistenceException {
      mapping.getGetterMapping("id").setAutoGenerated(true);
  }
}

The ObjectMappingKey class has some static shortcut factory methods to create object mapping keys that has custom object mappers for auto-generated columns. The first method is:

createInstanceForAutoGeneratedPrimaryKey(Class aClass)

This method creates an ObjectMappingKey with an ICustomObjectMapper attached that marks all the columns in the primary key of the mapped table, as auto-generated. This is very handy if your table has an auto-incrementing primary key column.

The second method is:

createInstanceForAutoGeneratedColumns(Class    aClass,
                                      String[] columns)

This method marks all getter mappings matching the columns in the String array, as auto-generated.

Here is how it looks when you call these factory methods:

ObjectMappingKey objectMappingKey2 = ObjectMappingKey
    .createInstanceForAutoGeneratedPrimaryKey(Employee.class);


ObjectMappingKey objectMappingKey = ObjectMappingKey
    .createInstanceForAutoGeneratedColumns(Employee.class,
         new String[]{"id", "lastUpdated"});

Jakob Jenkov

Featured Videos

Java ConcurrentMap + ConcurrentHashMap

Java Generics

Java ForkJoinPool

P2P Networks Introduction

















Close TOC
All Tutorial Trails
All Trails
Table of contents (TOC) for this tutorial trail
Trail TOC
Table of contents (TOC) for this tutorial
Page TOC
Previous tutorial in this tutorial trail
Previous
Next tutorial in this tutorial trail
Next