Use ModelMapper and jOOQ to Regain Control of your Domain Model

Please note that this is an old article and we no longer recommend using any third parties to map SQL results to domain models. Instead, use jOOQ’s own capabilities of nesting records, nesting collections, and applying ad-hoc conversion

If you’re operating on rather complex relational models, mapping data onto rather complex domain models, then you might want to get back in control of the mapping process, as auto-mapping will cause more headaches than it solves problems. An interesting approach has been shown recently on the ModelMapper website in an example integration with jOOQ. (note, there is also an example integration with JDBI). With the permission of the author Jonathan Halterman, I’m citing this interesting example:

jOOQ Integration

ModelMapper’s jOOQ integration allows you to map a jOOQ Record to a JavaBean.

Setup

To get started, add the modelmapper-jooq Maven dependency to your project:

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>0.6.1</version>
</dependency>
<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper-jooq</artifactId>
  <version>0.6.1</version>
</dependency>

Next, configure ModelMapper to support the RecordValueReader, which allows for values to be read and mapped from a jOOQ Record:

modelMapper.getConfiguration()
           .addValueReader(new RecordValueReader());

Example Mapping

Now let’s see an example mapping of a jOOQ record to a JavaBean. Consider the following record representing an order:

order_idcustomer_idcustomer_address_streetcustomer_address_city
345678123 Main StreetSF

We may need to map this to a more complex object model:

// Assume getters and setters are present

public class Order {
  private int id;
  private Customer customer;
}

public class Customer {
  private Address address;
}

public class Address {
  private String street;
  private String city;
}

Since the source Record’s fields in this example uses an underscore naming convention, we’ll need to configure ModelMapper to tokenize source property names by underscore:

modelMapper
  .getConfiguration()
  .setSourceNameTokenizer(NameTokenizers.UNDERSCORE);

With that set, mapping an order Record to an Order object is simple:

Order order =
  modelMapper.map(orderRecord, Order.class);

And we can assert that values are mapped as expected:

assertEquals(456, order.getId());
assertEquals(789, order.getCustomer().getId());
assertEquals("123 Main Street",
             order.getCustomer()
                  .getAddress()
                  .getStreet());
assertEquals("SF",
             order.getCustomer()
                  .getAddress()
                  .getCity());

Explicit Mapping

While ModelMapper will do its best to implicitly match Record values to destination properties, sometimes you may need to explicitly define mappings between properties.

Let’s map our Record’s customer_address_street to Order.customer.address.street:

PropertyMap<Record, Order> orderMap =
  new PropertyMap<Record, Order>() {
  protected void configure() {
    map(source("customer_address_street"))
        .getCustomer()
        .getAddress()
        .setStreet(null);
  }
};

Then we can add the mapping to our ModelMapper instance for the orderRecord:

modelMapper.createTypeMap(orderRecord, Order.class)
           .addMappings(orderMap);

(see the ModelMapper manual pages for more details about property mapping)

Things to Note

ModelMapper maintains a TypeMap for each source and destination type, containing the mappings bewteen the two types. For “generic” types such as Record this can be problematic since the structure of a Record can vary. In order to distinguish structurally different Records that map to the same destination type, we can provide a type map name to ModelMapper.

Continuing with the example above, let’s map another order Record, this one with a different structure, to the same Order class:

order_idorder_customer_idorder_customer_address_streetorder_customer_address_city
444777123 Main StreetLA

Mapping this Record to an order is simple, but we’ll need to provide a type map name to distinguish this Record to Order mapping from the previous unnamed mapping:

Order order = modelMapper.map(
    longOrderRecord, Order.class, "long");

Example taken from:
http://modelmapper.org/user-manual/jooq-integration/

More Examples

When choosing ModelMapper, you’re not just chosing an API to map relational data to your domain model. ModelMapper is designed for arbitrary model transformation, which can make it a strategic choice for your stack.

Check out this marvelous Open Source gem on the ModelMapper website.

One thought on “Use ModelMapper and jOOQ to Regain Control of your Domain Model

Leave a Reply