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.
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).
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
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...)
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?
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.
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.
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 :).
--
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.
--
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.
--
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.
--
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.
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 customizedcode rather then relying on auto-generated code for most of my use cases.
--
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.
Or does the fact that it is still "attached" bother you?
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.
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".
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.
Oh, interesting, so you extended the jOOQ records with lazy loading capabilities? :) OK, that wouldn't work then.
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.?
--
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!
--
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.
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();}// generatedpublic 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.
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.
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.
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...
--
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.
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.
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.
HiIn 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 includethe queries? the services layer? my "own specific" layer? in that case, you will see how the size of the classes included in this layer willgrow 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 usersin 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 useDSLContext 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 thepossibility to test only this layer and verify "all required queries" by my project using a database used for testing purpose. And when I wantto test "upper layers" like services or controllers for example, I only need to deal with the "specific business logic" added by this layer.
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).
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-isIn 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.
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.
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-isIn 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.
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); });}