0

I have an Entity that, simplified, looks like this:

@Entity
@Table(name = "SOMETABLE")
public class SomeEntity {

    // real code has id and more columns

    @Column(name = "SOMECOLUMN")
    private String someColumn;

    @Transient
    private SomeObject transientObject;

    // getters and setters
}

A DAO method loads a list of entities by using a @NamedQuery and a JPA EntityManager (roughly stubbed):

@Transactional
public List<SomeEntity> getSomeEntities() {
    TypedQuery<SomeEntity> query = entityManager.createNamedQuery("findSomeEntities", SomeEntity.class);
    List<SomeEntity> someEntities = query.getResultList();
    for (SomeEntity someEntity : someEntities) {
        someEntity.setTransientObject(<some value here>);
        return someEntities;
    }
}

Note that this method also sets transientObject (simplified in the code example).

The next time getSomeEntities() is called, query.getResultList(); returns a list of objects where transientObject is still set. I would expect the transient object to be null, but it is not. There are no first or second level caches enabled.

To further confuse this, this only happens during unit testing, where we use a HSQL in-memory database. When running the web application on a Tomcat server it works fine.

I debugged a bit and I found that in the session cache (which I understand is always enabled for Hibernate) seems to be loaded with all previously loaded objects when running the unit test, but it was empty when running on the application server. I suspect that this means that hibernate fetches the objects from the cache instead of the database.

Also worth to know is that it is a Spring application.

What is the reason for this? Or to rephrase my main problem: Why is transient object not null when loading entity second time using HSQLDB?

Magnilex
  • 11,584
  • 9
  • 62
  • 84
  • So before you call `someEntity.setTransientObject();`, `someEntity.transientObject != null`? – Sotirios Delimanolis Mar 19 '13 at 19:55
  • The first time I call the method, yes. The second time, it is **not** null. And yes, it is **BEFORE** I set it again. Tried to clarify this in the question. – Magnilex Mar 19 '13 at 19:57

3 Answers3

0

Sounds like something to do with the first level cache / session cache.

The first level cache stores all the objects within the context of a session and re-uses it. This does not have an impact when you use the application server since the a new session is started for each transaction.

In other words, each call to your DAO method will cause a new session to be created and this means that the cache is empty.

To resolve the issue, try closing a session and creating a new one before the second call.

You could also try encompassing it within two different transactions. This might also get it to work.

EDIT: Answering question from Magnus

I should really have put it another way. Each hibernate session will closed at the end of a transaction.

According to the Hibernate documenation for Session,

The lifecycle of a Session is bounded by the beginning and end of a logical transaction. (Long transactions might span several database transactions.)

From the perspective of an application server, in the large majority of cases(probably all the cases for practicality), it has no way to identifying a logical transaction which includes multiple physical transactions.

Due to this, it treats each physical transaction as a logical transaction. This means that the sessions is closed at the end of each physical transaction.

In the context of Seam's Conversation Scope and the new Scopes in Java EE 6 like @ViewScoped it is arguable that it is possible to identify a logical transaction that spans multiple physical transactions. However, I don't think it is quite as simple as that and do no believe that it is implemented in that way. However, I have no information to confirm this either way.

drone.ah
  • 1,135
  • 14
  • 28
  • `This does not have an impact when you use the application server since the a new session is started for each transaction.` Why is this? I could actually get rid of the problem by calling `entityManager.clear()` in my tests, but that's not something I want to do there. And the DAO method is transactional, I also verified that it actually is two different transactions. – Magnilex Mar 19 '13 at 20:38
  • @MagnusTengdahl - Edited the answer to include the answer to your questions. Also, clearing the entitymanager is probably more efficient that creating a new session to emulate the App Server – drone.ah Mar 20 '13 at 09:24
  • Thanks for your efforts. It's still not totally clear to me however. :) Your recommendation to me is to actually call `.clear()` in my unit tests to make sure session cache is empty? That's really something I would like to avoid, it's too error prone. Why isn't Hibernate/HSQL handling this since the DAO method actually is encapsulated in a transaction? You probably answered this, but I don't quite get it from your answer. – Magnilex Mar 20 '13 at 09:45
  • The alternative to get a new entity manager (not factory) for each transaction. This will more closely mimic the behaviour within the app server. As for why the app server does it that way, the condensed answer is that's what the Hibernate spec expects it to do. – drone.ah Mar 20 '13 at 09:48
  • Sounds more like what we want to do. Do you know if there is a way to configure this with Spring? – Magnilex Mar 20 '13 at 09:49
  • Of course... The thing is, there shouldn't be a difference in how the EM is wired. It's down at the DAO layer. I don't want to touch that in my tests. Well, thanks for your effort anyway. – Magnilex Mar 20 '13 at 09:56
  • tbf, it may just be a case of incorrect transaction demarcation or transactions not being picked up correctly by spring while testing. It may have a simpler solution. I would ask a question specifically about that to get a more appropriate answer. – drone.ah Mar 20 '13 at 10:17
0

Are you using @Transactional on your test case? If yes,Try removing that.

bornleo
  • 478
  • 3
  • 8
0

This is an effect of the Hibernate first-level cache -- which is always on. The first-level cache provides this behavior:

If you fetch the same row twice from the same Session, you get the same entity instance (ie, with == semantics) from the Session object.

It sounds like you have the same Session for multiple calls to getSomeEntities at test time -- but not at run time. That makes me think that your test cases are differently transacted than your app code.

Adam Maass
  • 931
  • 1
  • 6
  • 6