JPA CriteriaQuery with ListJoin - java

I'm working with JPA CriteriaBuilder, CriteriaQuery etc. and static metamodels for typesafe queries, like here for example: click.
I have 3 tables: Client, Package, Vegetable.
Every client has 1 or more packages, and those packages contain multiple vegetables.
What I have now:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Client> query = builder.createQuery(Client.class);
Root<Client> root = query.from(Client.class);
ListJoin<Package, Vegetable> join = root.join(Client_.packages).join(Package_.vegetables);
TypedQuery<Client> typedQuery = entityManager.createQuery(query);
return typedQuery.getResultList();
The ListJoin I added recently. Point is what Hibernate does: generates the whole select for all fields in Client class from the Client table inner joined with Package and Vegetable, but it doesn't actually selects those fields from joined tables. It gets every package by ID and then every vegetable by ID, thus doing n+1 selects.
Without the ListJoin it doesn't inner join those tables, but I'm working on it right now so I added those joins. Now I want to select all the fields from those classes so I get whole object hierarchy with 1 select. I tried doing something with projections like in the link I gave in Projecting the result chaper, but I have no idea how to connect that with ListJoin.
Is that even possible in this situation? When I run this query on database (with manually added all the fields from joined tables) it works fine, but would Hibernate handle that? And if so - how to project the result so it selects all the fields from 3 tables joined together and constructs whole object hierarchy, all with 1 select?
//Edit: managed to retrieve all packages with a single query, but going further raises exception:
Root<Client> root = query.from(Root.class);
ListJoin<Client, Package> join = root.join(Client_.packages);
ListJoin<Package, Vegetable> secondJoin = join.join(Package_.vegetables);
root.fetch(Client_.packages);
Naturally, I tried to add: join.fetch(Package_.vegetables); but it raises the org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list, no idea what is that.
As to the latest comment: gonna try that now.
//Edit2: I added 2 fetches (couldn't cast them to Join like in that answer, compiler errors):
Fetch<Client, Package> join = root.fetch(Client_.packages);
Fetch<Package, Vegetable> secondJoin = join.fetch(Package_.vegetables);
It raises org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags, known error I guess so at least got something to search for.
//Edit3: Changed it both to Sets and it works, thanks, couldn't have done it without the Fetch instead of Join suggestion, seems kinda unintuitive to me.

Related

Insert values in many to many relationship tables with JOOQ

I have three tables in my database, SUBSCRIPTION, USER_ID, and an association table called SUBSCRIPTION_USER_ID.
My strategy is to use JOOQ batch with three queries, the first one to insert on row into SUBSCRIPTION, the second query to insert multiple rows into USER_ID, and finally, I need to insert the association IDs into SUBSCRIPTION_USER_ID, so I did the following:
InsertValuesStep2 insertUserIds = insertInto(
USER_ID, USER_ID.USER_ID_TYPE, USER_ID.USER_ID_VALUE);
for (String userId : subscriptionDTO.getUserId())
insertUserIds = insertUserIds.values(getValue(0, userId), getValue(1, userId));
InsertReturningStep insertReturningUserIds = insertUserIds.onConflictDoNothing();
InsertResultStep insertReturningSubscription = insertInto(SUBSCRIPTION)
.set(SUBSCRIPTION.CHANNEL_ID, subscriptionDTO.getChannel())
.set(SUBSCRIPTION.SENDER_ID, subscriptionDTO.getSenderId())
.set(SUBSCRIPTION.CATEGORY_ID, subscriptionDTO.getCategory())
.set(SUBSCRIPTION.TOKEN, subscriptionDTO.getToken())
.onConflictDoNothing()
.returningResult(SUBSCRIPTION.ID);
Unfortunately, to insert values into the association table, I tried many ways but nothing works for me, finally, I tried to insert values in SUBSCRIPTION_USER_IDusing with select but It doesn't work:
InsertValuesStep insertValuesSubscriptionUserIds = insertInto(
SUBSCRIPTION_USER_ID,
SUBSCRIPTION_USER_ID.SUBSCRIPTION_ID,
SUBSCRIPTION_USER_ID.USER_ID_ID)
.select(select(SUBSCRIPTION.ID, USER_ID.ID)
.from(SUBSCRIPTION)
.innerJoin(USER_ID)
.on(concat(USER_ID.USER_ID_TYPE,
val(CATEGORY_USER_ID_DELIMITER),
USER_ID.USER_ID_VALUE).in(subscriptionDTO.getUserId())
.and(SUBSCRIPTION.SENDER_ID.equal(subscriptionDTO.getSenderId()))
.and(SUBSCRIPTION.CHANNEL_ID.equal(subscriptionDTO.getChannel()))
.and(SUBSCRIPTION.CATEGORY.equal(subscriptionDTO.getCategory()))
.and(SUBSCRIPTION.TOKEN.equal(subscriptionDTO.getToken()))));
Am I missing something above? Is there a better way using JOOQ to insert many-to-many relationship values or to use queries results as parameters for other queries?
I'm assuming you posted your entire code. In case of which:
You don't call execute on your USER_ID insertion
Simply add
insertUserIds.onConflictDoNothing().execute();
Or alternatively, fetch the generated IDs using a call to returning().fetch()
Inner join
This might just be a stylistic question, but what you seem to be doing is a cross join. Your INNER JOIN filters aren't really join predicates. I'd put them in the WHERE clause. Clarity may help avoid further problems in such a query.
Specifically, that first "join predicate" is very confusing, containing a CONCAT call, which isn't something one would see in an INNER JOIN every day, and only touches one table, not both:
.on(concat(USER_ID.USER_ID_TYPE,
val(CATEGORY_USER_ID_DELIMITER),
USER_ID.USER_ID_VALUE).in(subscriptionDTO.getUserId())
Wrong predicate
That last predicate seems wrong. You're inserting:
.set(SUBSCRIPTION.TOKEN, subscriptionDTO.getToken())
But you're querying
.and(SUBSCRIPTION.TOKEN.equal(subscriptionDTO.getContactId()))));
That should probably be subscriptionDTO.getToken() again
As mentioned above, I have inserted values for SUBSCRIPTION and USER_ID tables. And get for the association table I need to get the IDs of the already inserted values from the above two tables, so to solve the issue I've used this query to insert in SUBSCRIPTION_USER_ID:
InsertReturningStep insertReturningSubscriptionUserId = insertInto(
SUBSCRIPTION_USER_ID,
SUBSCRIPTION_USER_ID.SUBSCRIPTION_ID,
SUBSCRIPTION_USER_ID.USER_ID_ID)
.select(select(SUBSCRIPTION.ID, USER_ID.ID).from(SUBSCRIPTION
.where(concat(USER_ID.USER_ID_TYPE, val(CATEGORY_USER_ID_DELIMITER), USER_ID.USER_ID_VALUE).in(subscriptionDTO.getUserId()))
.and(SUBSCRIPTION.SENDER_ID.equal(subscriptionDTO.getSenderId()))
.and(SUBSCRIPTION.CHANNEL_ID.equal(subscriptionDTO.getChannel()))
.and(SUBSCRIPTION.CATEGORY.equal(subscriptionDTO.getCategory()))
.and(SUBSCRIPTION.TOKEN.equal(subscriptionDTO.getToken()))).onConflictDoNothing();
Finally, I have executed all the queries using batch:
using(configuration).batch(insertReturningSubscription,
insertReturningUserIds,
insertReturningSubscriptionUserId).execute()

Hibernate Filter by enum type after join

I have 2 hibernate entities TariffDescription and Tariff,
TariffDescription contains a list of tariffs. I need to filter tariffs by sellType and catalogEntryId.
SellType is an enum. And when I execute HQL:
SELECT t FROM Tariff t WHERE t.catalogEntryId = :catalogEntryId AND
t.sellType=:sellType
Everything works fine.
But when I'm trying to execute that HQL:
SELECT td FROM TariffDescription td JOIN td.tariffs t where
t.catalogEntryId = :catalogEntryId AND t.sellType=:sellType
It returns all types of sellType.
It really looks like a bug, but I haven't found something like that on bug trackers.
The version of hibernate: 4.2.18.Final.
JOIN FETCH fixed my issue. Looks like tariffs was lazy loaded and hibernate couldn't apply causes in where.

QueryDsl - creating query with subquery operating on two tables

I'm new with this API and I'm having some problems with a subquery. I'm using QueryDsl - JPA. I have two tables e.g. Film and Producer. In Producer table I have a primary key producer_id and in my Film table I also have this field as producer_id and it's connected. My goal is to count number of film entity for specific producer using QueryDsl.
I wrote something like it:
QFilm film = QFilm.film;
QProducer producer = QProducer.producer;
createQuery().from(film, producer).select(film.count())
.where(film.producerId.eq(producer.producerId)).fetch();
I'm getting a sum of all matching results, e.g. in Producer table I have 2 producers, and in Film table I have 4 films. First producer have 2 films, and second also have 2. Result of this query is 4. But I want for this particular query to return only 2 for first producer, and only 2 for second one. How to archive this?
You are not mentioning what is the relationship between the two db fields(OneToMany, OneToOne e.t.c). Also you are declaring a QFilm and QProducer instance without actually using them in the query.
Assuming that there is OneToOne relationship between producer.producer_id - film.producer_id and you are using the Querydsl's generated metamodel classes I would try this:
QFilm qFilm = QFilm.film;
long count = creatyQuery().selectFrom(qFilm).where(qFilm.producerId.eq(yourDesiredProducerId)).fetchCount();

Querydsl ignores common table expression alias for JPA Entities

I need to build SQL query with common table expression using QueryDSL:
WITH cte AS (
SELECT DISTINCT BUSINESS_ID FROM BUSINESS WHERE MERCHANT_CODE like ?
)
SELECT t0.*
FROM PAYMENT t0
LEFT JOIN cte t1 ON t0.PAYER = t1.BUSINESS_ID
LEFT JOIN cte t2 ON t0.PAYEE = t2.BUSINESS_ID
WHERE (t1.BUSINESS_ID IS NOT NULL OR t2.BUSINESS_ID IS NOT NULL)
I have two JPA entities (Payment, Business).
This is how I implemented that:
String merchantCode = "abcd%";
QPayment payment = QPayment.payment;
QBusiness business = QBusiness.business;
QBusiness cte = new QBusiness("cte");
QBusiness merchant1 = new QBusiness("t1");
QBusiness merchant2 = new QBusiness("t2");
Configuration configuration = new Configuration(new OracleTemplates());
new JPASQLQuery<>(entityManager, configuration)
.with(cte,
JPAExpressions.selectDistinct(business.businessId).from(business)
.where(business.merchantCode.like(merchantCode)))
.select(payment)
.from(payment)
.leftJoin(cte, merchant1).on(payment.payer.eq(merchant1.businessId))
.leftJoin(cte, merchant2).on(payment.payee.eq(merchant2.businessId))
.where(merchant1.businessId.isNotNull()
.or(merchant2.businessId.isNotNull()));
And the problem is that during leftJoin it doesn't treat cte as a link, instead it inserts table name and two aliases: LEFT JOIN BUSINESS cte t1 ON .... I tried different templates – didn't help.
Am I doing something wrong or it's a QueryDSL bug?
JPQL doen't support CTEs, as we can see in grammar. And querydsl works over JPQL. CTEs are pretty vendor-specific, so you'll have to do one of following:
Rewrite query to be JPA-compatible
Use JPA native query
Query sql with querydsl (actually I don't remember if it supports CTEs)
From all above I would chose the 2nd option. Making native queries doen't harm your code. It makes your code more performant.
Take a good look at the tutorial
QCat cat = QCat.cat;
QCat mate = new QCat("mate");
QCat kitten = new QCat("kitten");
query.from(cat)
.innerJoin(cat.mate, mate)
.leftJoin(cat.kittens, kitten)
.list(cat);
You will want to .leftjoin(cte.merchant1, merchant1).on(...) or whatever the corresponding field is called in the parent "cte".
Basically you need to name the field which you want to join. Just stating the meta model does not suffice as there is no way of telling what you actually want. You can see it in your code (as well in the tutorial's kitten example): you have two Merchant you want to join to the cte, so which one is which.
The .on()-clause just states the conditions under which a join is valid, like you could place filters there.
.

Automatic generation of java hibernate code from sql code

Is there a tool able to generate java hibernate code starting from the sql query?
(like reverse of what hibernate does, generating selects from java code) It will help me move all my queries to hibernate!
I mean if i have a select with parameters like this:
select ta.id label, ta.nume value
from ar
left outer join ta ta on idp = ta.ID
where ta.status = 1
and (dp = 0 OR ps = idps_)
and status = 1
order by ta.nume;
to obtain in the end something like this:
DetachedCriteria criteria = DetachedCriteria.forEntityName("ar");
criteria.createAlias("ta", "ta", Criteria.LEFT_JOIN);
criteria.add(Restrictions.eq("ta.status", 1));
Criterion eq = Restrictions.eq("ps.id", idps_);
Criterion isZero = Restrictions.eq("dp.id", 0);
Criterion or = Restrictions.or(eq, isZero);
criteria.add(or);
criteria.add(Restrictions.eq("status", 1));
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.property("ta.id"), "value");
projectionList.add(Projections.property("ta.nume"), "label");
criteria.setProjection(Projections.distinct(projectionList));
criteria.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
criteria.addOrder(Order.asc("ta.nume"));
OR something similar using maps as output...
providing to the tool the path where i store the mappings of the entities/beans with the tables (or the path to the beans, if the beans are annotated)
You have the HQL that is an SQL-like dialect to work with Hibernate. You use field names from entities instead of those from tables. It supports joins etc.
In fact, Criteria API has very limited support of joins (at least it was so last time I've tried to used) and I've a few times finished rewriting everything from Criteria API to HQL, so I now simply tread Criteria API as no option.
In HQL you can also use SQL functions, both in SELECT and in WHERE part, those embedded and those you've written yourself.

Categories

Resources