Hibernate Criteria API - update many rows with one database query - java

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

Related

JPA Update Multiple Records in Table for a ID

I have a requirement for a Input record with id1 from source, in target table I need to update value v1 in column c1 and in target for id1 there are multiple records. Using JPA I need to update all those records with value v1. Using JPA what is the best way to do this?
I used below
findallbyid() then saveall() - it failed saying there are mutliple records in target but expected was one.
Based on the details provided findallbyid() then saveall()
here the method findallbyid() is actually expecting to find only one record in the table, where as there are multiple rows.
changing the to signature of the method should work as expected without expection. As it expect capitalised words in method signature
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html
List<T> findAllById(Long id);
but recommend not to read all rows and then save again just to update a column or two, you could use something like below to achieve the same
#Modifying
#Transactional
#Query(value = "UPDATE table t SET t.column = :status WHERE t.id = :id")
int update(#Param("status") String status, #Param("id") Long id);

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()

QueryDsl - How create inner join with sorted and grouped table

I have to find Salesmen that have sold some itemType. I created method (see below).
But client told me that he wants to find Salesmen by LAST sold itemType.
DB schema:
My attempts: we have in table ORDERS date column, so in normal SQL query I can do double subquery and it should work.
Double, because first I'm sorting by date, then group by salesman - that returns list with only last sold items.
SELECT *
FROM SALESMEN
JOIN
(SELECT *
FROM
(SELECT *
FROM ORDERS
ORDER BY ORDERS.date)
GROUP BY ORDERS.salesman_id) ON SALESMEN.id = ORDERS.salesman_id
WHERE ORDERS.item_type = "CAR"
Unfortunately, queryDSL can do subquery only in IN clause not in FROM.
I spend many hours to find a solution, and in my opinion, it's simply impossible using queryDSL to get sorted and grouped list and join it with another table in one query.
But maybe someone with grater experience has any idea, maybe a solution is simpler than I think :D
public List<SalesmanEntity> findSalesman(SalesmanSearchCriteriaTo criteria) {
SalesmanEntity salesmanEntity = Alias.alias(SalesmanEntity.class);
EntityPathBase<SalesmanEntity> alias = Alias.$(salesman);
JPAQuery<SalesmanEntity> query = new JPAQuery<SalesmanEntity>(getEntityManager()).from(alias);
... a lot of wird IF's....
if (criteria.getLastSoldItemTyp() != null) {
OrderEntity order = Alias.alias(OrderEntity.class);
EntityPathBase<OrderEntity> aliasOrder = Alias.$(order);
query.join(aliasOrder)
.on(Alias.$(salesman.getId()).eq(Alias.$(order.getSalesmanId())))
.where(Alias.$(order.getItemTyp()).eq(criteria.getLastSoldItemTyp()));
}
return query.fetch();
}
Environment:
Java 1.8
SpringBoot 2.0.9
QueryDSL 4.1.4
This is not a limitation of QueryDSL, rather it is a limitation of JPQL - the query language of JPA. For example, SQL does allow subqueries in the FROM clause, and as such querydsl-sql also allows it. With plain plain JPA, or even Hibernate's proprietary HQL it cannot be done. You would have to write a native SQL query then. For this you can have a look at #NamedNativeQuery.
It is possible to add subqueries on top of JPA using Common Table Expressions (CTE) using Blaze-Persistence. Blaze-Persistence ships with an optional QueryDSL integration as well.
Using that extension library, you can just write the following:
QRecursiveEntity recursiveEntity = new QRecursiveEntity("t");
List<RecursiveEntity> fetch = new BlazeJPAQuery<>(entityManager, cbf)
.select(recursiveEntity)
.from(select(recursiveEntity)
.from(recursiveEntity)
.where(recursiveEntity.parent.name.eq("root1"))
.orderBy(recursiveEntity.name.asc())
.limit(1L), recursiveEntity)
.fetch();
Alternatively, when using Hibernate, you can map a Subquery as an Entity, and then correlate that in your query. Using this you can achieve the same result, but you won't be able to reference any outer variables in the subquery, nor will you be able to parameterize the subquery. Both of these features will however be available with the above approach!
#Entity
#Subselect("SELECT salesman_id, sum(amount) FROM ( SELECT * FROM ORDERS ORDER BY ORDERS.date ) GROUP BY ORDERS.salesman_id")
class OrdersBySalesMan {
#Id #Column(name = "salesman_id") Long salesmanId;
#Basic BigDecimal amount; // or something similarly
}
And then in your query:
.from(QSalesman.salesman)
.innerJoin(QOrdersBySalesMan.ordersBySalesMan)
.on(ordersBySalesMan.salesmanId.eq(salesman.id))

How to use update hibernate query using setMaxResults?

I hope it is the appropriate section, I have a problem with this code
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("update database set floop= :ctrl1" +" where ctrl= :ctrl2 ").setMaxResults(2);
query.setMaxResults(2);
query.setParameter("ctrl1",3);
query.setParameter("ctrl2", 5);
I ask through setMaxResults(2) to do the update only on the first two and he makes the update of all records as I do what is wrong?? thanks for any help
I thought to use session.createSQLQuery, but I do not know how to do.
This answer is posting delay but it can be helpful for others user who is looking update number of rows in DB with limit using HQL
Unfortunatly setMaxResults() do not work update and delete hibernate
query. It works only select criteria.
As per HQL there is no specific solution is available and you want to update rows with number of limit then follow below steps
Write a HQL to select all rows with condition or range with
setMaxResults. It will return you a List object with limit.
Then update the specific property (Property you want to update) and
store Objects of these rows again by session.update() method.
I'm assuming tablename with map Database class and there are two variable ctrl and floop with getter and setter(as per your question)
List<Database> list = new ArrayList<>();
Transaction transaction = session.beginTransaction();
//Fetching record with limit 2 using setMaxResults()
int setlimit = 2;
String hql_query = "from Database where ctrl = :ctrl2";
Query select_query = session.createQuery(hql_query).setMaxResults(setlimit);
select_query.setParameter("ctrl2", 5);
list = select_query.list();
//iterating list and setting new value to particuler column or property
int result;
if (list != null) {
for (Database element : list) {
element.setFloop(ctrl1);
//Here is updating data in database
session.update(element);
}
result = list.size();
} else {
result = 0;
}
System.out.println("Rows affected: " + result);
transaction.commit();
setMaxResults limits the number of results which are returned by the query, not the number of affected rows.
When you only want to update a limited set of rows, you should specify these rows within the where condition. Setting a hard limit on the number of updated rows wouldn't make much sense, because there would be no way to tell which rows would be updated.
query.setMaxResults(2); will be used for selection queries and will be ignored for insertion/updation. If you use it for selection queries, then you will get 2 records in result.
setMaxResults only applies to select. For your problem I would perform a select query and then use the query.setMaxResults(2), this will return a list of max 2 elements. Then loop the list returned and use session.update for the one or two elements returned.
I can see a number of perfectly valid use-cases where you want to update only a limited number of rows and as other have already answered, the Hibernate Query cannot deal with this so you need to resort to native SQL.
You don't specify in the question which type of database you are using so this answer will only apply to MySql:
Transaction transaction = session.beginTransaction();
Query query = session.createSQLQuery("UPDATE database SET floop= :ctrl1 WHERE ctrl= :ctrl2 LIMIT :max");
query.setParameter("ctrl1",3);
query.setParameter("ctrl2", 5);
query.setParameter("max", 2);
Please note that the sql query above needs to use the native table and column names and not the ones in your ORM model.

How do I access multiple fields in a JPA query?

I have a JPA query of the form:
SELECT category, count(*) AS c FROM ...
I know that if the query just returns a single column I can do something like:
List<Article> articles = query.getResultList();
However, how do I access the results when there are 2 or more columns as in the example above?
check out section 14.6 here: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/queryhql.html#queryhql-select
it will return a List of Object[] if you select more than one column, but dont get the actual entity.

Categories

Resources