2

I'm experiencing a strange behaviour with Hibernate. I've been banging my head against the wall for a while now, and will award any answer which leads to a solution with a +100 bounty.

I have a JAX-RS (Jersey) REST server, with a filter that associates one Hibernate-session per request.

In one request a client POSTs some data which is stored in the database using one session (and one transaction). In a subsequent call, the client tries to GET this entity, but Hibernate can't find it.

Some observations:

  • I can only reproduce this if I run multiple simultaneous clients. I've never managed to reproduce it by running one client at a time.)

  • I can see the entity ID in the database, and if I restart the server, the entity is found by Hibernate as it should.

  • The error does not occur if I use a thread pool of size 1 (regardless of how many clients I run simultaneously).

Here's the code, with some logging:

chargeables.setId(new SecureRandom().nextLong());

System.out.printf("%s,  session: %s  [%s]%n",
                  Thread.currentThread(),
                  System.identityHashCode(session),
                  "session.beginTransaction()");

session.beginTransaction();


System.out.printf("%s,  session: %s  [%s]%n",
                  Thread.currentThread(),
                  System.identityHashCode(session),
                  "session.save(id = "+chargeables.getId()+")");

session.save(chargeables);


System.out.printf("%s,  session: %s  [%s]%n",
                  Thread.currentThread(),
                  System.identityHashCode(session),
                  "session.getTransaction().commit()");

session.getTransaction().commit();

The code for getting the entity:

System.out.printf("%s,  session: %s  [%s]%n",
                  Thread.currentThread(),
                  System.identityHashCode(session),
                  "session.get("+id+")");

Chargeables entity = (Chargeables) session.get(Chargeables.class, id);

if (entity == null)
    System.out.printf("%s,  session: %s  [%s]%n",
                      Thread.currentThread(),
                      System.identityHashCode(session),
                      "ENTITY NOT FOUND!");

Now here is an excerpt of the resulting log (with some additional open/close session output):

Thread[Grizzly(5),5,main],  session:  2041842357  [factory.openSession()]
Thread[Grizzly(5),5,main],  session:  2041842357  [session.beginTransaction()]
Thread[Grizzly(5),5,main],  session:  2041842357  [session.save(id = 7939229356942262438)]
Thread[Grizzly(5),5,main],  session:  2041842357  [session.getTransaction().commit()]
Thread[Grizzly(5),5,main],  session:  2041842357  [session.close()]
[...]
Thread[Grizzly(7),5,main],  session:  1717445911  [factory.openSession()]
Thread[Grizzly(7),5,main],  session:  1717445911  [session.get(7939229356942262438)]
Thread[Grizzly(7),5,main],  session:  1717445911  [ENTITY NOT FOUND!]
Thread[Grizzly(7),5,main],  session:  1717445911  [session.close()]

Why on earth do I reach ENTITY NOT FOUND!?


Hibernate version: 4.1.9.Final
MySQL verison: 14.14 Distrib 5.5.29


Mapping file for Chargeables:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
       "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
       "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping
   default-cascade="all"
   package="se.package.common.chargeables"
   default-lazy="false">


    <class name="Chargeables">

        <id name="id" type="long">
            <generator class="assigned"/>
        </id>

        <property name="startTimeStamp" />
        <property name="endTimeStamp" />

        <list name="chargeables">
            <key column="chargeableId" />
            <list-index column="pos" />
            <many-to-many class="Chargeable"/>
        </list>

    </class>


    <class name="Chargeable">

        <id column="id" type="long">
            <generator class="native"/>
        </id>

        <discriminator />

        <property name="timestamp" />

    </class>


    <subclass name="DataTransfer" extends="Chargeable">
        <property name="bytesSent" />
        <property name="bytesReceived" />
    </subclass>


    <subclass name="TelephonyChargeable" extends="Chargeable">
        <many-to-one name="num" />
    </subclass>

    <subclass name="Call" extends="TelephonyChargeable">
        <property name="duration" />        
    </subclass>

    <subclass name="OutgoingCall" extends="Call" />
    <subclass name="IncomingCall" extends="Call" />

    <subclass name="Message" extends="TelephonyChargeable" />

    <subclass name="Sms" extends="Message" />
    <subclass name="IncomingSms" extends="Sms" />
    <subclass name="OutgoingSms" extends="Sms" />

    <subclass name="Mms" extends="Message" />
    <subclass name="IncomingMms" extends="Mms" />
    <subclass name="OutgoingMms" extends="Mms" />


</hibernate-mapping>
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • Have you turned on SQL logging for Hibernate? Dumb question: what's your ID generation strategy for this entity? – Sofian Djamaa Apr 04 '13 at 09:26
  • I haven't turned on SQL-logging. That's obviously a good idea. The generation strategy is "assigned". – aioobe Apr 04 '13 at 09:31
  • code looks ok. Please show us the annotations you have for Chargeables or provide mapping file. to see what you have there. – sudmong Apr 04 '13 at 09:41
  • Unable to access due to restriction on my network, please add it here. – sudmong Apr 04 '13 at 09:46
  • Answer updated with mapping-xml. – aioobe Apr 04 '13 at 09:48
  • could we have timestamps in logs ? – Gab Apr 04 '13 at 09:50
  • Updated the question with a (possibly) important observation. @Gab, I'll try to print that. – aioobe Apr 04 '13 at 09:53
  • looking into mapping. In the mean time have sysout in code to print the id if the persisted object. use reference returned from session.save(chargeables); and see whats is the id. – sudmong Apr 04 '13 at 09:55
  • are we sure that commit is actually happening? record exists in db after commit. may be some empty catch is letting the error get passed and record in saved. – sudmong Apr 04 '13 at 10:02
  • @sudmong, logging confirms that the ID returned by save equals the ID I assign to the entity. – aioobe Apr 04 '13 at 10:02
  • mapping looks ok too. generator is assigned and id is assigned before save. should be ok. – sudmong Apr 04 '13 at 10:02
  • @sudmong, I believe the commit goes through (but I agree that it is suspicious). I can see the values in the DB, and I'm not catching any exceptions. – aioobe Apr 04 '13 at 10:03
  • Following your observations I'm wondering whether something goes wrong with your session. Even though you have 1 session per thread (assuming you have this option: hibernate.current_session_context_class=thread) you may experiment concurrent access issues in your code. For example if you use Spring with Singleton beans you may have your session (if stored as a class member) modified by another thread. Can you provide a shorthand of your class used to save and retrieve data? – Sofian Djamaa Apr 04 '13 at 11:48
  • Can you confirm that 2nd session is opened after the 1st transaction is commited ? Assuming your isolation level is read_commited – Gab Apr 04 '13 at 12:17
  • @Gab, The second session is opened after the first is committed. (Unless the commit is asynchronous and can return before it is completed.) The ID returned in the first request is used as input to the second request. – aioobe Apr 04 '13 at 12:26
  • @aioobe id is generated before you even call persist, this doesn't ensure anything. I've no idea of system out buffering behavior so don't know if console output is representative here – Gab Apr 04 '13 at 13:01
  • What are doing the multiple clients? Only GET? POST and GET? If you have one client (thread) doing a GET while another thread does a POST you may have concurrency issues as I explained in my comment above ... Is the session a method-scoped variable? What about the ID variable? Can you send the SQL logging? – Sofian Djamaa Apr 04 '13 at 13:14
  • Refined question with an SSCCE available [here](http://stackoverflow.com/questions/16055324/hibernate-mysql-concurrency-issue). – aioobe Apr 17 '13 at 08:38

2 Answers2

0

IMHO it's an isolation problem. Your second session start before the transaction of the 1st one is commited. As default hibernate isolation level is read_commited, the 2nd session can't so retrieve the entity.
How are you passing id between the 2 threads ?

Gab
  • 7,869
  • 4
  • 37
  • 68
  • The client posts some data (the entity) which is persisted on the server. The response of this post is the ID of the persisted entity. In the second request the client tries to fetch this entity by providing the ID received from the previous request. If it is an isolation problem, can I change the isolation level so that it can be confirmed or ruled out? – aioobe Apr 04 '13 at 13:25
  • It's not really standard : http://stackoverflow.com/questions/5234240/hibernatespringjpaisolation-does-not-work. Don'k know which isolation levels are supported by Mysql. Regarding your comment above, isolation problem seems not very probable – Gab Apr 04 '13 at 13:33
  • If it's an isolation level issue I think a timeout would occur as the READ COMMITTED transaction waits for the other one to complete. See this post I've written about a similar issue: http://theoptimizerdiary.com/2013/03/31/csi-database-lock-investigations/ – Sofian Djamaa Apr 04 '13 at 14:32
  • Never seen this case, i suppose it depends on the isolation implementation of the underlying database. theoretically it should not wait anything, but return something missing the uncommited record – Gab Apr 04 '13 at 14:47
0

If you can see the commit in the database, it cannot be an isolation issue at the MySQL level: console operations are also client operations, you don't get special powers when running them, so they conform to the isolation policy you selected.

Looking out for solution, I discovered that Hibernate provide some facilities to cache DB results: there are actually 2 levels of cache implemented there. Perhaps your hibernate installation comes bundled with a cache mechanism?

The first level works on a per session basis, which is consistent with the behaviour you observe when running the code on one thread, and seemingly happens to be activated by default. I am not Hibernate proficient, I can't say that for sure, but my opinion is that your setup has this first level of cache set in read-only mode, while you would like it to be in read-write mode.

Community
  • 1
  • 1
didierc
  • 14,572
  • 3
  • 32
  • 52