27

This is a design question, concrete code not submitted to protect my bottom.

When working with Hibernate the standard workflow is as follows:

  1. Open Session
  2. Start Transaction
  3. Do the business (read and modify data)
  4. Commit Transaction
  5. Close Session

with possibly iterations through 2-4.

What are reasonable use cases for Session.clear()?

A: The concrete problem I have is a (big) piece of code which loads and modifies entities, then clear()s the session, essentially throwing away the changes that were made. (The business task to be accomplished does not include modifying the entities, so the code "works").

Seems to me the right design would be to make sure the (big) piece of code does not make changes it does not want to save?

B: I would guess Session.clear() exists for convenience/flexibility, not because it is a good idea to use it.

Have I misunderstood the Hibernate philosophy?

C: Subquestion: Is it a bad idea for framework code to unconditionally clear() the session when a task completes? IMHO, the framework should complain if the session is dirty when a task completes! The session should be closed, seeing as the task is done... (Disregarding performance for the minute)

(Labels A, B and C so you can indicate which part you are answering).

braX
  • 11,506
  • 5
  • 20
  • 33

2 Answers2

30

Ad. A: Looks like you are aware what clear() does. The reason to call it explicitly is to remove all managed entities from L1 cache, so that it does not grow infinitely when processing large data sets in one transaction.

It discards all the changes that were made to managed entites not explicitly persisted. This means that you can safely modify an entity, update it explicitly and clear the session. This is the right design. Obviously if no changes are made (long, but read only session), clear() is always safe.

You can also use stateless sessions.

Ad. B: No, it exists for the reasons above: to make sure L1 (session cache) does not grow too much. Of course maintaining it manually is a poor idea and an indication that another tool should be used for large data sets, but sometimes it is a must.

Note that in JPA specification there is also clear() and flush() method. In this case you should always call flush() first to push changes into the database (explicit update) prior to calling clear().

Ad. C: It's actually a good idea to warn the user (maybe by issuing warning message rather than throwing an exception) when he/she clears the session with dirty changes. Also I don't think a framework code should call clear() unconditionally, unless it is sure that the user code it runs flushes or does not make any changes.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • ad A): Processing large datasets in one transaction sounds like bad design to me (granted, I might not have thought of all possible Problems...). What do you mean by "explicitly" persisting an entity? session.save(entity)? Ad C): Sounds like we agree frameworks should not do this - the framework can't possibly be sure what clients do. :-) – Morten Lauritsen Khodabocus Nov 17 '11 at 12:53
  • @MortenLauritsenKhodabocus: obviously, loading and modifying thousands of records in one Hibernate transaction is a sign that a different tool should be used. Yes, by *explicitly* I mean calling `save()` as opposed to letting Hibernate to discover dirty entities. – Tomasz Nurkiewicz Nov 17 '11 at 13:03
  • @MortenLauritsenKhodabocus Please enlighten me. Why would it be a bad idea to process a large read-only data set in one Transaction? It seems to me that if it involved ordering on the DB side, it would be smart because it would save from needing to reorder multiple times. Doesn't using a transaction mean the DB will remember your ordered state across different queries that are part of the same transaction? Also, isn't 'using one Transaction' exactly what Hibernate's ScrollableResults API does? – KyleM Apr 17 '13 at 15:37
3

Here's another reason that I just ran into: caching previous results when calling a stored procedure multiple times within same transaction. Simplified code as follows.

//Begin transaction
SessionFactory sf = HibernateSessionFactory.getFactory();
Session dbSession = sf.getCurrentSession();
dbSession.beginTransaction();

//First call to stored procedure
Query query = dbSession.getNamedQuery("RR_CUST_OPP_DATA");
query.setString("custName", "A");
List<ShipSummaryRow> shipSummaryRows = query.list();

//Second call to stored procedure
Query query = dbSession.getNamedQuery("RR_CUST_OPP_DATA");
query.setString("custName", "B");
List<ShipSummaryRow> shipSummaryRows = query.list();

//Commit both    
dbSession.getTransaction().commit();

Without a clear() after the first call, the resultset rows of the first call are replicated into the resultset of the second call. I'm using Oracle 11gR2.

Key to replicating this bug is to make both calls within the same transaction. Since I'm using open session in view pattern, both calls automatically happen within the same transaction (as the original code calls the proc within a loop storing the results of each). Hence I call it a bug; else could be considered a feature but even then clear() is not called out in code samples stating it should be called. session.flush() did nothing. Mapping file as below. As a result I've added clear() to the end of all my procedure calls. Have not yet tested with my custom SQL calls. This is trivial stuff; surprised the bug exists.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.jfx.rr.model.ShipSummaryRow">
        <id name="id" type="integer"/>
        <property name="shipQtrString" not-null="true" type="string"/>
        <property name="shipAmount" not-null="true" type="double"/>
    </class>
    <sql-query callable="true" name="RR_CUST_OPP_DATA">
        <return class="com.jfx.rr.model.ShipSummaryRow">
            <return-property column="SHIPPED_ID" name="id"/>
            <return-property column="SHIP_QTR" name="shipQtrString"/>
            <return-property column="SHIPPED_AMOUNT" name="shipAmount"/>
        </return>
        { call RR_DASHBOARD_REPORTS_PKG.RR_CUST_OPP_DATA(?, :custName) }
    </sql-query>
</hibernate-mapping>
Anand
  • 113
  • 8