There are often scenarios in our application wherein we need to perform batch updates. For example if there was a need to update the zip code of all records in Address table a batch update would be a very good way to achieve the same. A single update SQL query would ensure that the job is done at the database level.
However there arise complications in big applications wherein the update operation would be much more complex then a singe update query or the code complexity better handled in java then in a stored procedure.
The Hibernate equivalent would involve
Hibernate provides an alternative to handle this situation ( or else I wouldn't be writing this post :p ) Here too we execute the query to identify the affected objects. However instead of loading all of them into the session, Hibernate provides us with a reference to a database cursor. Instead of retrieving all objects in the result-set we retrieve one at a time. Consider the below code.
Every time we call the get method Hibernate retrieves a single entity and adds it to the session (also the persistence cache) making it available to us. To ensure that no memory issues occur we periodically flush and clear the session (in this case after every 5 reads) The logs indicate the story
However there arise complications in big applications wherein the update operation would be much more complex then a singe update query or the code complexity better handled in java then in a stored procedure.
The Hibernate equivalent would involve
- opening a session,
- executing a select query to get the records to update.
- On each object, run the update logic.
- Then schedule the affected object for a save.
- Once all entities are modified we commit our transaction thus flushing all modifications to the database.
Hibernate provides an alternative to handle this situation ( or else I wouldn't be writing this post :p ) Here too we execute the query to identify the affected objects. However instead of loading all of them into the session, Hibernate provides us with a reference to a database cursor. Instead of retrieving all objects in the result-set we retrieve one at a time. Consider the below code.
static void testBatchUpdate() { final int BATCH_SIZE = 5; final String query = "from Entity where id < :id"; Session session = sessionFactory.openSession(); Transaction transaction = null; ScrollableResults allEntities = null; try { transaction = session.beginTransaction(); allEntities = session.createQuery(query).setLong("id", 7).scroll(); int updateCount = 0; while (allEntities.next()) {//Advance to the next result final Entity entity = (Entity) allEntities.get(0); System.out.println("Updating entity with id - " + entity.getId()); entity.setName("Updated new Name " + entity.getId()); session.update(entity); if (++updateCount % BATCH_SIZE == 0) { System.out.println(" Total records to be flushed as yet : " + updateCount); System.out.println("performing db session flush and clear"); session.flush(); session.clear(); } } session.flush(); session.clear(); transaction.commit(); session.close(); System.out.println("Total queries fired : " + updateCount); } catch (Exception e) { e.printStackTrace(); transaction.rollback(); } }The hero of the story is the ScrollableResults class. The class internally is holding the reference to an open cursor in the database. The HQL query was executed using the Query.scroll() method. Unlike the list() method which fetches the entire result the scroll method returns a ScrollableResults object.
Every time we call the get method Hibernate retrieves a single entity and adds it to the session (also the persistence cache) making it available to us. To ensure that no memory issues occur we periodically flush and clear the session (in this case after every 5 reads) The logs indicate the story
2750 [main] DEBUG org.hibernate.SQL - select entity0_.ID as ID0_, entity0_.NAME as NAME0_ from ENTITY entity0_ where entity0_.ID<? 2890 [main] DEBUG org.hibernate.loader.Loader - Initializing object from Result Set: [com.batch.scrollable.Entity#1] ... 2922 [main] DEBUG org.hibernate.loader.Loader - Initializing object from Result Set: [com.batch.scrollable.Entity#2] ... 2953 [main] DEBUG org.hibernate.loader.Loader - Initializing object from Result Set: [com.batch.scrollable.Entity#3] .. 2953 [main] DEBUG org.hibernate.loader.Loader - Initializing object from Result Set: [com.batch.scrollable.Entity#4] ... 2953 [main] DEBUG org.hibernate.loader.Loader - Initializing object from Result Set: [com.batch.scrollable.Entity#5] ... Total records to be flushed as yet : 5 performing db session flush and clear 2953 [main] DEBUG org.hibernate.event.def.AbstractFlushingEventListener - flush ing session ... 2968 [main] DEBUG org.hibernate.event.def.AbstractFlushingEventListener - Flush ed: 0 insertions, 5 updates, 0 deletions to 5 objects ... 2984 [main] DEBUG org.hibernate.SQL - update ENTITY set NAME=? where ID=? ... 3000 [main] DEBUG org.hibernate.event.def.DefaultFlushEntityEventListener - Upd ating entity: [com.batch.scrollable.Entity#6]An alternative to using ScrollableResults would be to go for a Stateless session.
Great. Thanks.
ReplyDelete