Page 1 of 2 12 LastLast
Results 1 to 10 of 13

Thread: non-singleton EntityInterceptors

  1. #1
    Join Date
    May 2005
    Posts
    10

    Default non-singleton EntityInterceptors

    I've seen lots of example code implementing auditing using a Hibernate entity interceptor. But I don't understand why these solutions don't suffer from concurrency problems. My understanding is that unless you use HibernateTransactionManager.setEntityInterceptorBe anName and mark your interceptor bean as a non-singleton, you'll always get the same Interceptor for every Session. This means that if two sessions are flushing simultaneously, the postFlush() Interceptor method will be running concurrently in two different threads. The problem with this is that most Interceptor-based auditing examples I've seen build up Lists of creates, updates, and deletes, then iterator over these Lists in postFlush(). If two Sessions in two different threads are running postFlush() concurrently, it seems to me that you could get duplicate audit log records.

    Q1: Am I correct about this?

    I'd also like to use a Hibernate Interceptor for updating lucene indexes of Hibernate entities. I'd like to use some sort of dirty-tracking, then update all lucene documents in the postFlush() method. But if I'm correct about the Interceptor concurrency problems, this won't work too well.

    I tried setting the entityInterceptorBeanName on the HibernateTransactionManager, but this doesn't seem to work as I am using OpenSessionInView. The HibernateTransactionManager never gets a chance to create a new session since OpenSessionInView has already created one. It just reuses that existing Session, so HibernateTransactionManager never gets a chance to set the entityInterceptor.

    Q2: If I'm using OpenSessionInView, must I set the entityInterceptor on LocalSessionFactoryBean?

    And finally, if I'm correct about Q2:

    Q3: Why isn't there are setEntityInterceptorBeanName on LocalSessionFactoryBean? It seems to me that is the only way I'm going to have a new instance of an entityInterceptor for each Session.

    Q4: Is there some other way to make sure that I get a new instance of the entityInterceptor for each Session?

    Thanks!

    Peter

  2. #2
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    Q4: Is there some other way to make sure that I get a new instance of the entityInterceptor for each Session?
    I haven't used interceptor that much and I don't know if HB creates only an instance per SF or per Session but I believe it's the first one. Here you should use either thread locals or synchronize access on the lists.
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  3. #3
    Join Date
    May 2005
    Posts
    10

    Default

    That's what I'm thinking: that there's only a single Interceptor instance shared across all Hibernate Sessions.

    In this case, ThreadLocal would be the only work around, I think. The problem is that the Lists are populated in the onSave(), onFlushDirty(), and onDelete() methods, and then the Lists are iterated and cleared in postFlush(). Synchronizing on the Lists doesn't solve the problem of the same audit entries being logged multiple times if two Sessions/threads invoke postFlush() at the same time.

    The HibernateTransactionManager has a setEntityInterceptorBeanName method, which is exactly what I need in order to implement Interceptor-per-Session. But, as I described above, HibernateTransactionManager never gets a chance to create a new session (and set the entityInterceptor), since OpenSessionInView has already created a session.

    If there was only a setEntityInterceptorBeanName method in LocalSessionFactoryBean...

  4. #4
    Join Date
    May 2005
    Posts
    10

    Default

    On my walk home, I realized a possible work-around is to subclass OpenSessionInViewFilter. I just did this:

    Code:
    package com.dcxms.web.filters;
    
    import net.sf.hibernate.FlushMode;
    import net.sf.hibernate.Interceptor;
    import net.sf.hibernate.Session;
    import net.sf.hibernate.SessionFactory;
    
    import org.springframework.beans.BeansException;
    import org.springframework.dao.DataAccessResourceFailureException;
    import org.springframework.orm.hibernate.SessionFactoryUtils;
    import org.springframework.orm.hibernate.support.OpenSessionInViewFilter;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    public class MyOpenSessionInViewFilter extends OpenSessionInViewFilter {
    
        private String entityInterceptorBeanName;
    
        public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
            this.entityInterceptorBeanName = entityInterceptorBeanName;
        }
    
        public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
            if (this.entityInterceptorBeanName != null) {
                WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
                if (wac == null)
                    throw new IllegalStateException("Cannot get entity interceptor via bean name without a WebApplicationContext");
                return (Interceptor) wac.getBean(this.entityInterceptorBeanName, Interceptor.class);
            }
            return null;
        }
    
        protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
            Session session = SessionFactoryUtils.getSession(sessionFactory, getEntityInterceptor(), null);
            session.setFlushMode(FlushMode.NEVER);
            return session;
        }
    
    }
    Here's the modified web.xml entry:

    Code:
        <filter>
            <filter-name>hibernate</filter-name>
            <filter-class>
                com.dcxms.web.filters.MyOpenSessionInViewFilter
            </filter-class>
            <init-param>
                <param-name>entityInterceptorBeanName</param-name>
                <param-value>auditLogInterceptor</param-value>
            </init-param>
        </filter>
    I also removed the entityInterceptor from LocalSessionFactoryBean, and added a entityInterceptorBeanName to HibernateTransactionManager. I have some Quartz jobs that call service layer methods, so not all HB Sessions are created from servlet requests.

    Code:
        <bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
            <property name="sessionFactory">
                <ref local="sessionFactory"/>
            </property>
            <property name="entityInterceptorBeanName">
                <value>auditLogInterceptor</value>
            </property>
        </bean>
    and of course, the auditLogInterceptor is marked non-singleton:

    Code:
        <bean id="auditLogInterceptor" class="com.dcxms.auditing.AuditLogInterceptor" singleton="false">
            <property name="auditLog">
                <ref local="auditLog" />
            </property>
        </bean>
    I've confirmed that I am indeed getting new instances of my HB Interceptor in both servlet requests and in the Quartz jobs. So "mission accomplished", maybe.

    I'm a bit concerned about other ways that a HB Session might be had in the application. Clearly, if someone gets a session from the LocalSessionFactoryBean, the entityInterceptor will not get set.

    I'm curious to get feedback about this problem/solution. I haven't seen anyone really talk about the concurrency problems using HB Interceptors with Spring. I'm I missing the obvious solution, or are there lots of potential concurrency bugs in people's HB Interceptors?

  5. #5
    Join Date
    May 2005
    Posts
    10

    Default

    Sorry for the bump, but now I'm really curious.

    In implementing our HB Interceptor based auditing feature, I read many code snippets both in these forums and elsewhere. If I'm correct, all of these code snippets suffer from concurrency problems if the HB Interceptor is a singleton. I've been able to easily duplicate the problem by simultaneously accessing a web application from two different machines. In doing so, I got duplicate audit log records in the database. But, I can't find a single reference to this problem anywhere!

    I know I'm repeating myself here, but here goes:

    We are using Spring 1.1.5. I have confirmed that if you use OpenSessionInViewFilter, an entityInterceptor (or entityInterceptorBeanName) set on the HibernateTransactionManager is never used since transactions will just use the HB Session opened by OSIVF. So one must set the entityInterceptor on LocalSessionFactoryBean. When one does this, the HB Interceptor is a singleton, and is shared by all HB Sessions across all threads.

    Am I way off base, or have I indeed discovered a potential problem with the "standard" auditing code out there?

    Thanks!

    Peter

  6. #6
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    Spring simply passes the interceptor to the HB configuration which reuses the instance for every session.
    As I've said before you have to make sure your interceptors are thread safe and use threadlocals. Another option would be extend the LocalSessionFactoryBean or use AOP and proxy the session factory bean and intercept each openSession request and redirect the call to openSession(interceptor) using your interceptor.
    However I think it's more complicated then the first option. I am working with interceptors but I don't have class level fields...
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  7. #7
    Join Date
    May 2005
    Posts
    10

    Default

    Right, if you are using a singleton HB Interceptor, you must make sure it is thread safe. Can you see any downside to the approach I'm taking above? I'm making sure that each session has it's own instance of an HB Interceptor, obviating the need for thread-safeness.

  8. #8
    Join Date
    Jan 2005
    Location
    Bucharest, Romania
    Posts
    5,403

    Default

    No, you have one interceptor per session so you should be safe.
    Costin Leau
    SpringSource - http://www.SpringSource.com- Spring Training, Consulting, and Support - "From the Source"
    http://twitter.com/costinl
    Please use [ c o d e ] [ / c o d e ] tags

  9. #9
    Join Date
    Aug 2004
    Location
    Wellington, New Zealand
    Posts
    15

    Default

    Peter,

    Thanks for pointing out the concurrency issues with the standard audit logging facility. That would have cost me a lot of time later on. I'll try implementing your solution.

    I have come across an issue with the entity interceptor that I hope others can help me with. I have created an interceptor as discussed in my previous post
    http://forum.springframework.org/showthread.php?t=15682

    The problem I am running into is that the inserts into the audit tables are occurring in a separate transaction. I'm not sure what I am doing wrong to cause this problem.

    I am using the technique of having 2 session factories so that the audit logging does not itself trigger another audit request. Is it possible that this second session factory is not participating in any transactions? The configuration that I have seen only specifies a single HibernateTransactionManager bean injecting the default session factory into it. There does not seem to be any transaction manager specifying the second session factory.

    In the auditLog, I get a connection, save my audit object then flush. As soon as I execute the flush() statement, the database is updated. If the original transaction is rolled back, the audit object is not rolled back.
    Code:
    Session tempSession = sessionFactory.openSession();
    //  create the history object
    tempSession.save( history );
    tempSession.flush();
    Has anybody else seen this behaviour? Any help would be appreciated.

    Thanks,
    Bryan
    Last edited by robyn; May 14th, 2006 at 07:03 PM.

  10. #10
    Join Date
    May 2005
    Posts
    10

    Default

    When you say "separate transaction", I assume you mean a separate database transaction (i.e., JDBC connection)? As opposed to a Spring/JTA transaction?

    I'm pretty sure that using two SessionFactories will result in two separate JDBC connections. I hadn't thought about the ramifications of this before, but I imagine that if the commit on the first JDBC connection (where the Hibernate entities were flushed) fails, the commit on the second JDBC connection could succeed (the audit log records). If that's true, it's obviously not good.

    That's probably why most (if not all) of the Hibernate examples I've seen create the second session using the same JDBC connection as the first session, which avoids this database transaction synchronization problem.

    I'm still wrapping my head around the Spring Transaction stuff, so I can't think of a clear way of synchronizing the database transactions on the two sessions, if that's even possible.

    Is this the problem you are concerned about, or a different one?

Similar Threads

  1. Is a 'singleton' flow a bad idea?
    By akw in forum Web Flow
    Replies: 4
    Last Post: Oct 6th, 2005, 01:16 AM
  2. Replies: 4
    Last Post: Oct 5th, 2005, 11:04 AM
  3. EHCaching Hibernate
    By dencamel in forum Data
    Replies: 3
    Last Post: Sep 6th, 2005, 09:03 PM
  4. Replies: 6
    Last Post: May 25th, 2005, 01:56 AM
  5. DefaultAdvisorAutoProxyCreator skipping beans
    By youngm in forum Container
    Replies: 6
    Last Post: Apr 12th, 2005, 04:29 PM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •