Spring JPA: Association requirement when joining tables - java

We have two tables, ORDER and STATUS.
For every change in the orders in ORDER table, we enter the audit into STATUS table with lastUpdatedDateTime.
Sometimes, I just need to fetch order detail and sometimes I need latest status along with order details.
How should I design my entity and how should I join tables when needed?
Do I need to add associations to join table(using criteria builder)?
If I add associations, status data from STATUS table will be pulled even when not needed (e.g. when doing findBy on ORDER where we just need order details)?
Also when I display to the User the orders, they should look like below. Need status and lastUpdatedBy in the Order object
[
{
"OrderId": "1",
"product": "pen",
"status": "IP",
"lastUpdatedBy": "user1"
},
{
"OrderId": "1",
"product": "book",
"status": "CP",
"lastUpdatedBy": "user2"
}
]
I have tried using association and criteria builder to join tables.
Questions:
A right way to design my entites
how to join tables to fetch the top record from status table for a given order.
(P.S i need to use predicates as I have dynamic where clause requirement)
Sample SQL
Select f, o.statusCode, o.lastUpdatedBy
from FX_ORDER f left join ORDER o
ON f.ORDER_ID = o.ORDER_ID
and f.SEQUENCE= o.SEQUENCE
where o.statusCode = 'IP'
and o.lastUpdatedDateTime in (select max(s.lastUpdatedDateTime)
from ORDER s where
s.ORDER_ID = o.ORDER_ID
and s.SEQUENCE= o.SEQUENCE);

Based on the information, it seems like you have a one-to-many relationship between ORDER and STATUS tables, where an ORDER can have multiple STATUS records.
To design your entities, you can create two classes: Order and Status. The Order class will have a collection of Status objects as a member variable to represent the one-to-many relationship. The Order class will also have attributes for OrderId and product, and the Status class will have attributes for statusCode, lastUpdatedBy, and lastUpdatedDateTime. You can then use JPA annotations to map these classes to the corresponding tables in the database.
When you want to fetch order details with the latest status, you can use a query that joins the ORDER and STATUS tables and selects the top record from the STATUS table for each ORDER record.Check below example query
SELECT o.OrderId, o.product, s.statusCode, s.lastUpdatedBy
FROM ORDER o
LEFT JOIN STATUS s ON o.OrderId = s.OrderId
AND s.lastUpdatedDateTime = (
SELECT MAX(s2.lastUpdatedDateTime)
FROM STATUS s2
WHERE s2.OrderId = o.OrderId
)
This query joins the ORDER and STATUS tables on the OrderId column and uses a subquery to select the latest STATUS record for each ORDER record. The LEFT JOIN ensures that all ORDER records are included in the result, even if there are no corresponding STATUS records.
To use this query with dynamic where clause requirements, you can create a JPA CriteriaQuery object and add predicates to it based on the search criteria.Here's an example
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> orderRoot = query.from(Order.class);
Join<Order, Status> statusJoin = orderRoot.join("statuses", JoinType.LEFT);
query.multiselect(
orderRoot.get("OrderId"),
orderRoot.get("product"),
statusJoin.get("statusCode"),
statusJoin.get("lastUpdatedBy")
);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(statusJoin.get("statusCode"), "IP"));
query.where(predicates.toArray(new Predicate[0]));
query.orderBy(cb.desc(statusJoin.get("lastUpdatedDateTime")));
List<Order> result = em.createQuery(query).getResultList();

Related

'LIKE' expression on JSONB column with CriteriaQuery

I am trying to use JSONB with CriteriaQuery, and trying to implement this query in terms of criteria builder:
select *
from customer
where exists (
select true
from jsonb_array_elements(customer.contacts::jsonb)
where value::text like '%2%'
);
But seems to be there is no way, in criteria query, to have correlated subquery from function execution result.
Do you know how to implement this query in criteria query or how to make a 'LIKE' expression against JSONB column?
JSONB column has following format: [{"type": "PHONE", "value": "1122"}, {"type": "PHONE", "value": "3344"}];
The query should be a criteria query instance.
Thanks in advance.
Try something like this:
select *
from customer
cross join lateral jsonb_array_elements(contacts) as x(jcontact)
where jcontact->>'value' like '%2%';
Have a look at the results. You will get multiple rows if there is more than one contact that has value like '%2%', but you can eliminate those with distinct and restricting to the columns you actually need.
As for HIbernate or criteriaquery, I cannot help you there. ORMs are a really bad idea.

How to filter child collection in JPQL query?

I've the following DB model:
Category -< ProductCategory >- Product -< Variant
(Category has many-to-many relationship with Product and Product has one-to-many relationship with Variant)
Now I need to get all Category records that have product with active variants. I'm getting these objects via the following JPQL query:
#Query("select distinct c from Category c join c.products as p join p.variants as pv where pv.active = true")
It works well - returns categories accurately - however every single Category contains all the products - not only these with active variants.
How can I filter out the products (or variants) that are inactive in a single query?
Here's a postgres script that with database struct and sample data. For given data two categories (CAT 1, CAT 2), two products (PROD 1, PROD 2) and three variants (VAR 1, VAR 2, VAR 3) should be returned.
I had exactly the same problem and it took me a while to find out how this works. The child list should be filtered when you add FETCH after your JOIN like this:
SELECT DISTINCT c FROM Category c JOIN FETCH c.products as p join p.variants as pv where pv.active = true
I have the same problem on it, and I found that the FetchType in the #OneToMany annotation is important. It need to be set as Lazy mode. If it is Eager mode, jpa will create the sql query to fetch the data from the table for your child collection and won't filter it for you.

Hibernate Criteria API - update many rows with one database query

I'm fetching from database bunch of persons like this:
public List<Object[]> getLimitedBunchOfPersons(Integer limit) {
Criteria criteria = getSession().createCriteria(Person.class, "person")
.setProjection(
Projections.projectionList()
.add(Projections.property("person.personId"), "personId")
)
.createAlias("person.status","status")
.add(Restrictions.eq("status.statusId", 1L))
.addOrder(Order.asc("person.createdOn"));
return criteria.setMaxResults(limit).list();
}
As I needed to speed things up, I only fetched ID's of my entity. Important thing to note is that I'm manipulating with large number of rows and for one query had to use maxResults limitation.
Now my problem is, how to easily update with Hibernate Criteria API in one database query all fetched rows from previously mentioned query?
Plain SQL query would go something like this:
UPDATE PERSON
SET STATUS = 2, CREATED_ON = CURRENT_TIMESTAMP
WHERE STATUS = 1;
It's important to note that update method have to use same order and limit as getLimitedBunchOfPersons() method.
For Single Object it will work as follows after your code
Person per= (Person) criteria.uniqueResult();
per.setCreatedOn("crtBy");
currentSession.merge(per);
Now if comes in list you can iterate list by passing mentioned code in your List iteration

Hibernate Criteria join to table containing foreign key

I have two tables:
Client (clientId, firstName, lastName, gender)
Event (clientId, eventId)
I need to represent a query similar to the following using Criteria:
SELECT c.clientId, c.firstName, c.lastName, c.gender, MAX(eventId)
FROM Client c JOIN Event e ON c.clientId = e.clientId
GROUP BY c.clientId, c.firstName, c.lastName, c.gender
I have tried this:
final Criteria criteria = session.createCriteria(Client.class);
criteria.setFetchMode("Event", FetchMode.JOIN);
criteria.setProjection(Projections.projectionList().add(Projections.groupProperty("clientId")).add(Projections.max("eventId")));
but it throws an exception on the last line with the message:
HibernateQueryException: could not resolve property: eventId of:
Client
How can I specify the join between the Client table which itself contains no column related to the Event table but the clientId column on the Event table is a foreign key back into the Client table?
As you can see, it's really driven off the Client table and that I only need to select the maximum eventId from the Event table. Also, as I mentioned, I am trying to make a change to an existing Criteria query which is based on the Client class. It is used to retrieve all the columns for all active clients. I just need to add one extra column to the query results - the maximum eventId.
Use alias
Criteria criteria = session.createCriteria(Event.class, "et").
createAlias("et.Client", "ct").
setProjection(Projections.projectionList().
add(Projections.groupProperty("et.clientId")).
add(Projections.max("et.eventId")));
For more details on criteria, refer Criteria Queries
That is obvious. Because Client class does not have eventId property, and your criteria is defined for Client class.
When trying to use a property of B class inside a Criteria for A, you have to use Aliases.
All you have to do is to modify your code like this:
final Criteria criteria = session.createCriteria(Event.class, "event");
criteria.createAlias("event.client", "client");
criteria.setProjection(Projections.projectionList().add(Projections.groupProperty("clientId")).add(Projections.max("eventId")));
UPDATED (based on your comment)
As your query needs Event class, you have to have a Criteria for this class. So you have to something like this:
final Criteria criteria = session.createCriteria(Event.class, "event");
criteria.createAlias("event.client", "client");
//The criteria below, is returning clientId
DetachedCriteria eventCr = DetachedCriteria.forClass(Event.class, "event");
eventCr.setProjection(Projections.projectionList().add(Projections.groupProperty("clientId")).add(Projections.max("eventId")));
//Now using subqueries you can achieve your goal
criteria.add(Subqueries.propertyIn("clientId", eventCr));
I don't know for sure what you're looking for, but I hope I have given you some good hints. You might want to try Subqueries.propertyEq instead if your query must return a single id.

How to map native query to one model class?

Hi I´m using Eclipselink and I did a native query to select some fields of 2 tables. I mapped my table Logins in a model class. I would not like to map my table "B" because I need only 2 fields of this table on my sql result.. can I map this 2 fields in my Logins table to my sql result ?
My sql is this:
select l.login_id, s.lugarcerto,s.vrum, l.username, l.first_name, l.last_name, l.phone, l.fax_number, l.address, l.zip,
l.address2 as 'birth_date', l.city as 'cpf_cnpj'
from Logins l
join (select se.login_id, lugarcerto = min(case when se.service = 'IM' then '1' end), vrum = min(case when se.service = 'VE' then '1' end)
from (select distinct ad.login_id, substring(ap.Rate_code,(CHARINDEX('-', ap.Rate_code)+1),2) as 'service'
from Ad_Data.dbo.ad ad
join Ad_Data.dbo.ad_pub ap on (ad.ad_id = ap.ad_id)
where ap.ad_type =1) se
group by se.login_id) s on (s.login_id = l.login_id)
I did map Logins table and I want to map s.lugarcerto and s.vrum to my SQL query result.
There´s anyway to just add it to my Logins model ?
Not without having mappings for the attributes you want those values put into, and not without causing problems with them being cached in the entity.
Why not just return the values beside the entity, much like you would with a JPQL query such as: "Select l, subquery1, subquery2 from Logins l" ie:
Query q = em.createNativeQuery(yourQueryString, "resultMappingName");
And in the entity, include the annotation:
#SqlResultSetMapping(name="resultMappingName",
entities={#EntityResult(entityClass=com.acme.Logins.class, )},
columns={#ColumnResult(name="LUGARCERTO"), #ColumnResult(name="VRUM")}
)
Best Regards,
Chris

Categories

Resources