How to create jooq custom record? - java

How can I create a custom jOOQ Record from existing two others, i.e. merge properties from two existing Record Objects.
For example:
CustomerRecord(id, name, surname),
ProductRecord(id, customer_id, description)
SELECT * FROM Customer JOIN Product ON Customer.id = Product.customer_id;
After such a query I'll get RecordImpl object and I want to have custom one with access to field properties from both tables.

There are several options to achieve what you're looking for.
Use views
One of them would be to simply create a view:
-- Potentially disambiguate the ID (and other) columns
CREATE VIEW CustomerAndProducts AS
SELECT * FROM Customer JOIN Product ON Customer.id = Product.customer_id;
The code generator would then pick up that view and generate a CustomerAndProductsRecord for you
Use your own CustomRecord
You can create your own TableRecord subtype by extending org.jooq.impl.CustomRecord as is documented here:
https://www.jooq.org/doc/latest/manual/sql-building/queryparts/custom-queryparts
They work almost like ordinary TableRecord types and can have your own set of getters / setters
Use any class you like
You don't have to use records. jOOQ can fetch your results into any kind of class (which may happen to be records, see above) using the following code:
List<MyClass> list =
DSL.using(configuration)
.select()
.from(CUSTOMER)
.join(PRODUCT)
.on(CUSTOMER.ID.eq(PRODUCT.CUSTOMER_ID))
.fetchInto(MyClass.class);
In the above example, the DefaultRecordMapper is applied to map between jOOQ's records and your class. See its Javadoc for details.

Related

Use pagination on query on multiple indices in hibernate search

we are implementing a global search that we can use to query all entities in our application, let's say cars, books and movies. Those entities do not share common fields, cars have a manufacturer, books have an author and movies have a director, for example.
In a global search field I'd like to search for all my entities with one query to be able to use pagination. Two aproaches come to my mind when thinking about how to solve this:
Query one index after another and manually merge the result. This means that I have to implement pagination myself.
Add common fields to each item like name and creator (or create an interface as shown here Single return type in Hibernate Search).In this case I can only search for fields in my global search that I map to the common fields.
My question is now: Is there a third (better) way? How would you suggest to implement such a global search on multiple indices?
Query one index after another and manually merge the result. This means that I have to implement pagination myself.
I definitely wouldn't do that, as this will perform very poorly, especially with deep pagination (page 40, etc.).
Add common fields to each item like name and creator (or create an interface as shown here Single return type in Hibernate Search).In this case I can only search for fields in my global search that I map to the common fields.
That's the way. You don't even need a common interface since you can just target multiple fields in the same predicate. The common interface would only help to target all relevant types: you can call .search(MyInterface.class) instead of .search(Arrays.asList(Car.class, Book.class, Movie.class)).
You can still apply predicates to fields that are specific to each type; it's just that fields that appear in more than one type must be consistent (same type, etc.). Also, obviously, if you require that the "manufacturer" (and no other field) matches "james", Books and Movies won't match anymore, since they don't have a manufacturer.
Have you tried it? For example, this should work just fine as long as manufacturer, author and director are all text fields with the same analyzer:
SearchResult<Object> result = searchSession.search( Arrays.asList(
Car.class, Book.class, Movie.class
) )
.where( f -> f.simpleQueryString()
.fields( "manufacturer", "author", "director" )
.matching( "james" ) )
.fetch( 20 );
List<Object> hits = result.hits(); // Result is a mix of Car, Book and Movie.
One approach would be to create a SQL view (SearchEntry?) that combines all of the tables you want to search. This allows you to alias your different column names. It won't be very good for performance but you could also just create one big field that is a concatenation of different searchable fields. Finally, include a "type" field that you tie back to your entity.
Now you can query everything in one go and use the type/id to tie back to a specific entity that the "search" data was initially pulled from.

Mapping derived columns to POJOs with RecordMapper in JOOQ

i have a table tickets where i insert ticket and have a field createdBy which stores the UserId Integer of the creator of that record. During fetching I join with users table and concat firstname and last name and my DTO has field createdBy of the concatenated name of creator. How can i map the derived field? this is my reference https://www.jooq.org/doc/3.13/manual/sql-execution/fetching/pojos/ and I cant seem to find such a scenario provided
the issue is not the join. the issue is mapping the string createdBy derived after the join whereas in the record class generated by jooq is an Integer because in the database table i store the userId.
List<MyTickets> mytickets = create.select(....FIELDS).from(TICKETS_).fetch().into(MyTickets.class);
#Override
public Field<Integer> field9() {
return Tickets.TICKETS_.CREATEDBY;
}
In my answer, I will assume that your computed column is going to be called CREATED_BY_NAME, not CREATED_BY, which is a name that's already taken, and to avoid confusion.
If this is something you do frequently, you have a few options that could be interesting to you:
Use views to generate this alternative CREATED_BY_NAME column. A lot of databases can insert into / update views as well, so you won't have a big penalty in using views to replace your tables. To your client logic, the origin of this column will be transparent. If you want to work with UpdatableRecord, you will have to tell jOOQ's code generator what the view's underlying primary key is using the synthetic primary key flag.
Similar to the above views, you could use computed columns on your tables, using the GENERATED ALWAYS AS ... syntax (or whatever your dialect uses for the syntax). Not all dialects support this, but it is a nice feature that turns tables into views without the extra view object.
If you want to keep computing this column manually in your jOOQ code, you could either write your own DTO / POJO objects, or extend the code generator with a custom code section, where you generate the relevant attribute / getter / setter. This approach only works for mutable POJOs, as you cannot modify the constructor of an immutable POJO.
You can also specify a base class for all of your affected POJOs and inject that base class using a generator strategy (programmatic or configurative). The base class could then implement all the getters / setters for columns like CREATED_BY_NAME.
You can also use structural typing instead. You don't have to map all the columns into your POJO. You could also map some columns into your generated POJO (excluding CREATED_BY_NAME) and map the CREATED_BY_NAME column separately. Just keep a reference to your jOOQ Result and/or Record, and perform several map / intoXYZ() calls on it.

Filter a JOOQ generated table with a condition while maintaining type

I have generated a number of JOOQ classes from my database. I want to easily filter my tables by customer while maintaining the strong type of my tables.
This is what I want to be able to do:
// Generated class books
JBooks books = JBooks.BOOKS;
// get ownershipCheck (this could be more complicated, possibly joining multiple tables)
Condition ownershipCheck = books.customer().ID.eq(currentCustomer);
// desired output that I can do further operations on
JBooks filteredBooks = selectFrom(books).where(ownershipCheck).asTable();
// a bunch of random operations using the functionality from JBooks
db.select(filteredBooks.AUTHOR, filteredBooks.PUBLISH_DATE, ...etc)
Unfortunately, I can't do this. I get a Table<JBooksRecord> instead and I see no way to cast my new Table to JBooks
This is being worked on through:
#8012 "Override Table.where(Condition) methods in generated tables"
#1969 "Add support for views expressed in jOOQ"
In short, a table can accept a predicate and the result is a modified table of the same table type, exposing the same type safe column expressions. In generated SQL, this can either produce a derived table or be inlined into the calling SQL statement.
As of jOOQ 3.11, these features are not yet available.

Custom POJO with Left Join in jooQ

I'm doing a left join of Table A and Table B and trying to fetch the results into a custom POJO which has fields from both Table A and Table B as follows:
List<MyCustomPojo> res = create.select()
.from(TABLE_A)
.leftJoin(TABLE_B)
.on(TABLE_A.MY_CODE.eq(TABLE_B.MY_CODE))
.fetchInto(MyCustomPojo.class);
It works fine for all the fields except for the field myCode the one on which these two tables are joined. For me the values for myCode were picked up from the right table, Table B, which is NULL for all of those records in Table A that do not have a corresponding entry in Table B. I would like to know how jooQ decides which field to map to POJO and if this behavior is documented anywhere.
My goal is to fetch all the fields from Table A and Table B into the custom POJO such that myCode is picked up from the left table. I would appreciate your advice on the right way to achieve it.
The default behaviour of ResultQuery.fetchInto(Class) (and most other into(Class) methods) is specified in DefaultRecordMapper. It can be overridden globally by providing a custom RecordMapperProvider in your Configuration.
In your particular case, DefaultRecordMapper will map all values from your records in field order. If there's a column that appears twice, it will be mapped twice, meaning that the second value will persist in your resulting object. There are two easy workarounds:
Don't select the "wrong" myCode. This is really the most robust solution
List<MyCustomPojo> res = create
.select(TABLE_A.fields())
.select(/* all fields in TABLE_B except the ones you don't want */)
.from(TABLE_A)
.leftJoin(TABLE_B)
.on(TABLE_A.MY_CODE.eq(TABLE_B.MY_CODE))
.fetchInto(MyCustomPojo.class);
Use RIGHT JOIN instead:
Perhaps a bit of a hack, but this will quickly reverse the table order in your SELECT statement.
List<MyCustomPojo> res = create
.select()
.from(TABLE_B)
.rightJoin(TABLE_A)
.on(TABLE_A.MY_CODE.eq(TABLE_B.MY_CODE))
.fetchInto(MyCustomPojo.class);
Finally a use-case for RIGHT JOIN :)
Note, this is the only solution that will also prevent wrong values for other columns that "accidentally" share the same name.
Add the "correct" myCode field once more.
Another hack, but it will work around the issue you're experiencing:
List<MyCustomPojo> res = create
.select(TABLE_A.fields())
.select(TABLE_B.fields())
.select(TABLE_A.MY_CODE)
.from(TABLE_A)
.leftJoin(TABLE_B)
.on(TABLE_A.MY_CODE.eq(TABLE_B.MY_CODE))
.fetchInto(MyCustomPojo.class);

Hibernate aggregates + Spring MVC

I am using Hibernate with my Spring MVC project.
Lets say my model has 2 objects each linked to Oracle tables, respectively USERS (USERID, NAME) and USERMONEYDATA (CATEGORY, USERID, AMOUNT)
My users can add , edit and remove rows in USERMONEYDATA, that belongs to them of course.
Now, I want to have a view that aggregates those data.
Using oracle, I made a simple view to get the total amount per user and category :
select userid, category, sum(amount)
from USERS a inner join USERMONEYDATA b on a.USERID = b.USERID
group by userid, category
But what is the best way to use it? should I create a new MODEL object specifically for this view ?
Should I aggregate directly in Hibernate ? But if yes, how do I display the results if I dont have a specific POJO object to map it to ?
thanks
If User and UserMoneyData are mapped as Hibernate entities, it's a natural choice to run aggregate queries in Hibernate.
By default, HQL or JPQL SELECT clause with multiple paths produces tuples in the form of Object[]. Since this kind of data is read-only, you may display these arrays directly, without converting them to objects.
Another option is to create a POJO for representing results of the query and use a constructor syntax SELECT NEW MoneyReport(u.userId, d.category, SUM(d.amount)) .... These POJOs don't need to be entities.
See also:
Chapter 15. HQL: The Hibernate Query Language

Categories