Hi Lukas,
Just to get the complete picture [...] What currently keeps you from using
> JPA?
A long time ago, I used to use JPA to do very basic CRUD stuffs, and it was
working fine.
But I began to issue less and less insert/updates, and more and more
complex selects, and to be very frustrated with the "code-in-a-string" JPQL
approach, the verbosity of CriteriaBuilder, the lack of type-safety, and
the heaviness of JPA in general.
Then came jOOQ, and it was good, and then came Java 8 streams and
collectors, and there was nothing else left to say.
Until I needed a dumb CRUD resource on objects spanning several tables, and
the circle was complete.
Hence the idea to be able to occasionally CRUD simple object graphs with
jOOQ - the keywords being "occasionally" and "simple".
But I must confess I had the nasty feeling to re-create a JPA engine when
writing this code, so I may well be wrong in refusing to use JPA again.
Perhaps there's already a low-hanging feature request that could be
> extracted from the above and moved into core jOOQ.
>
Well, I'll say the easiest part is the code generation, but not sure it's
very useful by itself...
Also I'm heavily relying on Key objects, and it's bothering me that they
don't have any generic to represent their type. But I posted another topic
about that.
You bet. Check this out:
>
Ok, you won. I suddenly feel so pointless with my tiny helper...
I'm very curious about some examples of what you have done
>
For the purpose of demonstration, here is a simple graph:
------------
| SIMU |
|------------|
|PK: SIMU_ID |
------------
1
___________|___________
/ \
/ \
1 *
------------- ---------------------------
| SIMU_COST | | SIMU_OPERATION |
|-------------| |---------------------------|
| PK: SIMU_ID | | PK: SIMU_ID, OPERATION_ID |
| FK: SIMU_ID | | FK: SIMU_ID |
------------- ---------------------------
Where it is assumed that the SimuRecord class has two additional properties:
private SimuCostRecord cost;
private List<SimuOperationRecord> operation;
// and related accessors (getter/setter)
These properties are generated with a special JavaGenerator in this
example, but they could as well be written manually by simply extending the
SimuRecord
class.
*Tree creation*
The base of my object tree is the TreeNode class:
// KR: type of the PK of the parent (or root) table
// PR: type of the parent table
public abstract class TreeNode<KR extends Record, PR extends TableRecord<PR
>> {...}
I started with a simple tree builder to construct trees manually at runtime:
// SimpleNode assumes a Record1<K> primary key
TreeNode<Record1<Integer>, SimuRecord> NODE = SimpleNode
// parent table and its primary key
.one(SIMU, SIMU.SIMU_ID)
// one-to-one child with foreign key and accessors for a SimuCostRecord
property
.toOne(SIMU_COST, SIMU_COST.SIMU_ID, SimuRecord::getCost, SimuRecord::
setCost)
// one-to-many child with foreign key and accessors for a
List<SimuOperationRecord> property
.toMany(SIMU_OPERATION, SIMU_OPERATION.SIMU_ID, SimuRecord::getOperation
, SimuRecord::setOperation);
No need for actual foreign or unique keys here so it can be used with any
schema.
Then comes the interesting part, allowing user to write as little code as
possible:
// Associations are retrieved from foreign and unique keys reported by jOOQ,
// and accessors are found by reflection.
TreeNode<?, SimuRecord> NODE = new ReflectNode<SimuRecord>(SIMU);
ReflectNode is very simple: it deduces relationships from the foreign keys
referencing the parent table primary key, and looks for unique constraints
to determine their cardinality:
// Relationships targeting the Table "parentTable"
Collection<ForeignKey<?,?>> keys = parentTable
.getPrimaryKey()
.getReferences();
// Cardinality of relationship defined by ForeignKey "fk"
boolean unique = fk
.getTable()
.getKeys()
.stream()
.anyMatch(uk -> fk.getFields().containsAll(uk.getFields()));
This code is used in both code generation (to generate properties) and at
runtime to create the tree.
At runtime, properties are retrieved by reflecting on generated record
classes.
*Usage*
And then, after writing the appropriate code to fetch/merge/delete a tree
(which, with all the assumptions made on PKs and FKs, is quite simple),
here is how a simple CRUD resource can look like:
private final static TreeNode<?, SimuRecord> NODE; // use one of the
above method to create the tree
// assumes a DSLContext field "dsl" is available
@GET
public Collection<SimuRecord> query() {
return NODE.fetch(dsl);
}
@POST
public SimuRecord merge(SimuRecord parent) {
NODE.merge(dsl, parent);
return NODE.fetchOne(dsl, condition(intoPrimaryKey(parent)));
}
@DELETE
@Path("{id}")
public void delete(@PathParam("id") Integer id) {
NODE.delete(dsl, DSL.row(id));
}
*Conclusion*
First remark: the ReflectNode doesn't have a known type for the parent
primary key (it extends TreeNode<?, Record>). It makes the NODE.delete()
method typeless and I'm not happy with that. This must be improved. But I'm
still not sure about the right way to convey typed keys (posted another
topic about that).
Then if I'm quite happy with the CUD part of the CRUD, the R part may be
too simplistic, I'll probably need filters and joins in my select one day.
So I'd like to find a way to mix this tree approach with a custom select
query. Maybe Record with additional fields (Field<Record> or
Field<List<Record>>) can replace concrete TableRecord with additional
properties. Saw some feature requests about this kind of field I'll need to
check.
But then again, let's try not to write yet another JPA engine.
I've not included the actual fetch/merge/delete code nor the generator
code, because I didn't manage to extract meaningful snippets. And this post
is already long enough anyway, seems I've been carried away by enthusiasm ;)
But I may share once properly packaged in a more standalone lib, if you're
interested.
Cheers,
Thomas
On Monday, February 27, 2017 at 5:57:01 PM UTC+1, Lukas Eder wrote:
>
> Hi Thomas,
>
> Thanks a lot for your comments. I'm very curious about some examples of
> what you have done:
>
> 2017-02-27 13:17 GMT+01:00 Thomas GILLET <[email protected]>:
>
>> What I've done for now is:
>> - extend the JavaGenerator to generate properties (i.e
>> field/getter/setter) in generated records (or POJOs) based on foreign keys
>> - write code to fetch records from a table and its "children" tables, and
>> then associate children records to their parent (that is create a graph)
>> using generated setters
>> - write code to insert/update/merge/delete a graph of record objects,
>> copying foreign key values from parent to children when needed
>>
>
> Perhaps there's already a low-hanging feature request that could be
> extracted from the above and moved into core jOOQ.
>
>
>> For fetching it's a lot less than what a MULTISET can offer I guess
>>
>
> You bet. Check this out:
> https://twitter.com/lukaseder/status/832939068933681152
>
> Getting all actors, and the categories they played in, as a nested
> collection, and the films per category they played in as a doubly nested
> collection. Beautiful! :)
>
>
>> but the idea was to have write capabilities too (so you can insert/update
>> a graph right from deserialized JSON).
>> And for now I only handles one-to-one and one-to-many relationships.
>>
>
> Just to get the complete picture: Materialising a 1:1 entity graph that is
> supposed to be written back to the database later on is exactly the main
> value proposition of JPA. What currently keeps you from using JPA?
>
> Cheers,
> Lukas
>
--
You received this message because you are subscribed to the Google Groups "jOOQ
User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.