[ REQUEST FOR FEEDBACK ] DAO Deprecation

347 views
Skip to first unread message

Lukas Eder

unread,
Mar 20, 2017, 12:47:07 PM3/20/17
to jooq...@googlegroups.com
Dear group,

The DAO type has been haunting this list ever since it was added to the library in version 2.4 (2012). At the time, it was a quick win over a competing library that already had the feature. They were extremely easy to implement in jOOQ.

However, DAOs (much like repositories, e.g. in Spring Data) are some of the most opinionated things in software. As such, they require either:

- A very strong opinion on the matter (duh), but jOOQ doesn't have one
- An extremely versatile design that satisfies all needs, which is probably not possible

This list has debated DAOs time and again in the past. There had been many feature requests, extension requests, and people criticising jOOQ's use of the Java final keyword as it prevents the application of the open-closed principle, at least in those people's understanding. My understanding is a bit different: https://blog.jooq.org/2017/03/20/the-open-closed-principle-is-often-not-what-you-think-it-is

In fact, people bumping into jOOQ's usage of final in this area simply shows that the design (or the vision) is insufficient in this particular case. Otherwise, there would be no desire to "hack" additional behaviour into the existing API through what I believe to be a highly overrated tool of object orientation: Concrete class overriding.

Non-essential, yet incomplete features distract us from what jOOQ really does well: SQL language abstraction. And this causes great harm to the library as I'd really rather be implementing features like MULTISET, SQL's temporal validity, better XML and JSON support, etc. etc. than debating DAOs.

I am thus proposing the deprecation of the DAO type per jOOQ 3.10, and its removal per jOOQ 4.0:

I would like to invite you to join this discussion either to:

- Confirm that you agree with this move
- Request we continue supporting DAOs (in case of which: please illustrate why they're a killer feature for you)

Best Regards,
Lukas

Bill O'Neil

unread,
Mar 20, 2017, 1:24:30 PM3/20/17
to jOOQ User Group
I use the DAOs quite heavily on my side projects and find them to be very helpful for simple CRUD operations which I believe was the intention.

However, I also think it would be doing a disservice to the jOOQ community if you are wasting lot's of time on feature requests and answering questions on DAOs.

Do you find it a burden to keep DAO generation up to date with newer versions of jOOQ?

If not, I would ask if you could split it to its own release schedule and freeze all features. In this case you just list breaking changes (github issues?) and ask the community to contribute. Once all changes are made you can review and release. Freezing features still gives simple crud but all new features should be rejected. If someone wants additional functionality they can fork or write custom generators. You would still need to work on it occasionally but hopefully drastically less and it wouldn't block the release schedule of more important features.

If yes, possibly find someone else to take it over, someone who says no to most feature requests preferably ;)?

My killer features
- Simplicity, You get CRUD for free out of the box.
- Optimizations such as SQL batching under the hood for free.
- POJO generation - I just let a lot of these classes pass through my whole codebase since they are plain POJOs. If I need something special or some optimization then I can map them to other POJOs later.

When I need to add functionality I just have a class that uses composition with one or more DAOs. Sure there is some boilerplate and it might just pass through to the DAO for a lot of methods but it takes almost no effort to do that. The more complex queries then use the DSL directly.

I'm not sure if this could have any dual licensing issues.

Bill

Lukas Eder

unread,
Mar 20, 2017, 5:14:21 PM3/20/17
to jooq...@googlegroups.com
Thank you very much for your feedback, Bill. I will comment inline

2017-03-20 18:24 GMT+01:00 Bill O'Neil <onei...@gmail.com>:
I use the DAOs quite heavily on my side projects and find them to be very helpful for simple CRUD operations which I believe was the intention.

That was absolutely the intention. However, few people know that a lot of functionality offered by DAOs is also available from DSLContext with almost the same ease of use. This deprecation is not about the quick-and-dirty fetch of rows. There's always room for adding more convenience API that removes some CRUD boilerplate.

This is about the DAO type (and its code generation), which enforces an opinionated 1:1 mapping between the domain model and the relational model, which works only for small projects, or at the beginning, leaving many open questions later on.
 
However, I also think it would be doing a disservice to the jOOQ community if you are wasting lot's of time on feature requests and answering questions on DAOs.

That's one way of looking at it, but I also think that having these DAOs is a disservice to the community because the community's time is wasted trying to figure out how to best use these things (and then after some time, probably abandon the idea).
 
Do you find it a burden to keep DAO generation up to date with newer versions of jOOQ?

No, that's not a problem at all. It's actually rather trivial, the way it is now.
 
If not, I would ask if you could split it to its own release schedule and freeze all features. In this case you just list breaking changes (github issues?) and ask the community to contribute. Once all changes are made you can review and release. Freezing features still gives simple crud but all new features should be rejected. If someone wants additional functionality they can fork or write custom generators. You would still need to work on it occasionally but hopefully drastically less and it wouldn't block the release schedule of more important features.

If yes, possibly find someone else to take it over, someone who says no to most feature requests preferably ;)?

If this is to be deprecated, then it will be frozen for the jOOQ 3.x release stream and removed from 4.0. I think it is not a good idea to keep such things around in a frozen state.

Note that jOOQ 4.0 is probably still quite some time away. The cost / benefit ratio of the 4.0 roadmap is not strong enough yet to start implementing breaking changes. (The most important driver being a completely immutable DSL).

Having said so, the existing DAO / DAOImpl APIs and implementations are so trivial, really, that it will be very easy to fork this functionality to a new repository if someone will see the need after jOOQ 4.0.

So, I don't think we'll have an issue here.
 
My killer features
- Simplicity, You get CRUD for free out of the box.

Absolutely, but that isn't restricted to DAO. During this deprecation, we'll certainly look at improving (and probably better documenting) the existing CRUD API in DSLContext
 
- Optimizations such as SQL batching under the hood for free.

Good point, I think that functionality isn't really available as transparently in DSLContext yet. It is more of a conscious choice, e.g. through: DSLContext.batchInsert(UpdatableRecord...)
 
- POJO generation - I just let a lot of these classes pass through my whole codebase since they are plain POJOs. If I need something special or some optimization then I can map them to other POJOs later.

I would like to learn more about this part - it is the most controversial aspect of the DAO, because the generated POJOs are really closely coupled to the existing DAOs, conceptually. Has the rigid 1:1 mapping between "domain model" and relational model never really bothered you?

When I need to add functionality I just have a class that uses composition with one or more DAOs. Sure there is some boilerplate and it might just pass through to the DAO for a lot of methods but it takes almost no effort to do that. The more complex queries then use the DSL directly.

So, you use DAOs as a pragmatic "starter" or "base implementation" until they stop working for you?
 
I'm not sure if this could have any dual licensing issues.

What do you mean by this?

Thanks again for your feedback, this is very helpful!

Bill O'Neil

unread,
Mar 20, 2017, 6:06:09 PM3/20/17
to jOOQ User Group
That's one way of looking at it, but I also think that having these DAOs is a disservice to the community because the community's time is wasted trying to figure out how to best use these things (and then after some time, probably abandon the idea).
 
 You are probably right here I have been using DAOs for 3-4 years now thinking it was the right way to do the CRUD parts. I'd love if you can point me to some of the DSL methods you are referring to. Just took a quick look here https://www.jooq.org/doc/2.6/manual/sql-execution/crud-with-updatablerecords/simple-crud/ I guess I could just then map it to a POJO similar to how the DAOs do it unless there is an easier way.

Absolutely, but that isn't restricted to DAO. During this deprecation, we'll certainly look at improving (and probably better documenting) the existing CRUD API in DSLContext

This would probably meet all my needs.

Good point, I think that functionality isn't really available as transparently in DSLContext yet. It is more of a conscious choice, e.g. through: DSLContext.batchInsert(UpdatableRecord...)
This is fine. I generally know when I want to batch and if I switch from DAOs to DSLContext it will seem natural.

I would like to learn more about this part - it is the most controversial aspect of the DAO, because the generated POJOs are really closely coupled to the existing DAOs, conceptually. Has the rigid 1:1 mapping between "domain model" and relational model never really bothered you?

Not really, my side projects are small and simple for the most part. I use DAOs when the domain model and relational model map 1:1. If they don't I either use the DSL or sometimes run multiple queries using DAOs.

 If I have a User model that is backed by a user table and roles table I can fetch from both tables in parallel and join in memory. Sometimes if I know one of the tables has data that infrequently changes I just cache the whole data set in memory and join on that.

So, you use DAOs as a pragmatic "starter" or "base implementation" until they stop working for you?
 
Pretty much. It sounds like I can achieve the same thing easily with the DSL I will look into this more.

I'm not sure if this could have any dual licensing issues.

I wasn't sure if any of the suggestions would cause issues with your licensing model. I don't think it would.


 I will need to look through some more blog posts and see if I have fallen into the DAO rabbit hole :).

Lukas Eder

unread,
Mar 21, 2017, 4:32:45 AM3/21/17
to jooq...@googlegroups.com
Hi Bill

2017-03-20 23:06 GMT+01:00 Bill O'Neil <onei...@gmail.com>:
 You are probably right here I have been using DAOs for 3-4 years now thinking it was the right way to do the CRUD parts. I'd love if you can point me to some of the DSL methods you are referring to. Just took a quick look here https://www.jooq.org/doc/2.6/manual/sql-execution/crud-with-updatablerecords/simple-crud/ I guess I could just then map it to a POJO similar to how the DAOs do it unless there is an easier way.

Hmm, that link clearly indicates the problem here. The documentation is not good enough. I've added this point to the relevant issue:

And then, there will be some features that DAO can currently do and that don't have a corresponding method in DSLContext. That will be fixed along with the deprecation.
 
I would like to learn more about this part - it is the most controversial aspect of the DAO, because the generated POJOs are really closely coupled to the existing DAOs, conceptually. Has the rigid 1:1 mapping between "domain model" and relational model never really bothered you?

Not really, my side projects are small and simple for the most part. I use DAOs when the domain model and relational model map 1:1. If they don't I either use the DSL or sometimes run multiple queries using DAOs.

 If I have a User model that is backed by a user table and roles table I can fetch from both tables in parallel and join in memory. Sometimes if I know one of the tables has data that infrequently changes I just cache the whole data set in memory and join on that.

So, you use DAOs as a pragmatic "starter" or "base implementation" until they stop working for you?
 
Pretty much. It sounds like I can achieve the same thing easily with the DSL I will look into this more.

OK, thanks for your explanations, that's very interesting.

Your approach does seem to work fine for you, you've found a pragmatic balance between what works "for now", and how things should be done once models become more complex. I think this is what many experienced users of a framework will eventually do.

My goal here is to make it very easy and obvious for new users as well. The DAO "case" is a quite orthogonal way of doing things in jOOQ. Sure, it works for some needs, but I'd like jOOQ to be an API that *always* works in all cases. A user shouldn't have the cognitive overhead of thinking: "How do I best use jOOQ *in this case*".

Which again means: All the useful stuff in DAO should be moved to DSLContext where it fits the query model without being distracted by this 1:1 model mapping "philosophy".

I'm not sure if this could have any dual licensing issues.

I wasn't sure if any of the suggestions would cause issues with your licensing model. I don't think it would.

I see. No, what's published on GitHub and Maven Central is purely ASL 2.0 licensed, so there's absolutely no problem for anyone to fork that.

Of course, I'd prefer to find a solution that makes a fork unnecessary, because the solution should be much better than any fork could be.
 
I will need to look through some more blog posts and see if I have fallen into the DAO rabbit hole :).

I don't think you have. You've found a pragmatic solution, which is fine.

Sheldon D'Souza

unread,
Mar 21, 2017, 7:34:17 AM3/21/17
to jOOQ User Group
In our latest platform release we moved all our DAO code to DSLContext, as that gives us more flexibility to query only what we need.

Bill O'Neil

unread,
Mar 21, 2017, 8:26:14 AM3/21/17
to jooq...@googlegroups.com
@lukas Additional documentation / examples and an improved DSLContext sounds sufficient to deprecate the DAOs. +1

--
You received this message because you are subscribed to a topic in the Google Groups "jOOQ User Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/jooq-user/-LnkZtUTb3c/unsubscribe.
To unsubscribe from this group and all its topics, send an email to jooq-user+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Lukas Eder

unread,
Mar 21, 2017, 8:55:17 AM3/21/17
to jooq...@googlegroups.com
Thanks, Sheldon. Nice to know. That's exactly what I had in mind (plus add some more convenience to DSLContext if needed)

--
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 jooq-user+unsubscribe@googlegroups.com.

Lukas Eder

unread,
Mar 21, 2017, 8:55:35 AM3/21/17
to jooq...@googlegroups.com
Perfect, thanks Bill

--
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 jooq-user+unsubscribe@googlegroups.com.

Samir Faci

unread,
Mar 21, 2017, 9:21:00 AM3/21/17
to jooq...@googlegroups.com
I looked at pulling DAO into our auto-generation code and never really found much use to them.  Usually the way they fetch a record isn't really correct for my use case.  

ie.  A lot of times fetching by the PKey isn't what I'm looking for and at least when I was looking at this there wasn't really an obvious way of doing custom fetching behavior or really to expand upon it.

For simple Crud I'd much rather use the Record type.

record.refresh()
record.update()
record.store()

give me all I actually care about and the .fetchInto() combined with the auto-generated pojos are all I've used.

+1 on dropping support for it.  I don't object to DAO/Repository design pattern but I find that I usually need to write customized 
code rather then relying on auto-generated code for most of my use cases.



--
Samir Faci



--
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 jooq-user+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Thank you
Samir Faci

Lukas Eder

unread,
Mar 21, 2017, 9:33:21 AM3/21/17
to jooq...@googlegroups.com
Thanks a lot for the feedback, Samir

2017-03-21 14:20 GMT+01:00 Samir Faci <sa...@esamir.com>:
I looked at pulling DAO into our auto-generation code and never really found much use to them.  Usually the way they fetch a record isn't really correct for my use case.  

ie.  A lot of times fetching by the PKey isn't what I'm looking for and at least when I was looking at this there wasn't really an obvious way of doing custom fetching behavior or really to expand upon it.

For simple Crud I'd much rather use the Record type.

record.refresh()
record.update()
record.store()

That's exactly the point.
 
give me all I actually care about and the .fetchInto() combined with the auto-generated pojos are all I've used.

Oh, interesting bit about the auto-generated POJOs. So, they're sufficient for (most) of your use-cases? From the jOOQ perspective, they're closely linked to the DAOs (so, the deprecation *might* also touch on that POJO generation).
 
+1 on dropping support for it.  I don't object to DAO/Repository design pattern but I find that I usually need to write customized 
code rather then relying on auto-generated code for most of my use cases.

Exactly. The DAOs are too opinionated / simple for that customized implementation

Samir Faci

unread,
Mar 21, 2017, 9:39:56 AM3/21/17
to jooq...@googlegroups.com
I tend to use the auto-generated POJO for a simple entity.

so if I had a table named User for example, I might just use the auto-generated POJO as a Model returned by a REST service and just fetch into it.

That's definitely not sufficient when the tables get more complicated, but it's free code I don't have to write.  I didn't mean that POJO supported all my use cases, but my use of auto-gen code is usually Records + Pojos, once we get into custom code I can't get away from writing specialized behavior, nor would I expect Jooq to support my internal uses.



--
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 jooq-user+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Lukas Eder

unread,
Mar 21, 2017, 9:44:20 AM3/21/17
to jooq...@googlegroups.com
Interesting, it makes sense of course. On the other hand, would a Record as a model returned by a REST service be sufficient? Or does the fact that it is still "attached" bother you?

Samir Faci

unread,
Mar 21, 2017, 10:01:24 AM3/21/17
to jooq...@googlegroups.com
I think the Record type is a bit too complex for a simple model, which is why I prefer the Pojo.  I think I've tried using a record in the past and Jackson was serializing more data then was really needed to be returned.  

Also, the swagger introspection seems to puke out on me when I try to use the record data type.  I can look into it a bit more if you'd like, but mainly I think the object contains state and other attributes that aren't really necessary when all you want to convey is "Here's some data".




Bill O'Neil

unread,
Mar 21, 2017, 10:16:22 AM3/21/17
to jooq...@googlegroups.com
Samir's use is exactly how I use the generated POJOs.


Or does the fact that it is still "attached" bother you?

This bothers me a little but can be worked around. I ran into some complications doing something similar in a previous project. The issue we ran into was some values were lazily loaded and the model was passed into our JSON serializer AFTER the connection was closed causing exceptions. I'm not sure if the same issue would arise here but its nice working with plain POJOs and knowing it won't be an issue. Also as Samir mentioned it can mess with serializers.

That doesn't mean jOOQ should be responsible for such conveniences. However, if POJO generation was still supported you could also keep the RecordMapper generation to avoid reflection and do something like this.


UserPojo user = getPojo();

create.executeInsert(UserPojo.mapper().map(user));
// Alternate API?
create.executeInsert(user, UserPojo::mapper);

user = create.selectFrom(Tables.USER).where(Tables.USER.ID.eq(1L)).fetchOne(UserPojo::mapper);

RecordMappers could be in new classes instead of the DAO, as static fields / methods to the POJOs, or part of the generated Tables.

Just a thought.

You received this message because you are subscribed to a topic in the Google Groups "jOOQ User Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/jooq-user/-LnkZtUTb3c/unsubscribe.
To unsubscribe from this group and all its topics, send an email to jooq-user+unsubscribe@googlegroups.com.

Lukas Eder

unread,
Mar 21, 2017, 12:13:37 PM3/21/17
to jooq...@googlegroups.com
2017-03-21 15:01 GMT+01:00 Samir Faci <sa...@esamir.com>:
I think the Record type is a bit too complex for a simple model, which is why I prefer the Pojo.  I think I've tried using a record in the past and Jackson was serializing more data then was really needed to be returned.  

Oh, I see. Indeed, Jackson defines its serialisation rules through what's available via reflection, so type hierarchies don't really help here. On the other hand, jOOQ 3.10 will include Record.formatJSON(), so perhaps Jackson won't be necessary here?
 
Also, the swagger introspection seems to puke out on me when I try to use the record data type.  I can look into it a bit more if you'd like, but mainly I think the object contains state and other attributes that aren't really necessary when all you want to convey is "Here's some data".

Oh, interesting. Yes, I guess in that case, POJOs are the ideal solution.

Thanks for sharing!

Lukas Eder

unread,
Mar 21, 2017, 12:19:14 PM3/21/17
to jooq...@googlegroups.com
2017-03-21 15:16 GMT+01:00 Bill O'Neil <onei...@gmail.com>:
Samir's use is exactly how I use the generated POJOs.

Or does the fact that it is still "attached" bother you?

This bothers me a little but can be worked around. I ran into some complications doing something similar in a previous project. The issue we ran into was some values were lazily loaded and the model was passed into our JSON serializer AFTER the connection was closed causing exceptions. I'm not sure if the same issue would arise here but its nice working with plain POJOs and knowing it won't be an issue. Also as Samir mentioned it can mess with serializers.

Oh, interesting, so you extended the jOOQ records with lazy loading capabilities? :) OK, that wouldn't work then.
 
That doesn't mean jOOQ should be responsible for such conveniences. However, if POJO generation was still supported you could also keep the RecordMapper generation to avoid reflection and do something like this.


UserPojo user = getPojo();

create.executeInsert(UserPojo.mapper().map(user));
// Alternate API?
create.executeInsert(user, UserPojo::mapper);

user = create.selectFrom(Tables.USER).where(Tables.USER.ID.eq(1L)).fetchOne(UserPojo::mapper);

RecordMappers could be in new classes instead of the DAO, as static fields / methods to the POJOs, or part of the generated Tables.

Just a thought.

Yeah, indeed. Although again, I'm not sure if we'd just be moving an unnecessary feature elsewhere rather than scratching the actual itch. If the POJO is used almost uniquely as an intermediary for a serialisation library, then something's not right. Better solutions would include:

1. jOOQ implements better serialisation capabilities (e.g. jOOQ 3.10 Record.formatJSON())
2. A better intermediary is used (e.g. java.util.Map)
3. etc.?

I guess, this will need some further thought... But generated POJOs *and* RecordMappers seem to go the wrong direction, in my opinion... (I remember the discussion, though)

Bill O'Neil

unread,
Mar 21, 2017, 1:45:16 PM3/21/17
to jooq...@googlegroups.com
Oh, interesting, so you extended the jOOQ records with lazy loading capabilities? :) OK, that wouldn't work then.

It was a Scala / Slick project and it made some sense for that specific model. Most of the code did not operate that way :). The connection caused a few headaches although most of them just pointed out our bugs in the request lifecycle.

1. jOOQ implements better serialisation capabilities (e.g. jOOQ 3.10 Record.formatJSON())
2. A better intermediary is used (e.g. java.util.Map)
3. etc.?

This could work just as well. I personally like POJO generation but it probably wouldn't be much work for me to handle that myself if I really needed it.

I could see JSON having endless opinionated debates around snake case vs camel case, including or excluding nulls in the JSON, etc (Look at all of the Jackson configuration options). I think Record.formatJSON() will be extremely beneficial but might not solve the web service requirements. Users probably already have a preferred serializer configured with their preferences. Maybe an option would be to have contributors create 3rd party jooq-jackson, jooq-gson Record serializers?.

java.util.Map could be a good intermediary.

I'm not sure how this would work with something like Swagger but I believe it would solve the Jackson issues. As always bear with me on naming. Assuming we have a user table (id, email).

interface UserView {
    void setId(Long value);
    Long getId();
    void setEmail(String value);
    String getEmail();
}

// generated
public class UserRecord extends UpdatableRecordImpl<UserRecord> implements Record2<Long, String>, UserView {
    ...
    public UserView view() {
        detach(); // could or would this be a good / bad idea?
        return this;
    }
    ...
}

Or if you wanted records could extend interface Viewable<T> { T view() }. Obviously not a good name especially in regards to SQL.

I think most reflection should play nicely with this unless they are being overly aggressive and grabbing internal states. It gets rid of the intermediate objects and overhead creating them. Is effectively the same as a POJO. It would add a single method to each record and not alter the API other than that (maybe add some override annotations).

After writing I realize this will only work well for serializing and not deserializing unless there is a way to convert it back to a UserRecord which could require "attaching". I'll leave it in case it generates any ideas but currently I don't think its a viable solution.

Bill

--

Samir Faci

unread,
Mar 21, 2017, 2:25:59 PM3/21/17
to jooq...@googlegroups.com
inline....



On Tue, Mar 21, 2017 at 9:13 AM, Lukas Eder <lukas...@gmail.com> wrote:


2017-03-21 15:01 GMT+01:00 Samir Faci <sa...@esamir.com>:
I think the Record type is a bit too complex for a simple model, which is why I prefer the Pojo.  I think I've tried using a record in the past and Jackson was serializing more data then was really needed to be returned.  

Oh, I see. Indeed, Jackson defines its serialisation rules through what's available via reflection, so type hierarchies don't really help here. On the other hand, jOOQ 3.10 will include Record.formatJSON(), so perhaps Jackson won't be necessary here?

I'm sure the .formatJSON() would be handy.  though since swagger pukes out I'd have to define the return type as a String which removes most of the benefits of having a model class.  
 
 
Also, the swagger introspection seems to puke out on me when I try to use the record data type.  I can look into it a bit more if you'd like, but mainly I think the object contains state and other attributes that aren't really necessary when all you want to convey is "Here's some data".

Oh, interesting. Yes, I guess in that case, POJOs are the ideal solution.

Yup.  POJOs work great,  I've also extended the class at times, and added the swagger annotation and documentation required.  It's really handy, and the back and forth between records and pojos are very handy as well.


Anyways, this is sort of a tangent.  +1 on the DAO drop.
 

Thanks for sharing!

--
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 jooq-user+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

ben....@gmail.com

unread,
Mar 21, 2017, 4:11:44 PM3/21/17
to jOOQ User Group
I never found the DAOs and Pojos useful and disabled code generating them. In the past, I was also concerned that they'd promote an ORM world view, leading to requests for JPA, rather than embracing SQL.

I prefer to use DSLContext directly and never heard a complaint (only praise) from coworkers when they used jOOQ. I sometimes use the record types, but only for internal logic. Instead I use jsonSchema2Pojo (or in the past, protobuf) to generate my service types. The fetchInto() mapping is usually enough and when not the custom mapping is pleasant enough. I've often written custom repositories to extract the query logic from the services to simplify the code. But when the class is well factored, like a background job, I'm more inclined to embed the query rather than bloat a repository with one-off usages.

I can understand why some would want to use the Pojo codegen and return the results directly. To me that couples the service schema to the data schema, which may only match one-to-one early on. By having a service schema its easier to refactor and be less likely to break the external contract by mistake. The amount of work to write a schema, e.g. json schema, is trivial and it is nice to know exactly what the payload looks like and, optionally, run it through a validator.

These days I use a hybrid nosql / sql data model in Postgres, resulting in a high usage of json schema for generation the UI and dynamically configuring a backend processing pipeline. If I had started with DOAs then I'd have needed to move away regardless.

Lukas Eder

unread,
Mar 22, 2017, 6:36:00 AM3/22/17
to jooq...@googlegroups.com
2017-03-21 18:45 GMT+01:00 Bill O'Neil <onei...@gmail.com>:
I could see JSON having endless opinionated debates around snake case vs camel case, including or excluding nulls in the JSON, etc (Look at all of the Jackson configuration options).

Indeed! We're going that way, too :)

JSON, XML, and CSV import and export are very useful jOOQ features, so there's definitely room for improvement here. For instance, jOOQ 3.9 allows for both array-of-array [[1,2,3],[4,5,6],[7,8,9]] and array of object [{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}] result formats. jOOQ 3.10 further expands on this. We'll continue to take feature requests to see what else users want here.

... it's not like serialising a Result / Record to JSON is black magic. There aren't too many options for these data types.
 
I think Record.formatJSON() will be extremely beneficial but might not solve the web service requirements. Users probably already have a preferred serializer configured with their preferences. Maybe an option would be to have contributors create 3rd party jooq-jackson, jooq-gson Record serializers?.

One per JSON library? There are so many :)
I'm pretty sure that in the case of JSON, we'll be able to tackle all the hurdles of "customisation" eventually (if the serialisation is 1:1, otherwise, users will use RecordMappers anyway).
 
java.util.Map could be a good intermediary.

I'm not sure how this would work with something like Swagger but I believe it would solve the Jackson issues. As always bear with me on naming. Assuming we have a user table (id, email).

interface UserView {
    void setId(Long value);
    Long getId();
    void setEmail(String value);
    String getEmail();
}

// generated
public class UserRecord extends UpdatableRecordImpl<UserRecord> implements Record2<Long, String>, UserView {
    ...
    public UserView view() {
        detach(); // could or would this be a good / bad idea?
        return this;
    }
    ...
}

Or if you wanted records could extend interface Viewable<T> { T view() }. Obviously not a good name especially in regards to SQL.

We already have the IUserRecord interface generation, and the detach call could be achieved by using the Settings.attachRecords = false...
But I'm not sure if this is going in the right direction... I don't like the idea of encoding some implied knowledge of specific third party libraries into jOOQ.
 
I think most reflection should play nicely with this unless they are being overly aggressive and grabbing internal states. It gets rid of the intermediate objects and overhead creating them. Is effectively the same as a POJO. It would add a single method to each record and not alter the API other than that (maybe add some override annotations).

But in most cases, there's really no need for a nominal type (I know, Java is obsessed with nominal typing), but a Record is ultimately just a tuple with attribute names. Serialisation libraries should be able to serialise such types IMO. Map is one of the simplest encodings of a tuple in the JDK...
 
After writing I realize this will only work well for serializing and not deserializing unless there is a way to convert it back to a UserRecord which could require "attaching". I'll leave it in case it generates any ideas but currently I don't think its a viable solution.

You can auto-attach records after deserialisation by using RecordListener. It's possible with ExecuteListener as well. Or, you can use Record.from(Object) (back to reflection).

Anyway, my lesson here in the context of the deprecation discussion is: POJOs are sometimes a useful workaround for certain serialisation topics. If we invest more into serialisation, this use-case will be weakened eventually, and we might no longer need to generate POJOs - from this point of view. We'll probably see other points of view...

Cheers,
Lukas

Lukas Eder

unread,
Mar 22, 2017, 6:46:09 AM3/22/17
to jooq...@googlegroups.com
Hi Ben,

Thanks for sharing your thoughts!

2017-03-21 21:11 GMT+01:00 <ben....@gmail.com>:
I never found the DAOs and Pojos useful and disabled code generating them. In the past, I was also concerned that they'd promote an ORM world view, leading to requests for JPA, rather than embracing SQL.

Well... no, we won't :)

I prefer to use DSLContext directly and never heard a complaint (only praise) from coworkers when they used jOOQ. I sometimes use the record types, but only for internal logic. Instead I use jsonSchema2Pojo (or in the past, protobuf) to generate my service types.

Oh, interesting, I wasn't aware of this website / library. One would wish we had a more formal JSON binding API like JAXB with XJC, though... :) Then again, one would wish for a more formal JSON schema specification like XSD.

But oh well. That discussion is something entirely else...

The fetchInto() mapping is usually enough and when not the custom mapping is pleasant enough. I've often written custom repositories to extract the query logic from the services to simplify the code. But when the class is well factored, like a background job, I'm more inclined to embed the query rather than bloat a repository with one-off usages.

I can understand why some would want to use the Pojo codegen and return the results directly. To me that couples the service schema to the data schema, which may only match one-to-one early on. By having a service schema its easier to refactor and be less likely to break the external contract by mistake. The amount of work to write a schema, e.g. json schema, is trivial and it is nice to know exactly what the payload looks like and, optionally, run it through a validator.

These days I use a hybrid nosql / sql data model in Postgres, resulting in a high usage of json schema for generation the UI and dynamically configuring a backend processing pipeline. If I had started with DOAs then I'd have needed to move away regardless.

I think that last sentence wraps it up very nicely. Unlike a lot of other jOOQ features, this one is an "early prototype" one. Perhaps that's OK if some third-party library did it (e.g. a hypothetical github.com/use-jooq-for-trivial-apps), but not for the core library.

Thank you very much for your confirmation!
Lukas

Bill O'Neil

unread,
Mar 22, 2017, 8:45:58 AM3/22/17
to jooq...@googlegroups.com
JSON, XML, and CSV import and export are very useful jOOQ features, so there's definitely room for improvement here. For instance, jOOQ 3.9 allows for both array-of-array [[1,2,3],[4,5,6],[7,8,9]] and array of object [{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}] result formats. jOOQ 3.10 further expands on this. We'll continue to take feature requests to see what else users want here.

... it's not like serialising a Result / Record to JSON is black magic. There aren't too many options for these data types.

 Here are some options I have come across relatively frequently.
1. "snake_case" vs "camelCase" for field names
2. Including or excluding nulls / Optional.empty()
3. Formatting java.sql.Date, java.util.Date, java.time* as timestamps or Strings with configurable formatters.
4. Serializing an Enum as an Object when it has additional fields.

I only bring it up because web services that expose different formats can be extremely frustrating to work with. I do think you are right and if someone needs more control they will just use RecordMappers.


 Anyway, my lesson here in the context of the deprecation discussion is: POJOs are sometimes a useful workaround for certain serialisation topics. If we invest more into serialisation, this use-case will be weakened eventually, and we might no longer need to generate POJOs - from this point of view. We'll probably see other points of view...

Other potential non serialization issues.
1. Caching - Attached records might cause issues when caching the results from a query in memory. I think there are enough work arounds by either using RecordMappers or detached Records where it shouldn't be a concern.
2. Validation - The existing POJO generation offers adding the validation annotations. Although this might save time, I think validation is often more complex than just matching the table constraints so it's probably not very useful to keep.

Personally I would see myself using DSLContext directly with RecordMappers over using the Records directly, just personal preference. POJO generation is a convenience that jOOQ currently offers but that doesn't mean it belongs in jOOQ. I can auto generate POJOs in my IDE, or write my own generator. I don't see any reason why POJOs couldn't also be deprecated.

Bill

--
You received this message because you are subscribed to a topic in the Google Groups "jOOQ User Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/jooq-user/-LnkZtUTb3c/unsubscribe.
To unsubscribe from this group and all its topics, send an email to jooq-user+unsubscribe@googlegroups.com.

c.javier...@gmail.com

unread,
Mar 22, 2017, 5:28:27 PM3/22/17
to jOOQ User Group
Hi

In my case, the generation of the DAOs that brings me jOOQ was one of the main decision to choose it instead of QueryDSL for example.
When you work in a medium/big project that manages information stored in relational database, where is the "best ubication" to include
the queries? the services layer? my "own specific" layer? in that case, you will see how the size of the classes included in this layer will
grow dramatically.

And it is a very bad design to spread the queries among several files without a strict criteria, for example, include a sql to get the list of users
in the RoleService or something like that.

jOOQ offers the possibility of generate DAOs or not, every developer can choose it in the XML configuration file. So if you prefer to use
DSLContext directly or develop your own DAO, then go ahead. Currently, jOOQ allow you it.

Unfortunately, not always I can write all required test (due to deadlines and similar issues) but when I can do it, the DAO layer offers me the
possibility to test only this layer and verify "all required queries" by my project using a database used for testing purpose. And when I want
to test "upper layers" like services or controllers for example, I only need to deal with the "specific business logic" added by this layer.


Best regards
Celestino Reyes

Lukas Eder

unread,
Mar 29, 2017, 6:00:13 AM3/29/17
to jooq...@googlegroups.com
Hi Bill,

Thanks a lot for the additional info!

2017-03-22 13:45 GMT+01:00 Bill O'Neil <onei...@gmail.com>:
JSON, XML, and CSV import and export are very useful jOOQ features, so there's definitely room for improvement here. For instance, jOOQ 3.9 allows for both array-of-array [[1,2,3],[4,5,6],[7,8,9]] and array of object [{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}] result formats. jOOQ 3.10 further expands on this. We'll continue to take feature requests to see what else users want here.

... it's not like serialising a Result / Record to JSON is black magic. There aren't too many options for these data types.

 Here are some options I have come across relatively frequently.
1. "snake_case" vs "camelCase" for field names

Yes, that eternal debate :) There are feature requests for that on the roadmap (even for RecordMapper)
 
2. Including or excluding nulls / Optional.empty()

Null encoding is partially configurable in other serialisers, but I hadn't thought of excluding null attributes in JSON. Will definitely implement that:
 
3. Formatting java.sql.Date, java.util.Date, java.time* as timestamps or Strings with configurable formatters.
4. Serializing an Enum as an Object when it has additional fields.

Absolutely. These will be part of https://github.com/jOOQ/jOOQ/issues/5673. We already have the appropriate type for this: Converter. We could register additional Converter<T, String> types, which implement serialisation (and optionally: deserialisation) logic for each column and/or data type. Deserialisation will be used in the Loader API, which can read the JSON / CSV / XML content.
 
I only bring it up because web services that expose different formats can be extremely frustrating to work with. I do think you are right and if someone needs more control they will just use RecordMappers.

I can feel the pain. Fun starts when something that is modelled as one value in one system needs to be split into two values in another system, and vice versa :) At some point, out-of-the-box functionality always breaks. But we can probably get quite far nonetheless.
 
 Anyway, my lesson here in the context of the deprecation discussion is: POJOs are sometimes a useful workaround for certain serialisation topics. If we invest more into serialisation, this use-case will be weakened eventually, and we might no longer need to generate POJOs - from this point of view. We'll probably see other points of view...

Other potential non serialization issues.
2. Validation - The existing POJO generation offers adding the validation annotations. Although this might save time, I think validation is often more complex than just matching the table constraints so it's probably not very useful to keep.

Yes, I'm personally a bit disappointed by these features. I think the Hibernate people really pushed this in the recent past, but it's too simplistic, and probably not worth pursuing in jOOQ. Will definitely put this also on the deprecation candidate list.

Thanks again for all your feedback!
Lukas

Lukas Eder

unread,
Mar 29, 2017, 6:09:43 AM3/29/17
to jooq...@googlegroups.com
Thank you very much for your feedback, Celestino.

Interesting, I would not have thought that among all features, the DAOs would be what sets jOOQ apart from QueryDSL :) There seem to be much more interesting advantages (in my opinion), but great to know that it was the DAOs for you.

Do not get me wrong, I do not at all oppose the idea of having a DAO "layer" or concept (or more generically, a Repository pattern) in an application. I think that's a great idea, and for some people, generating them in a 1:1 fashion per table is also very helpful.

The idea of deprecating the current jOOQ DAOs stems from the fact that they are much too opinionated for the jOOQ core library, and they would be rather easy to maintain outside of jOOQ. In fact, they hardly do anything (yet for many people, they're doing it wrong), so the problem for us is not the technical maintenance, but the support effort that goes into them.

Nevertheless, I do appreciate the need of some jOOQ users to have generated DAOs, and I think we might be able to cover your needs in a version 4.0 of jOOQ by implementing a better, more extensible code generator that makes generating custom classes from your database really easy (it's already easy with the existing generator, but it wasn't designed for extension).

I will further comment inline.

2017-03-22 22:28 GMT+01:00 <c.javier...@gmail.com>:
Hi

In my case, the generation of the DAOs that brings me jOOQ was one of the main decision to choose it instead of QueryDSL for example.
When you work in a medium/big project that manages information stored in relational database, where is the "best ubication" to include
the queries? the services layer? my "own specific" layer? in that case, you will see how the size of the classes included in this layer will
grow dramatically.

And it is a very bad design to spread the queries among several files without a strict criteria, for example, include a sql to get the list of users
in the RoleService or something like that.

I absolutely agree. But again, most of jOOQ is really unopinionated about these things, whereas DAOs are highly opinionated. They just happen to work well enough for you, but not for many others.
 
jOOQ offers the possibility of generate DAOs or not, every developer can choose it in the XML configuration file. So if you prefer to use
DSLContext directly or develop your own DAO, then go ahead. Currently, jOOQ allow you it.

Unfortunately, not always I can write all required test (due to deadlines and similar issues) but when I can do it, the DAO layer offers me the
possibility to test only this layer and verify "all required queries" by my project using a database used for testing purpose. And when I want
to test "upper layers" like services or controllers for example, I only need to deal with the "specific business logic" added by this layer.

Absolutely. Again, I do not want to argue the benefits of implementing a layered architecture. This is just about the hard-wired opinion that goes into having a DAO layer in what is otherwise only a SQL API.

Thanks again for your feedback. It's great to hear that you've found DAOs so useful, and if we go on deprecating them, we'll certainly think of your use-case of having an easily generateable DAO layer when improving the code generator.

Roger Thomas

unread,
Mar 29, 2017, 4:16:02 PM3/29/17
to jOOQ User Group
Just to add a little to the thread,

The last 2 times I have been involved with projects that needed Java based applications to be coded and developers hired I put forward jOOQ as the database layer. In the DevOps/SysOps role I had, it was easy to justify jOOQ as the databases had to be open relational structures. The developers on the other hand found it yet another thing they have to get up to speed on, so having it generate DAOs allows them to get up to speed over time as they started with the DAOs. The hiring process for both companies had no real focus on jOOQ skills as there were more important CV requirements, I think that the only DB related requirement was for possible hires to have an understanding relational database and the limitations of Hibernate :)

jklingsporn

unread,
Mar 30, 2017, 4:42:45 AM3/30/17
to jOOQ User Group
Nevertheless, I do appreciate the need of some jOOQ users to have generated DAOs, and I think we might be able to cover your needs in a version 4.0 of jOOQ by implementing a better, more extensible code generator that makes generating custom classes from your database really easy (it's already easy with the existing generator, but it wasn't designed for extension).

Very important sentence in this discussion, because the powerful code-generation was (besides the typesafety in queries) the second driver that pulled me to jOOQ (DAOs included ofc). So if we have this, I'm fine with deprecating DAOs (although I'm using them). Like others already wrote: they give you CRUD for free in many usecases and thus speed up development. 

Let me jump to the initial post:

This list has debated DAOs time and again in the past. There had been many feature requests, extension requests, and people criticising jOOQ's use of the Java final keyword as it prevents the application of the open-closed principle, at least in those people's understanding. My understanding is a bit different: https://blog.jooq.org/2017/03/20/the-open-closed-principle-is-often-not-what-you-think-it-is

In fact, people bumping into jOOQ's usage of final in this area simply shows that the design (or the vision) is insufficient in this particular case. Otherwise, there would be no desire to "hack" additional behaviour into the existing API through what I believe to be a highly overrated tool of object orientation: Concrete class overriding.
 
If there is a lot of discussion around that topic, doesn't that also mean, that plenty people are using it? So it could also be a discussion about how to implement a better DAO-extensibility. 

Lukas Eder

unread,
Apr 3, 2017, 9:13:37 AM4/3/17
to jooq...@googlegroups.com
Hi Roger,

That's very interesting. It's precisely the reason why I added DAO support in the first place: For people who do not want to learn the library, so they have a quick win. Unfortunately, that's how Hibernate got most of its hate - people didn't want to learn the library but have a quick mapping win. Then came the trouble.

So, eventually, the hiring process might require to have an understanding of relational databases, the limitations of Hibernate, and the caveats of jOOQ's DAOs :)

Anyway, I'll keep this in mind. We certainly want to make it easy for users wanting to work with DAO/Repositories, but I'd like to make that a code generation add-on feature (or even external plugin), rather than a core library feature

Thanks,
Lukas

2017-03-29 21:16 GMT+01:00 Roger Thomas <rithom...@gmail.com>:
Just to add a little to the thread,

The last 2 times I have been involved with projects that needed Java based applications to be coded and developers hired I put forward jOOQ as the database layer. In the DevOps/SysOps role I had, it was easy to justify jOOQ as the databases had to be open relational structures. The developers on the other hand found it yet another thing they have to get up to speed on, so having it generate DAOs allows them to get up to speed over time as they started with the DAOs. The hiring process for both companies had no real focus on jOOQ skills as there were more important CV requirements, I think that the only DB related requirement was for possible hires to have an understanding relational database and the limitations of Hibernate :)

--
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 jooq-user+unsubscribe@googlegroups.com.

Lukas Eder

unread,
Apr 3, 2017, 9:25:41 AM4/3/17
to jooq...@googlegroups.com
Thanks very much for your comment, Jens

2017-03-30 9:42 GMT+01:00 jklingsporn <jens.kl...@gmail.com>:
Nevertheless, I do appreciate the need of some jOOQ users to have generated DAOs, and I think we might be able to cover your needs in a version 4.0 of jOOQ by implementing a better, more extensible code generator that makes generating custom classes from your database really easy (it's already easy with the existing generator, but it wasn't designed for extension).

Very important sentence in this discussion, because the powerful code-generation was (besides the typesafety in queries) the second driver that pulled me to jOOQ (DAOs included ofc). So if we have this, I'm fine with deprecating DAOs (although I'm using them). Like others already wrote: they give you CRUD for free in many usecases and thus speed up development.

Excellent, so this is clearly understood now (and I'm very glad I asked). While most people agree that DAOs aren't useful in complex applications, they do help speeding up things in simple ones (at least for some people).

We'll keep this in mind, and probably postpone deprecation until the new code generator is properly laid out.
 
 Let me jump to the initial post:

This list has debated DAOs time and again in the past. There had been many feature requests, extension requests, and people criticising jOOQ's use of the Java final keyword as it prevents the application of the open-closed principle, at least in those people's understanding. My understanding is a bit different: https://blog.jooq.org/2017/03/20/the-open-closed-principle-is-often-not-what-you-think-it-is

In fact, people bumping into jOOQ's usage of final in this area simply shows that the design (or the vision) is insufficient in this particular case. Otherwise, there would be no desire to "hack" additional behaviour into the existing API through what I believe to be a highly overrated tool of object orientation: Concrete class overriding.
 
If there is a lot of discussion around that topic, doesn't that also mean, that plenty people are using it? So it could also be a discussion about how to implement a better DAO-extensibility. 

Yes, plenty of people are using it, but not necessarily because they like it. More often, they have (or had) expectations (explicit or implicit) towards the feature, and then spent a lot of time figuring out that it's incomplete.

There are now two options:

1. "Improving" DAOs (and I feel this is almost impossible, because it'll always be implementing only one opinion out of many)
2. Removing DAOs and leaving the topic to others.

Regarding bullet 1): Think of Spring Data's Repositories, which have features like translating english method names to SQL queries. That's nuts (in my opinion). Sure we can save a couple of seconds writing the actual SQL query, but we're still writing a query, and we're writing it in a rather mediocre "language" that will not be good enough sooner than later.

Moreover, that "language" will probably be an important reason for performance problems, as you cannot really tune those "queries" (e.g. how to avoid SELECT *). In fact, such a "language" is proof that some sort of query specification (which is likely storage specific) will inevitably leak outside of the repository, which breaks the very purpose of a repository. To be storage agnostic. The same is true for DAOs.

See, that's just my opinion. Yours may vary and I won't get in the way of DAOs (or repositories) in jOOQ client code. But I don't like jOOQ to be an opinionated framework, because that means, people might be less inclined to use it once they disagree with the opinion (because they waste time with jOOQ rather than saving time with jOOQ).

Bill O'Neil

unread,
Dec 9, 2017, 12:46:42 PM12/9/17
to jOOQ User Group
I have been messing around creating my own Dao class for when Dao's become deprecated. This is what I came up with which I think can be used with or without codegen.

public class TableCrud<Rec extends UpdatableRecord<Rec>, T, Table extends TableImpl<Rec>> {
    private final Table table;
    private final RecordMapper<Record, T> mapper;
    private final RecordUnmapper<T, Rec> unmapper;
    private final Supplier<DSLContext> configSupplier;
    public TableCrud(Table table,
                     RecordMapper<Rec, T> mapper,
                     RecordUnmapper<T, Rec> unmapper,
                     Supplier<DSLContext> configSupplier) {
        super();
        this.table = table;
        this.mapper = (RecordMapper<Record, T>) mapper;
        this.unmapper = unmapper;
        this.configSupplier = configSupplier;
    }

    public T insertReturning(T obj) {
        Rec rec = records(Collections.singletonList(obj), false).get(0);
        rec.insert();
        return rec.map(mapper);
    }

    public void insert(T obj) {
        insert(Collections.singletonList(obj));
    }

    @SuppressWarnings("unchecked")
    public void insert(T... objects) {
        insert(Arrays.asList(objects));
    }

    public void insert(Collection<T> objects) {
        // Execute a batch INSERT
        if (objects.size() > 1) {
            configSupplier.get().batchInsert(records(objects, false)).execute();
        }

        // Execute a regular INSERT
        else if (objects.size() == 1) {
            records(objects, false).get(0).insert();
        }
    }

    public void update(T obj) {
        update(Collections.singletonList(obj));
    }

    @SuppressWarnings("unchecked")
    public void update(T... objects) {
        update(Arrays.asList(objects));
    }

    public void update(Collection<T> objects) {
        // Execute a batch UPDATE
        if (objects.size() > 1) {
            configSupplier.get().batchUpdate(records(objects, false)).execute();
        }

        // Execute a regular UPDATE
        else if (objects.size() == 1) {
            records(objects, false).get(0).update();
        }
    }

    public void delete(T obj) {
        delete(Collections.singletonList(obj));
    }

    @SuppressWarnings("unchecked")
    public void delete(T... objects) {
        delete(Arrays.asList(objects));
    }

    public void delete(Collection<T> objects) {
        // Execute a batch DELETE
        if (objects.size() > 1) {
            configSupplier.get().batchDelete(records(objects, false)).execute();
        }

        // Execute a regular DELETE
        else if (objects.size() == 1) {
            records(objects, false).get(0).delete();
        }
    }

    public T findOne(Function<Table, Condition> func) {
        return configSupplier.get().fetchOne(table, func.apply(table)).map(mapper);
    }

    public List<T> find(Function<Table, Condition> func) {
        return configSupplier.get().fetch(table, func.apply(table)).map(mapper);
    }

    public int deleteWhere(Function<Table, Condition> func) {
        return configSupplier.get().deleteFrom(table).where(func.apply(table)).execute();
    }

    // Copy pasted from jOOQ's DAOImpl.java
    private /* non-final */ Field<?>[] pk() {
        UniqueKey<?> key = table.getPrimaryKey();
        return key == null ? null : key.getFieldsArray();
    }

    // Copy pasted from jOOQ's DAOImpl.java
    private /* non-final */ List<Rec> records(Collection<T> objects, boolean forUpdate) {
        List<Rec> result = new ArrayList<>();
        Field<?>[] pk = pk();

        for (T object : objects) {
            Rec record = unmapper.unmap(object);
            record.attach(configSupplier.get().configuration());

            if (forUpdate && pk != null)
                for (Field<?> field : pk)
                    record.changed(field, false);

            resetChangedOnNotNull(record);
            result.add(record);
        }

        return result;
    }

    // Copy pasted from jOOQ's Tools.java
    /**
     * [#2700] [#3582] If a POJO attribute is NULL, but the column is NOT NULL
     * then we should let the database apply DEFAULT values
     */
    private static final void resetChangedOnNotNull(Record record) {
        int size = record.size();

        for (int i = 0; i < size; i++)
            if (record.get(i) == null)
                if (!record.field(i).getDataType().nullable())
                    record.changed(i, false);
    }
}

public final class Organizations {
    private static final TableCrud<OrganizationRecord, Organization, com.stubbornjava.access.server.generated.tables.Organization> crud =
            new TableCrud<>(Tables.ORGANIZATION,
                            Organizations::mapper,
                            Organizations::unmapper,
                            ConnectionPools::connection);

    public static Organization create(String name) {
        Organization org = Organization.builder()
            .name(name)
            .dateCreated(LocalDate.now())
            .tsCreated(LocalDateTime.now())
            .enabled(true)
            .build();
        return crud.insertReturning(org);
    }

    public static void update(Organization org) {
        crud.update(org);
    }

    public static Organization findById(Long organizationId) {
        return crud.findOne(t -> t.ORGANIZATION_ID.eq(organizationId));
    }

    public static Organization findByName(String name) {
        return crud.findOne(t -> t.NAME.eq(name));
    }

    public static List<Organization> findByCreatedDate(LocalDate start, LocalDate end) {
        return crud.find(t -> t.DATE_CREATED.between(start, end));
    }

    private static final OrganizationRecord unmapper(Organization org) {
        return new OrganizationRecord(
            org.getOrganizationId(),
            org.getName(),
            org.getEnabled(),
            org.getDateCreated(),
            org.getTsCreated(),
            org.getRecVersion());
    }

    public static final Organization mapper(OrganizationRecord record) {
        return new Organization(
                record.getOrganizationId(),
                record.getName(),
                record.getEnabled(),
                record.getDateCreated(),
                record.getTsCreated(),
                record.getRecVersion());
    }
}

public static void main(String[] args) {
    ConnectionPools.transaction(() -> {
        Organization org = Organizations.create("test");
        org = org.toBuilder().enabled(false).build();
        Organizations.update(org);

        org = Organizations.findById(1L);
    });
}


This generic class can probably be improved a bit but I think it covers most of the use cases without code generation including auto batching. Code generation could further improve it if you wanted specific method names per Dao like the existing code gen.

Personally I don't know if this fits in jOOQ because as you said it might be a bit opinionated. It also won't be as easy to back port to Java 6/7. However it shows it's not too difficult to roll your own Dao class which could easily be created as 3rd party libraries that wrap jOOQ which would be a much better location for opinionated frameworks.

If you care about more complex queries we could add additional methods that allow you to utilize more of the jOOQ DSL and still handle mapping / unmapping. At that level I might prefer just to drop into the jOOQ DSL directly and only use the crud class for simple operations.

It would be nice to be able to generate the POJOs and mappers / unmappers but again that could still live outside of jOOQ.

Had to copy paste some internal jOOQ helpers.
Reply all
Reply to author
Forward
0 new messages