Announcement Announcement Module
Collapse
No announcement yet.
Pessimistic locking, Spring, Hibernate Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Pessimistic locking, Spring, Hibernate

    Hi,

    I'm trying to apply pessimistic locking on a field in one of my DB tables, using PostgreSQL, Spring and Hibernate.

    I tested my locking with two threads trying to read the field (select .. for update), sleep, update it, release the lock .. BUT it doesn't work ..

    The weird thing (to me) is that it works when i use two different processes to update the field.

    The stages are like this :

    ( thread1 = T1, thread2 = T2, process1 = P1, process2 = P2 )

    with threads :
    T1 read
    T2 read
    T1 sleep
    ...
    T1 update
    T1 close the connexion
    T2 NEVER wakes up

    with processes :
    P1 read
    P2 read
    P1 sleep
    ...
    P1 update
    P1 closes the connexion
    P2 wakes up
    P2 sleep
    ...
    P2 updates
    P2 closes the connexion

    So it works with two processes but not with two threads in the same process...

    Does anyone have an idea or need more information (code snippets, .. ) and could help ?

    Thanks in advance,

    Oliver

  • #2
    Please provide some code to produce this case.

    Comment


    • #3
      the code

      So i got a main class TestQuestionCounterDao

      Code:
      package **.****.***.main;
      
      import org.springframework.context.ApplicationContext;
      
      import **.****.***.dao.ApplicationContextFactory;
      import **.****.***.dao.QuestionCounterDao;
      
      /**
       * @author 
       *
       */
      public class TestQuestionCounterDao {
      	
      	static QuestionCounterDao questionCounterDao;
      
      	public static void main(String[] args) {		
      		
      		ApplicationContextFactory.init("ApplicationContext.xml");
      		ApplicationContext context = ApplicationContextFactory.getApplicationContext();
      		questionCounterDao = (QuestionCounterDao) context.getBean("QuestionCounterDao");
      		System.out.println("init");
      		
      		Thread r = new Thread(){
      			public void run(){
      				questionCounterDao.incrementQuestionCounter();
      			}
      		};
      		
      		Thread r2 = new Thread(){
      			public void run(){
      				questionCounterDao.incrementQuestionCounter();
      			}
      		};
      		  
      		r.start();
      		r2.start();
      	}
      }
      And there is my increment method in the QuestionCounterDaoSpringImpl
      (this is only test purposal code, not production code)
      Code:
      public int incrementQuestionCounter() {
      		try {
      			Session session = SessionFactoryUtils.getSession(this.getSessionFactory(),true);
      			session.setFlushMode(FlushMode.AUTO);
      			//binding the resource to the ThreadLocal, so when Spring calls closeSessionIfNecessary(), it doesn't close
      			//it, as it is still bound to the ThreadLocal
      			//don't forget to unbind the resource and to close the session manually then .. 
      			// note : this is done in the webapp by the JBBSessionFilter (JBB's own implementation of Spring's OpenSessionInViewFilter)
      			TransactionSynchronizationManager.bindResource(this.getSessionFactory(), new SessionHolder(session));
      			
      			
      			Query query = this.getHibernateTemplate().createQuery(this.getSession(),"from QuestionCounter qc").setMaxResults(1);
      			query.setLockMode("qc",LockMode.UPGRADE);
      			System.out.println("trying to get counter");
      			QuestionCounter counter = (QuestionCounter) query.uniqueResult();
      			System.out.println("sleep");
      			try {
      				Thread.sleep(5000);
      			} catch (InterruptedException e) {
      				// TODO Auto-generated catch block
      				e.printStackTrace();
      			}
      			System.out.println("increment");
      			counter.increment();
      			System.out.println("update");
      			this.getHibernateTemplate().update(counter);
      			
      			
      			TransactionSynchronizationManager.unbindResource(this.getSessionFactory());
      			//As it is not using an hibernate Transaction directly, it doesn't flush() the Session
      			//so i got to do it myself before closing the Session
      			try {
      				session.flush();
      			} catch (HibernateException e2) {
      				System.out.println("Error while trying to flush the Hibernate Session. The nested Exception is : ");
      				e2.printStackTrace();
      			}
      			SessionFactoryUtils.closeSessionIfNecessary(session, this.getSessionFactory());
      			
      			
      			
      			return counter.getValue();
      		} catch (HibernateException he){
      			throw new RuntimeException("Exception throwed while trying to increment the QuestionCounter",he);
      		}
      }
      Obviously, i do the same trick but with only one thread and run the main two times for the second case ...

      Does that help or do you need other code ?

      Comment


      • #4
        and i believe that my problem is with the way i do it with Spring, because working with Hibernate directly, works ..

        Code:
        public int incrementQuestionCounter() {
        		try {
        			
        			Session session = this.getSession();
        			Transaction tx = session.beginTransaction();
        			
        			Query query = this.getHibernateTemplate().createQuery(this.getSession(),"from QuestionCounter qc").setMaxResults(1);
        			query.setLockMode("qc",LockMode.UPGRADE);
        			System.out.println("trying to get counter");
        			QuestionCounter counter = (QuestionCounter) query.uniqueResult();
        			session.lock(counter,LockMode.UPGRADE);
        			session.refresh(counter);
        			System.out.println("sleep");
        			try {
        				Thread.sleep(5000);
        			} catch (InterruptedException e) {
        				// TODO Auto-generated catch block
        				e.printStackTrace();
        			}
        			System.out.println("increment");
        			counter.increment();
        			System.out.println("update");
        			session.update(counter);
        			
        						tx.commit();
        			session.close();
        			
        			
        			return counter.getValue();
        		} catch (HibernateException he){
        			throw new RuntimeException("Exception throwed while trying to increment the QuestionCounter",he);
        		}
        				
        	}

        Comment


        • #5
          Still no idea ? I've send a mail directly to Juergen Hoeller but haven't received any answer yet ..

          It is really urgent,

          Thank you in advance,

          Oliver

          Comment


          • #6
            You probably work with one session/connection in both threads - when close session spring don't close because another thread use session
            only for example/proof, you try with two session beans (one for thread)

            regards

            Comment


            • #7
              I don't think we use the same session object, as Session objects are ThreadLocal objects (so .. if i understand clearly, it's local to the current Thread...)

              that's why i don't understand ..

              Comment


              • #8
                Maybe You have two sessions, but one connection and it confuse PostgreSQL

                Again, You can create two SessionFactory beans and try

                regards

                Comment


                • #9
                  as I see it, it has more to do with the way spring manages the transactions .. the only change i have to do to make it work is adding

                  Code:
                  Transaction tx = session.beginTransaction()
                  and
                  Code:
                  tx.commit()
                  then it works .. so i imagine that is has something to do with the way spring manages transactions (in my case, i try to use HibernateTransactionManager), just as explained in Rod Johnson and Juergen Hoeller book ...

                  So i could make it work but I rather wish to understand why it still doesn't with spring managing my transactions ..

                  Thank's anyway for your time and answer,

                  Oliver

                  Comment


                  • #10
                    I tried the same thing using Postgresql and Spring declarative transaction demarcation. It worked fine:

                    a. POJO User
                    Code:
                    public class User {
                      private int id = -1;
                      private String name = null;
                      private int age = 0;
                    
                      public User() {
                      }
                    
                      public int getId() { return (this.id); }
                      public void setId(int id) { this.id = id; }
                    
                      public String getName() { return (this.name); }
                      public void setName(String name) { this.name = name; }
                    
                      public int getAge() { return (this.age); }
                      public void setAge(int age) { this.age = age; }
                    
                      public String toString() {
                        StringBuffer buffer = new StringBuffer("User[")
                          .append("id=").append(id)
                          .append(",name=").append(name)
                          .append(",age=").append(age)
                          .append("]");
                    
                        return buffer.toString();
                      }
                    }
                    b. Hibernate mapping
                    Code:
                    <?xml version="1.0" encoding="UTF-8"?>
                    
                    <!DOCTYPE hibernate-mapping PUBLIC
                        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
                        "http&#58;//hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
                    
                    <hibernate-mapping>
                      <class name="User" table="USERS" dynamic-update="false" dynamic-insert="false">
                        <cache usage="read-write"/>
                        <id name="id" column="ID" type="long" unsaved-value="-1">
                          <generator class="native" />
                        </id>
                    
                        <property name="name" column="NAME" type="java.lang.String"  />
                        <property name="age" column="AGE" type="integer" />
                      </class>
                    </hibernate-mapping>
                    c. dataContext.xml
                    Code:
                      <bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
                        <property name="sessionFactory"><ref local="sessionFactory"/></property>
                      </bean>
                    
                      <bean id="txProxyTemplate"
                            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
                            abstract="true">
                        <property name="transactionManager">
                          <ref local="transactionManager"/>
                        </property>
                        <property name="transactionAttributes">
                          <props>
                            <prop key="incrementAge*">PROPAGATION_REQUIRED</prop>
                          </props>
                        </property>
                      </bean>
                    
                      <bean id="userDAO" parent="txProxyTemplate">
                        <property name="target">
                          <bean class="UserDAOImpl">
                            <property name="sessionFactory">
                              <ref local="sessionFactory"/>
                            </property>
                          </bean>
                        </property>
                      </bean>
                    d. UserDAOImpl
                    Code:
                    public class UserDAOImpl extends HibernateDaoSupport implements UserDAO &#123;
                      public void incrementAge&#40;int id&#41; throws DataAccessException &#123;
                        Session session = SessionFactoryUtils.getSession&#40;getSessionFactory&#40;&#41;, false&#41;;
                        Utils.printCollection&#40;TransactionSynchronizationManager.getResourceMap&#40;&#41;.keySet&#40;&#41;&#41;;
                        try &#123;
                           System.out.println&#40;Thread.currentThread&#40;&#41;.getName&#40;&#41; + " inside"&#41;;
                           System.out.println&#40;Thread.currentThread&#40;&#41;.getName&#40;&#41; + " Session &#58; " + session&#41;;
                    
                           System.out.println&#40;Thread.currentThread&#40;&#41;.getName&#40;&#41; + " gettting user &#58; " + id&#41;;
                           User user = &#40;User&#41; session.load&#40;User.class, new Integer&#40;id&#41;, LockMode.UPGRADE&#41;;
                           System.out.println&#40;Thread.currentThread&#40;&#41;.getName&#40;&#41; + " got user &#58; " + user&#41;;
                           user.setAge&#40;user.getAge&#40;&#41;+1&#41;;
                    
                           try &#123;
                           //sleeping
                             System.out.println&#40;Thread.currentThread&#40;&#41;.getName&#40;&#41; + " sleeping..."&#41;;
                             Thread.sleep&#40;5000&#41;;
                    
                           &#125; catch &#40;Exception e&#41; &#123;
                             e.printStackTrace&#40;&#41;;
                           &#125;
                           System.out.println&#40;Thread.currentThread&#40;&#41;.getName&#40;&#41; + " saving..."&#41;;
                           session.save&#40;user&#41;;
                           System.out.println&#40;Thread.currentThread&#40;&#41;.getName&#40;&#41; + " saved"&#41;;
                        &#125; catch &#40;HibernateException e&#41; &#123;
                           e.printStackTrace&#40;&#41;;
                           throw SessionFactoryUtils.convertHibernateAccessException&#40;e&#41;;
                        &#125;
                      &#125;
                    &#125;
                    e. main class
                    Code:
                    public class Testos &#123;
                      private static final String&#91;&#93; locations = &#123;"dataContext.xml"&#125;;
                      private static ApplicationContext app;
                    
                      public static void main&#40;String&#91;&#93; args&#41; throws Exception &#123;
                        app = new FileSystemXmlApplicationContext&#40;locations&#41;;
                    
                        Thread td1 = new Thread&#40;"Thread 1"&#41; &#123;
                          public void run&#40;&#41; &#123;
                            UserDAO userDAO = &#40;UserDAO&#41; app.getBean&#40;"userDAO"&#41;;
                            userDAO.incrementAge&#40;1&#41;;
                          &#125;
                        &#125;;
                    
                        Thread td2 = new Thread&#40;"Thread 2"&#41; &#123;
                          public void run&#40;&#41; &#123;
                            UserDAO userDAO = &#40;UserDAO&#41; app.getBean&#40;"userDAO"&#41;;
                            userDAO.incrementAge&#40;1&#41;;
                          &#125;
                        &#125;;
                    
                        td1.start&#40;&#41;;
                        td2.start&#40;&#41;;
                      &#125;
                    &#125;
                    f. output
                    Code:
                    13&#58;47&#58;53,802  INFO SQLErrorCodesFactory - Database Product Name is PostgreSQL
                    13&#58;47&#58;53,802  INFO SQLErrorCodesFactory - Driver Version is PostgreSQL 7.4.4 JDBC3 with SSL &#40;build 215&#41;
                    13&#58;47&#58;53,992  INFO DataSourceTransactionObject - JDBC 3.0 Savepoint class is available
                    Thread 1 net.sf.hibernate.impl.SessionFactoryImpl&#58; net.sf.hibernate.impl.SessionFactoryImpl@21e554
                    Thread 1 org.apache.commons.dbcp.BasicDataSource&#58; org.apache.commons.dbcp.BasicDataSource@1cffeb4
                    Thread 1 inside
                    Thread 1 Session &#58; net.sf.hibernate.impl.SessionImpl@c3014
                    Thread 1 gettting user &#58; 1
                    Hibernate&#58; select ID, NAME, AGE from DEMOS where ID =? for update
                    Thread 1 got user &#58; User&#91;id=1,name=Taha Irbouh,age=27&#93;
                    Thread 1 sleeping...
                    Thread 2 net.sf.hibernate.impl.SessionFactoryImpl&#58; net.sf.hibernate.impl.SessionFactoryImpl@21e554
                    Thread 2 org.apache.commons.dbcp.BasicDataSource&#58; org.apache.commons.dbcp.BasicDataSource@1cffeb4
                    Thread 2 inside
                    Thread 2 Session &#58; net.sf.hibernate.impl.SessionImpl@8de462
                    Thread 2 gettting user &#58; 1
                    Hibernate&#58; select ID, NAME, AGE from DEMOS where ID =? for update
                    Thread 1 saving...
                    Thread 1 saved
                    Hibernate&#58; update DEMOS set AGE=? where ID=?
                    Thread 2 got user &#58; User&#91;id=1,name=Taha Irbouh,age=28&#93;
                    Thread 2 sleeping...
                    Thread 2 saving...
                    Thread 2 saved
                    Hibernate&#58; update DEMOS set AGE=? where ID=?
                    the output shows that
                    1. we are using the same Hibernate SessionFactory: SessionFactoryImpl@21e554
                    2. threads 1 and 2 use two Hibernate Sessions: SessionImpl@c3014 and SessionImpl@8de462
                    3. Hibernate loads the user using for update: pessimistic locking
                    4. thread 2 waits for thread 1 until it commits

                    we can make thread 2 fail using Hibernate versionning.

                    Comment


                    • #11
                      Thanks for your demonstration, I'll try that as soon as possible and will surely tell you what it did for me .. as far as i see it, the only thing different is inside the spring configuration file ..

                      i hadn't declare the DAO the same way .. I'll try your way and tell you what it changed.

                      Thank's

                      Oliver

                      Comment


                      • #12
                        Thank's for your answer.

                        I don't know if it was a bug in older version .. but as you were using the "abstract" attribute of bean element, I noticed that i was using spring 1.0.2 and not spring 1.1.

                        I tested with your declaration and spring 1.1 and it worked fine.

                        Thank you very much !

                        Oliver

                        Comment

                        Working...
                        X