jOOQ and Spring: Manage your RecordMappers as @Components

A RecordMapperProvider implementation that resolves jOOQ RecordMappers from a Spring application context
September 9, 2015 by Michael

Lately i’ve been evaluating jOOQ as a core database tool for ENERKO Informatik. Though i’ve got quite some experience (and good experience!) with Hibernate and Spring Data JPA, we have some projects around that are quite old but not outdated. They are in need for an UI/UX and some architectural make over, but the data model is mostly not but has never been designed with an ORM in mind. All that will be the topic of a later post, as I’m not done with my evaluation.

We will be using jOOQ as part of a Spring / Spring Boot project, so @petrikainulaines post Using jOOQ With Spring is of quite some use and i intend to add some info as well.

jOOQ provides several basic but quite functional ways to map records into POJOs as described here, but that’s pretty much it. jOOQs primary goal isn’t defining a new ORM, but the best way to write SQL in Java. Playing around with my data model, i really start liking it. Instead of defining multiple entity graphs, i can select the columns, children and everything i need the way it is meant to be and build my DTOs the way i want it. Enter RecordMappers for creating complex POJOS from records. If you don’t want to roll your own RecordMappers, there are quite a few out there .

Here is a super simple RecordMapper, that does pretty much nothing apart from setting an attribute:

import de.enerko.ensupply.db.tables.records.AnwenderRecord;
import org.jooq.RecordMapper;
import org.springframework.stereotype.Component;
 
@Component
public class AnwenderRecordMapper implements RecordMapper<AnwenderRecord, Anwender> {
    @Override
    public Anwender map(AnwenderRecord record) {
	final Anwender rv = new Anwender();
	rv.setName(record.getName());
	return rv;
    }
}

Notice the @Component annotation, which makes the bean a Spring Component. Alternatively it can be configured through plain Java or even XML configuration.

Enter my RecordMapperProviderImpl

What does it do? It retrieves a map of all beans implementing the RecordMapper interface and searches for the bean whose E type parameter (which isn’t erased when inheriting a typed interface like shown above) matches the target type.

I even wanted to compare the RecordType, but that type parameter is erased at runtime and so i’m basically comparing org.jooq.Record with itself. An API method to retrieve this would be nice, hint,hint, Lukas ;).

You may notice the @Cacheable annotation: If configured correctly (in Spring boot, just throw @EnableCaching at your Application class), Spring will cache the outcome of the annotated method and so preventing several expensive type lookups (which will be of some importance when mapping more than a handful of records).

Thanks to @thomasdarimont i got rid of the ApplicationContext dependency and @olivergierke was so kind about pointing me to ResolvableType so that i only depend on Spring and jOOQ.

The RecordMapperProviderImpl is registered with the jOOQ configuration as follows:

@Bean
public org.jooq.Configuration jooqConfig(
	    ConnectionProvider connectionProvider,
	    TransactionProvider transactionProvider, 
	    ExecuteListenerProvider executeListenerProvider,
	    RecordMapperProvider recordMapperProvider,
	    @Value("${jooq.renderFormatted:false}")
	    boolean renderFormatted
) {
	final DefaultConfiguration hlp = new DefaultConfiguration();
	return hlp
		.derive(hlp.settings()
			.withRenderFormatted(renderFormatted)
		)
		.derive(connectionProvider)
		.derive(transactionProvider)
		.derive(executeListenerProvider)	
		.derive(recordMapperProvider)
		.derive(SQLDialect.ORACLE);
}

(Bye the way, this will be done automatically in upcoming Spring Boot 1.3 given RecordMapperProviderImpl is an @Component like in the example).

If everything works well, expect some jOOQ and SQL content coming soon.

Update:

See my tweet, composing Record Mappers is really simple using that technique:

import de.enerko.ensupply.db.tables.records.AnwenderRecord;
import org.jooq.RecordMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
@Component
public class SpecialRecordMapper implements RecordMapper<Record, SomeObject> {
    private final AnwenderRecordMapper anwenderRecordMapper;	
 
    @Autowired
    public SpecialRecordMapper(final AnwenderRecordMapper anwenderRecordMapper) {
        this.anwenderRecordMapper = anwenderRecordMapper;
    }
 
    @Override
    public Anwender map(Record record) {
	SomeObject s = new SomeObject();
	// Do other mapping stuff
	s.setAnwender(this.anwenderRecordMapper.map(record.into(AnwenderRecord.class));
	return s;
    }
}

No comments yet

2 Trackbacks/Pingbacks
  1. Thoughts about architecture | info.michael-simons.eu on September 23, 2015 at 1:58 PM

    […] into defining a logical and accurate class hierarchy to work with JPA but instead modeling and filling DTOs and business objects right from the database in a way that fits architectural slices of an […]

  2. […] So far nothing got executed. At this point you also can use #getSQL to just retrieve the generated SQL. #fetch will get you a result of records, any of the overloaded methods allow you to retrieve just the columns you need into a record. Note that no mapping to concrete objects is necessary at this point! You can however use generated POJOs or my idea of providing RecordMapper from a Spring Context. […]

Post a Comment

Your email is never published. We need your name and email address only for verifying a legitimate comment. For more information, a copy of your saved data or a request to delete any data under this address, please send a short notice to michael@simons.ac from the address you used to comment on this entry.
By entering and submitting a comment, wether with or without name or email address, you'll agree that all data you have entered including your IP address will be checked and stored for a limited time by Automattic Inc., 60 29th Street #343, San Francisco, CA 94110-4929, USA. only for the purpose of avoiding spam. You can deny further storage of your data by sending an email to support@wordpress.com, with subject “Deletion of Data stored by Akismet”.
Required fields are marked *