26

In Hibernate Envers, all related collections of an entity are loaded lazily, regardless of what fetch type is set. So when auditquerying for an entity that has a collection of other entities (both audited, of course), the collection is a SetProxy at first (can be seen when debugging).

So, how do I initialize that proxy? Using Hibernate.initialize() has no effect (I suspect because Hibernate and Envers are using different proxy objects). I know I can initialize the set by iterating over its items, but that isn't an option for me because I have multiple collections in an entity and not to mention the maintenance issues.

I need to initialize them eagerly because I'm accessing the collection at a later point in time when the Hibernate session is already closed (converting the domain objects into dtos).

I'm using Hibernate 3.5.6.

Jim Holden
  • 1,156
  • 1
  • 16
  • 36
  • I can't get this sh!t to work either! Envers seems to ignore the fetch plan! I have several entities set to eager fetch with a SELECT fetch mode (because I know they are always in the 2nd level cache - read only and eternal). I am getting lazy initialization errors when rendering the JSPs. PAIN IN THE YOU-KNOW-WHAT. :( Grrr! – les2 Apr 25 '11 at 22:10

3 Answers3

16

Apparently, this is an open issue with Hibernate Envers. There is already an existing issue in their JIRA: https://hibernate.atlassian.net/browse/HHH-3552. Feel free to vote on it, maybe it will speed things up, when they see that there are some people wanting this to be fixed ;)

Until the Envers team fixes this issue, there is a work around which works for me: Calling size() on the collections initializes the proxy objects.

Jim Holden
  • 1,156
  • 1
  • 16
  • 36
4

The best workaround I've found so far for initializing the Envers proxies is to use Dozer. Mapping the audited entity returned by Envers to itself forces the initialization.

For example:

    // Assuming you have an initialized EntityManager in entityManager & 
    // id contains your entity id..

    List<Object[]> auditList = (List<Object[]>)AuditReaderFactory.
                                   get(entityManager).
                                   createQuery().
                                   forRevisionsOfEntity(Foo.class, false, true).
                                   add(AuditEntity.id().eq(id)).
                                   getResultList();

    // Use a singleton in production apps instead...
    DozerBeanMapper mapper = new DozerBeanMapper();

    for(Object[] audit : auditList) {
        audit[0] = mapper.map(audit[0], Foo.class);
    }

    // The proxies in the Foo instances in auditList are now initialized

I'm not very happy with this solution, but I prefer it over initializing the proxies by manually touching the collections. Hope someone comes up with a better alternative or HHH-3552 gets fixed!

Lauri Harpf
  • 1,117
  • 8
  • 23
  • Good solution but watch out for bugs caused by failure to observe correct getter and setter naming for all properties in all classes of the whole object graph. An example error can be for boolean properties, e.g. http://stackoverflow.com/questions/5322648/for-a-boolean-field-what-is-the-naming-convention-for-its-getter-setter – user598656 Apr 01 '14 at 11:51
-3

There's something wrong with your design.

If you need to initialize them inside an interceptor (I suspect Envers works by intercepting hibernate calls), it means that you need to know about your domain model beforehand. Auditing should be a completely independeny concern from domain modeling.

Having this said, you can roll your own initializer using some generic reflection method for iterating the collection, or you can use the Open-Session-In-View pattern and adapt it to work with Envers (ie, inside your interceptor).

Bear in mind that accessing these items will probably trigger other queries, which can be confusing if you analyze the logs.


Edit: It seems that hibernate has fetch profiles, which can let you choose a fetch plan at runtime. See this SO question and the docs.

Community
  • 1
  • 1
Miguel Ping
  • 17,442
  • 22
  • 82
  • 135
  • I am not sure what you mean by "initializing them inside an interceptor". After all, I am getting the domain objects as it is supposed to by the Envers API (executing an AutitQuery via the AuditReader). However, my problem is indeed an open issue (http://opensource.atlassian.com/projects/hibernate/browse/HHH-3552) as I found out later. – Jim Holden Mar 24 '11 at 10:04
  • Got the same issue. How did you get around it in the end? – ndtreviv Sep 19 '11 at 11:51
  • There is some kind of work around. You have to call `size()` on the collections. The Envers proxy objects will then be initialized. – Jim Holden Mar 20 '12 at 12:47