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
[…] 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 […]
[…] 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