10

I want to execute multiple update statements in the same query in hibernate Hql. like below:

hql = " update Table1 set prob1=null where id=:id1; "
                + " delete from Table2 where id =:id2 ";
...
query.executeUpdate();

in the same executeUpdate call I want to update records in Table1 and delete records from Table2.

Is that possible?

Andremoniy
  • 34,031
  • 20
  • 135
  • 241
Nebras
  • 636
  • 1
  • 7
  • 16
  • if relationship between tables and data you want to modify allow this statement, **yes** it is possible, **but** IMHO you should use Hibernate [`Criteria`](https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querycriteria.html) instead `hql` language. http://stackoverflow.com/questions/197474/hibernate-criteria-vs-hql?rq=1 – Jordi Castilla Aug 19 '16 at 08:00
  • I can execute the statement on standard SQL. But hibernate throws exception `unexpected char: ';'`. Hibernate is not able to parse the string correctly. I am wondering if there is another syntax for the statement or another way to use HQL. – Nebras Aug 19 '16 at 08:14
  • 1
    you will need various `Query::executeUpdate`, cannot concatenate, but will be same transaction in session – Jordi Castilla Aug 19 '16 at 08:19
  • _Why_ do you want to combine queries like that? That is, _why_ is it so important as to add a bounty? – Rick James Aug 22 '16 at 20:35
  • @RickJames It is not OP that started the bounty, it is me. My answer to this question is in dispute with Andremoniy's, so I think that better clarification/comments from other people would be useful and would put more light on the subject. – Dragan Bozanovic Aug 22 '16 at 21:32
  • @DraganBozanovic - Ah. My oversight. Is there also a possibility of "SQL injection" when allowing multiple queries? (That, alone, made it a no-no for PHP.) – Rick James Aug 22 '16 at 22:47
  • @RickJames Yes, there is, because it is not possible to use bind variables without `PreparedStatement`s or `CallableStatement`s. – Dragan Bozanovic Aug 22 '16 at 23:18
  • That, alone, should be justification for outlawing multiple statements. – Rick James Aug 22 '16 at 23:27
  • @RickJames Don't listen this guy, he said some nonsense. You **can** use parameters without `PreparedStatement` and `CallableStatement`. `org.hibernate.Query` provides methods for that. Most possible that @DraganBozanovic is confused with pure `JDBC` calls. – Andremoniy Aug 23 '16 at 08:12
  • @Andremoniy `org.hibernate.Query` uses `PreparedStatement`s. – Dragan Bozanovic Aug 23 '16 at 09:39
  • @DraganBozanovic `org.hibernate.Query` doesn't use anything as it is *interface* – Andremoniy Aug 23 '16 at 09:40
  • Thank you all for your contribution, however, what i needed just to have one query with multiple statements to be executed in one call of .executeUpdate(). This is because that other framework allow to execute multiple statements at one query. – Nebras Aug 23 '16 at 09:50
  • @Nebras which framework? What's the purpose? Do you understand what exactly you want to achieve? – Andremoniy Aug 23 '16 at 10:21
  • @Andremoniy I think the question is clear. For more clarification; support that you need to run an HQL script that includes many update, delete or insert statements. This is what i asked for!!! – Nebras Aug 23 '16 at 10:32
  • 2
    @Nebras As I said in my answer, this is not possible with HQL, because Hibernate uses `PreparedStatement`s for this. – Dragan Bozanovic Aug 23 '16 at 11:41
  • @Nebras I believe it is an [XY Problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What problem you are trying to solve with such feature? What is the problem of sending individual query/update separately? You may write a little util to break the long line into multiple HQLs and send them separately though. – Adrian Shum Aug 26 '16 at 05:49
  • @AdrianShum I want to execute script in efficient way and high performance. I supposed that executing a multi-statements script in one `executeUpdate()` is better than calling many `executeUpdate()` for multiple statements, considering number of access times to DB. – Nebras Aug 26 '16 at 07:36
  • 2
    Then the answer is simply a No. If you are going to perform multiple bulk update like this, doing it under Hibernate rarely seems a good choice. Why don't you just do it through pure JDBC then? And, if you are doing bulk update like this, DB roundtrip is rarely the bottleneck (unless you are doing update/delete per entity, for which should be done by changing the entity state/session.delete(), and let Hibernate issue detect the changes and issue the SQLs in batches) – Adrian Shum Aug 26 '16 at 07:41
  • I have tried to elaborate my previous comment in an answer. Feel free to comment if it explain why you do not need such feature :) – Adrian Shum Aug 29 '16 at 06:39

5 Answers5

4

in the same executeUpdate call I want to update records in Table1 and delete records from Table2.

Is that possible?

executeUpdate() executes a single update query. So, no you cannot do it. You have to execute as many update queries as tables to update/delete. Besides, it makes a code cleaner if you separate queries :

  • queries are easier to read and parameters setting is readable and not error-prone.
  • to debug query execution, it would be easier to understand if queries are handled one by one in their own executeUpdate()

It doesn't mean that queries must mandatory be transmited one by one.
Batch Processing is a feature provided by Hibernate to improve performance when you want to execute multiples queries. You have to enable the feature to use it. hibernate.jdbc.batch_size property must be set with a suitable value.

If you are undertaking batch processing you will need to enable the use of JDBC batching. This is absolutely essential if you want to achieve optimal performance. Set the JDBC batch size to a reasonable number (10-50, for example):

hibernate.jdbc.batch_size 20 Hibernate disables insert batching at the JDBC level transparently if you use an identity identifier generator.

Besides from official documentation :

Hibernate disables insert batching at the JDBC level transparently if you use an identity identifier generator.

Nevertheless, in your case, it will be useless because as Dragan Bozanovic explained you update/delete different tables in your queries. So, it would create as many batch executions as queried tables.
So, you should execute each query individually. Just commit() the transaction when you deem it should be :

hql = "update Table1 set prob1=null where id=:id1;"
...
query.setParameter("id1",...);
query.executeUpdate();
hql = "delete from Table2 where id =:id2";
...
query.executeUpdate();
query.setParameter("id2",...);
..
tx.commit();
davidxxx
  • 125,838
  • 23
  • 214
  • 215
3

No, it is not possible, because Hibernate usesPreparedStatements for this (which is good because of bind variables), and PreparedStatements do not support batches consisting of multiple different statements.

PreparedStatement can only batch different combinations of bind variables for one statement, which Hibernate uses for batch inserts/updates when flushing changes in the persistence context (session).

Community
  • 1
  • 1
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
3

In short, what you are looking is something like batching in JDBC. Thich is not provided by Hibernate for Bulk Update query, and I doubt if it will ever be considered for Hibernate.

From my past experience, Batching feature for HQL is rarely useful in real life. It may sound strange that something being useful in SQL+JDBC but not in HQL. I will try to explain.

Usually when we work with Hibernate (or other similar ORM), we work against entities. Hibernate will be responsible to synchronize our entities' state with DB, which is most of the cases that JDBC batching can help in improving performance. However, in Hibernate we do not change individual entity's state by Bulk Update query.

Just give an example, in pseudo-code:

In JDBC, you may do something like (I am trying to mimic what you show in your example):

List<Order> orders = findOrderByUserId(userName);
for (Order order: orders) {
    if (order outstanding quantity is 0) {
        dbConn.addBatch("update ORDER set STATE='C' where ID=:id", order.id);
    } else if (order is after expriation time) {
        dbConn.addBatch("delete ORDER where ID=:id", order.id);
    }
}
dbConn.executeBatch();

Naive translation from JDBC logic to Hibernate may give you something like this:

List<Order> orders = findOrderByUserId(userName);
for (Order order: orders) {
    if (order outstanding quantity is 0) {
        q = session.createQuery("update Order set state='C' where id=:id");
        q.setParameter("id", order.id);
        q.executeUpdate();
    } else if (order is after expriation time) {
        q = session.createQuery("delete Order where id=:id");
        q.setParameter("id", order.id);
        q.executeUpdate();
    }
}

I suspect you think you need the batching feature because you are doing something similar (based on your example, which you use bulk update for individual record). However it is NOT how thing should be done in Hibernate/JPA

(Actually it is better to wrap the persistence layer access through a repository, here I am just simplifying the picture)

List<Order> orders = findOrderByUserId(userName);
for (Order order: orders) {
    if (order.anyOutstanding()) {
        order.complete();    // which internally update the state
    } else if (order.expired) {
        session.delete(order);
    }
}

session.flush();   // or you may simply leave it to flush automatically before txn commit

By doing so, Hibernate is intelligent enough to detect changed/deleted/inserted entities, and make use of JDBC batch to do the DB CUD operations at flush(). More important, this is the whole purpose for ORM: we want to provide behavioral-rich entities to work with, for which internal state change of entities can be "transparently" reflected in persistent storage.

HQL Bulk Update aims for other usage, which is something like one bulk update to DB to affect a lot of records, e.g.:

q = session.createQuery("update Order set state='C' " 
                        + " where user.id=:user_id "
                        + " and outstandingQty = 0 and state != 'C' ");
q.setParameter("user_id", userId);
q.executeUpdate();

There is seldom need for executing a lot of queries in such kind of usage scenario, therefore, overhead of DB round-trip is insignificant, and hence, benefit for and batch processing support for bulk update query is seldom significant.

I cannot omit that there are cases that you really need to issue a lot of update queries which is not appropriate to be done by meaningful entity behavior. In such case, you may want to reconsider if Hibernate is the right tool to be used. You may consider using pure JDBC in such use case so that you have control on how queries are issued.

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
1

The SQL generated by JPA bulk updates/deletes, i.e. calls to javax.persistence.Query.executeUpdate() cannot be batched by Hibernate when passed through to JDBC. @DraganBozanovic and @AdrianShum have already explained this, but to add to their comments: executeUpdate() returns an int (the number of entities updated or deleted) - irrespective of flushing the Hibernate session, how could the int be returned without calling the database immediately and synchronously? The JPQL/HQL/SQL would have to be evaluated client-side, which is not possible because the entities to be bulk updated/deleted may not even have been read into the Hibernate session. Furthermore if the update/delete were not executed on the database immediately, subsequent queries to read in JPA entities could get stale data. Example:

  1. executeUpdate to bulk delete all Customers with ID > 1000.
  2. read Customer entity with ID = 1001.

If the executeUpdate at 1 were allowed to be deferred until after the read at 2, then you get the wrong answer (Customer still exists).

You either need to read the entities in using JPA, update them, and let Hibernate generate the update SQL (which it can batch), or call JDBC directly to perform batch updates.

coder
  • 11
  • 2
0

Why not execute the two queries separately in a Transactional method

By annotating the method with @Transactional, if any of the query fails, the other won't execute.

 @Transactional(propagation = Propagation.REQUIRED, readOnly = false)

 public void executeQuery(Obj1 obj1) {

 String query="update table1 set actualRepaymentAmount=expectedRepaymentAmount,active='Y'  where loanCaseId = '"+caseId+"'";
        sessionFactory.getCurrentSession().createQuery(query).executeUpdate();

 query="update table2 set loanStatus='C' where loanCaseId = '"+caseId+"'";  
    sessionFactory.getCurrentSession().createQuery(query).executeUpdate();

        ...

 }