Announcement Announcement Module
Collapse
No announcement yet.
non-singleton EntityInterceptors Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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
    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.

    Comment


    • #3
      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...

      Comment


      • #4
        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?

        Comment


        • #5
          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

          Comment


          • #6
            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...

            Comment


            • #7
              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.

              Comment


              • #8
                No, you have one interceptor per session so you should be safe.

                Comment


                • #9
                  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, 08:03 PM.

                  Comment


                  • #10
                    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?

                    Comment


                    • #11
                      Yes, I mean a separate database transaction. I have tried to create the second session using the same JDBC connection, but I get the same results. It could be that the session factory that is used below is the 2nd session factory and is not part of the same jdbc connection.

                      Code:
                      Session    currentSession    = sessionFactory.getCurrentSession&#40;&#41;;
                      Connection currentConnection = currentSession.connection&#40;&#41;;
                      Session    tempSession       = sessionFactory.openSession&#40; currentConnection ;
                      As you say, this is probably due to having 2 session factories, each with their own connection. I haven't seen any examples of auditing within Spring and Hibernate that only use a single session factory though. Is it possible to use a single session factory?

                      I'll keep looking at ways to use the connection from the original session factory that is tied to the transaction.

                      Thanks again

                      Bryan

                      Comment


                      • #12
                        I think I have found the problem. Based on your comments I went looking to see which session factory I get injected into my audit code. It turns out that I was getting the 2nd session factory that did not have the correct connection.

                        I have now modified the code to ensure that I pass in the correct connection and everything seems to be working now.

                        Thanks again for pointing me in the right direction.

                        Bryan

                        Comment


                        • #13
                          I know this thread is pretty old, though I couldn't find a simple solution for it and the one I found might save other people some time. Suggested filter one creates new interceptor for each request plus it requires WebApplication context to be applied. In my situation interceptor was implemented in library used by multiple web-app that is why I didn't wanted to apply it in this level. I wanted transaction-based solution independent of web-app. I assume that you are using transaction management org.springframework.orm.hibernate3.HibernateTransa ctionManager.

                          First you have to set scope of your interceptor bean to "prototype".
                          Code:
                          <bean id="myInterceptor"  name="myInterceptor"
                          	class="com.example.MyInterceptor" scope="prototype">
                          </bean>
                          Than, in your transaction manager set "entityInterceptorBeanName" property to your interceptors bean name:
                          Code:
                          <property name="entityInterceptorBeanName" value="myInterceptor" />
                          Thats it! Now you will have new interceptor for each transaction. Don't forget to remove "entityInterceptor" from LocalSessionFactoryBean. If you want to come back to singleton global interceptor or change it to request interceptor you just have to modify scope of your bean. If you want new interceptor by request you just have to change scope to "request" or other (see: http://static.springsource.org/sprin...ch04s04.html):

                          Comment

                          Working...
                          X