Spring JPA repository update multiple entities with a single query - java

At the moment I have the following code which iterates a list of parameter entities and updates each of their names in the database:
public class test {
#Autowired
private ParameterJpaRepository parameterJpaRepository;
public updateParameters(List<Parameter> parameters) {
for (Parameter parameter : parameters) {
parameterJpaRepository.setNameById(parameter.getId(), parameter.getName());
}
}
}
public interface ParameterJpaRepository extends JpaRepository<Parameter, Long> {
#Modifying
#Query("UPDATE Parameter p SET p.name = :name WHERE p.id = :id")
void setNameById(#Param("id") long id, #Param("name") String name);
}
Obviously, this results in N queries:
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?
I would like to combine then into a single query equivalent to this attempt:
public interface ParameterJpaRepository extends JpaRepository<Parameter, Long> {
#Modifying
#Query("UPDATE Parameter p SET p.name = (:names) WHERE p.id = (:ids)")
void setNameById(#Param("ids") List<Long> ids, #Param("names") List<String> names);
}
Which should yield something like:
Hibernate: UPDATE parameter
SET name = (case when id = ? then ?
when id = ? then ?
when id = ? then ?
when id = ? then ?
end)
WHERE id in (?, ?, ?, ?);
Is this possible?

You probably want something like this
#Modifying
#Query(value = "UPDATE Parameter p SET p.name = (CASE " +
"WHEN p.id IN (:ids) THEN (CASE " +
"WHEN p.id = :ids[0] THEN :names[0] " +
"WHEN p.id = :ids[1] THEN :names[1] " +
"WHEN p.id = :ids[2] THEN :names[2] " +
// ...
"ELSE p.name " +
"END) " +
"ELSE p.name " +
"END) " +
"WHERE p.id IN (:ids)", nativeQuery = true)
void setNameById(#Param("ids") List<Long> ids, #Param("names") List<String> names);
This is a bad approach. Don't try to do that.
It's very bad for large lists, since the query will become very long and very difficult to maintain.
It does not work if the ids and names lists are not in the same order.
If you need to update a big number of rows, or if the order of the ids and names lists is not fixed, you might want to consider using a different approach, such as executing a separate update statement for each row or using a temporary table .

(Un)fortunately, spring-data-jpa functionality is not so flexible as you would like to see, however it does allow to create Custom Implementations for Spring Data Repositories and thus you may write any update query you want, some examples:
https://thorben-janssen.com/composite-repositories-spring-data-jpa/
https://vladmihalcea.com/custom-spring-data-repository/
https://www.baeldung.com/spring-data-composable-repositories
by the way, you need to keep in mind that in general that is not a good idea to update hibernate entities via direct update, the reasoning is following:
it is not cache friendly - if you are using second level cache, hibernate needs to completely cleanup those cache, cause it has no chance to get know what entities have been updated
if you are using auditing solution like envers, direct update bypasses that solution
so, sometimes it is much better to enable batch updates and write something like:
#Transactional
default void setNameById(List<Long> ids, List<String> names) {
Map<Long, Parameter> data = StreamSupport.stream(findAllById(ids).spliterator(), false)
.collect(Collectors.toMap(
Customer::getId,
Function.identity()
));
for (int i = 0, n = ids.size(); i < n; i++) {
Parameter parameter = data.get(ids.get(i));
if (parameter != null) {
parameter.setName(names.get(i));
}
}
saveAll(data.values());
}

Related

JPA native query from list to tuples

I have defined a method on my JPARepository to update a property's entity for a given list of ids.
#Modifying
#Transactional
#Query("UPDATE Entity SET date = ?1 WHERE id IN (?2)")
void updateDeletionDate(Date date, List<Long> ids);
This works, but I've just found out that maximum length of the list is 1000 items (due to ORA-01795), so I'm trying the best approach I've found so far: 2. Use tuples. However, I don't know how to translate the query, since something like this obviously fails: UPDATE Entity SET date = ?1 WHERE (id , 0) IN ((?2, 0))
you could use a join on a subquery which selects the ids you want to update
UPDATE Entity e
SET e.date = ?1
WHERE e.id IN (SELECT i.id FROM (VALUES (?2), (?3), ...) as i(id))

How to write "JPQL" query with "IN" between SELECTs?

I wrote this JPQL query and expect a result as List<Question>:
#Query("SELECT q FROM Question q WHERE q.id IN (SELECT qc.questions FROM QCard qc WHERE qc.id IN (SELECT ct.qCards FROM CTest ct WHERE ct.id=:id))")
These are my classes:
class CTest {
id, List<QCard>
}
class QCard{
id, List<Question>
}
class Question{
id
}
I expected all questions as return for given CTest.id.
But I got a compiler-error with message:
SQLSyntaxErrorException
I tried use ":" before "select" words but it had not helped.
What is wrong?
Test your SQL query in an SQL-client (e.g. Squirrel, DbVisualizer, etc.).
Assuming your foreign keys are named:
question_id in table QCard
card_id in table CTest
you could have an SQL with subselects like:
SELECT q.id
FROM Question q
WHERE q.id IN (
SELECT qc.question_id
FROM QCard qc
WHERE qc.id IN (
SELECT ct.card_id
FROM CTest ct
WHERE ct.id = 1 -- example test id
)
)
Does it return the expected result or are there syntax errors?
Then subsequently replace your subselects by JOINs.
Question and Cards
SELECT q.id, c.id
FROM Question q
JOIN QCard c ON c.question_id = q.id
Cards and Tests
SELECT c.id, t.id
FROM QCard c
JOIN CTest t ON t.card_id = c.id
All together
SELECT q.id, c.id, t.id
FROM Question q
JOIN QCard c ON c.question_id = q.id
JOIN CTest t ON t.card_id = c.id
Note: add WHERE clauses like WHERE t.id = 1 if needed.
Experiment with the FROM/JOIN order as it makes sense.
Then translate the running SQL query to JPQL. For example:
#Query("SELECT q"
+ " FROM CTest test"
// a test has many cards (1:n)
+ " JOIN QCard card ON card.id = test.card_id" // associated cards
// a card has many questions (1:n)
+ " JOIN Question q ON q.id = card.question_id" // associated questions
+ " WHERE test.id = :id")
public List<Question> findQuestionsByTestId(String id);
List<Question> findByIdIn(List<Long> idList); //In repository
or
String qlString = "select i from Item i where i.name IN :names";
Query q = em.createQuery(qlString, Item.class);
List<String> names = Arrays.asList("foo", "bar");
q.setParameter("names", names);
List<Item> actual = q.getResultList();
I did those example in my past work, check it, tnx
In your existing query, in inner queries you are selecting entities and checking IN against Id, which will definitely won't work. As you can't complete entry with the Id.
As you haven't shared your complete entity structure, assuming that you have two way relationship declared correctly in entities, here I am placing a reference query which uses the join:
#Query("SELECT q FROM Question q JOIN q.qCard qc WHERE q.qCardId = qc.id AND qc.cTestId = :id")
Where qCardId is the foreign key reference of QCard entity in Question entity and cTestId is the foreign key reference of CTest entity in QCard entity.
You can use this for your reference to update your query with joins.

How to return multiple results in a unique mapped or not object JPA

org.hibernate
hibernate-core
4.3.8.Final
org.hibernate
hibernate-entitymanager
4.3.8.Final
My pom.xml
My Problem is: How to make a query like this...
SELECT
TABLE_D.*,
TABLE_A.NAME_A
FROM
TABLE_D
INNER JOIN
TABLE_E
ON TABLE_D.ID_TAB_E = TABLE_D.ID_TAB_D
LEFT JOIN
TABLE_C
ON TABLE_C.ID_TAB_C = TABLE_D.ID_TAB_D
INNER JOIN
TABLE_B
ON TABLE_B.ID_TAB_B = TABLE_C.ID_TAB_C
INNER JOIN
TABLE_A
ON TABLE_A.ID_TAB_A = TABLE_B.ID_TAB_B
WHERE
TABLE_A.NAME_A = "XXXX";
And Return the selected the values TABLE_D and TABLE_A ​​in a unique Object List(ex: Object that i create to take all this fields) (I could create 1 filter, whatever...) in the JPA ? Plz Help.
If you need to return a list of selected columns in HQL you can just write your hql query and return a List of Object array, i.e.:
List<Object[]> result = session.createQuery("select a.field1, b.field2 from EntityA a join a.entityB b").list();
then you can iterate and get values, based on their type (i.e. String):
for (Object[] arr : result) {
String col1 = (String)arr[0];
String col2 = (String)arr[1];
}

JPA: Query that returns multiple entities

I'm writing a JPQL query that joins across three tables. In my resultlist I would like to get all three entities per matching row (hope that makes sense).
Any ideas?
Hibernate 3.x is my JPA provider.
IIRC, you can do a SELECT o1, o2, o3 FROM EntityA o1, EntityB o2, EntityC o3 WHERE ...., and the result will be a List<Object[3]>, where the array contents will contain the o1,o2,o3 values.
This is a Spring Data sample, however its works the same way in JPA
//HQL query
#Query("SELECT c,l,p,u FROM Course c, Lesson l, Progress p, User u "
+ "WHERE c.id=l.courseId AND l.id = p.lessonId AND p.userId = u.id AND u.id=:userId AND c.id=:courseId")
public List<Object[]> getLessonsWithProgress(#Param("userId") Integer userId, #Param("courseId")Integer courseId);
Then, I call this method and print the results:
List<Object[]> lst = courseRepository.getLessonsWithProgress(userId, courseId);
for (Object o[] : lst) {
Course c = (Course) o[0];
Lesson l = (Lesson) o[1];
Progress p = (Progress) o[2];
User u = (User) o[3];
//all the classes: Course, Lesson, Progress and User have the toString() overridden with the database ID;
System.out.printf("\nUser: %s \n Lesson: %s \n Progress: %s \n Course: %s",u,l,p,c);
}
The output #Test is here:
User: com.cassio.dao.model.User[ id=1965 ]
Lesson: com.cassio.dao.model.Lesson[ id=109 ]
Progress: com.cassio.dao.model.Progress[ id=10652 ]
Course: com.cassio.dao.model.Course[ id=30 ]
Cheers
Since You are asking JPA: Query that returns multiple entities, EclipseLink too comes under it. And I reached on this question googling for EclipseLink. So here is my solution. Hope it works for you.
TypedQuery<Object[]> query = entityManager.createQuery("select p from Post p where p.publisher.pubId= :ID order by p.createdAt desc",
Object[].class);
query.setParameter("ID", publisherID);
Then you can loop through the result objects and cast them accordingly.
for (Object result : query.getResultList()) {
myList.add((Post) result);
}
You can also try this,
Query query = entityManager.createQuery("select p from Post p where p.publisher.pubId= :ID order by p.createdAt desc");
Reference:http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL
In case of many to one or one to many relationship how to get multiple records of one of the entities? lets say A is one entity and B is another entity but they have one to many relationship and when you get result you expect B has one record and A has more than 1 record? My query is as below but I don't know how shall I get multiple records of 2nd entity?
#Query("SELECT wl, gr FROM WatchList as wl, GeozoneReference gr " +
"WHERE wl.watchlistId = gr.objWatchList.watchlistId " +
"AND wl.watchlistId =:watchlistId")
List<Object[]> findWatchlistByWatchlistId(#Param("watchlistId") Long watchlistId);

Hibernate "IN" clause as ALL instead of ANY

I'd like to start by apologizing for my unfamiliarity with Hibernate. I'm only recently getting into it and am far from an expert.
I have three tables: Contract, Products, and a link table between them to define a many to many relationship.
I'm trying to write an HQL query to return all contracts that contain a range of products. Unfortunately, the IN syntax works like an Any instead of an All. So if I want all contracts that have ProductA, ProductB, and ProductC, the IN keyword will return me contracts that have any individual one of those products, instead of contracts that have all of them.
How should I structure my HQL query?
Why are you expecting IN to behave like a AND? To my knowledge, IN is a kind of OR, not a AND. IN might thus not be what you're looking for. Have a look at Hibernate's Expressions and especially:
HQL functions that take collection-valued path expressions: size(), minelement(), maxelement(), minindex(), maxindex(), along with the special elements() and indices functions that can be quantified using some, all, exists, any, in.
[...]
The SQL functions any, some, all, exists, in are supported when passed the element or index set of a collection (elements and indices functions) or the result of a subquery (see below):
[...]
from Show show where 'fizard' in indices(show.acts)
For more than 2000 ids at in clause use a subquery like [from group where groupid in(select id from elemtable)]
Otherwise use criteria to overcome the stackoverflow error.
Example:
Session session = getHibernateTemplate().getSessionFactory().openSession();
Criteria criteriaEaquals = session.createCriteria(Elements.class);
criteriaEaquals.add(Restrictions.in("elementId", elemIds));
criteriaEaquals.setProjection(Projections.distinct(Projections.property("type")));
List list = criteriaEaquals.list();
session.close();
System.out.println("typelistis--->"+list.toString());
return list;
You can use group by / having:
select c
from Contract c join c.products p
where p.name in ('A', 'B', 'C')
group by c.id, // list ALL Contract properties
having count(*) = 3
Alternatively you can use a subquery to avoid listing all properties in group by:
from Contract c where c.id in (
select c.id
from Contract c join c.products p
where p.name in ('A', 'B', 'C')
group by c.id
having count(*) = 3
)
Obviously "3" will have to be replaced with the actual number of product names you supply in in clause.
In the blog I went over such hibernate queries, take a look at example #4.
Here is a snapshot (replace Articles with Contracts and Tags with Products):
String[] tags = {"Java", "Hibernate"};
String hql = "select a from Article a " +
"join a.tags t " +
"where t.name in (:tags) " +
"and a.id in (" +
"select a2.id " +
"from Article a2 " +
"join a2.tags t2 " +
"group by a2 " +
"having count(t2)=:tag_count) " +
"group by a " +
"having count(t)=:tag_count";
Query query = session.createQuery(hql);
query.setParameterList("tags", tags);
query.setInteger("tag_count", tags.length);
List<Article> articles = query.list();

Categories

Resources