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

Thread: Pessimistic locking, Spring, Hibernate

  1. #1
    Join Date
    Sep 2004
    Posts
    8

    Default 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. #2
    Join Date
    Aug 2004
    Location
    Montréal, Canada
    Posts
    845

    Default

    Please provide some code to produce this case.
    Omar Irbouh

    Spring Modules Team
    http://irbouh.blogspot.com/

  3. #3
    Join Date
    Sep 2004
    Posts
    8

    Default 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 ?

  4. #4
    Join Date
    Sep 2004
    Posts
    8

    Default

    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);
    		}
    				
    	}

  5. #5
    Join Date
    Sep 2004
    Posts
    8

    Default

    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

  6. #6
    Join Date
    Aug 2004
    Posts
    203

    Default

    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

  7. #7
    Join Date
    Sep 2004
    Posts
    8

    Default

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

  8. #8
    Join Date
    Aug 2004
    Posts
    203

    Default

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

    Again, You can create two SessionFactory beans and try

    regards

  9. #9
    Join Date
    Sep 2004
    Posts
    8

    Default

    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

  10. #10
    Join Date
    Aug 2004
    Location
    Montréal, Canada
    Posts
    845

    Default

    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.
    Omar Irbouh

    Spring Modules Team
    http://irbouh.blogspot.com/

Similar Threads

  1. Replies: 8
    Last Post: Aug 12th, 2010, 01:19 PM
  2. Replies: 5
    Last Post: Feb 3rd, 2009, 05:19 AM
  3. Replies: 3
    Last Post: Aug 16th, 2007, 12:10 PM
  4. Replies: 14
    Last Post: Feb 21st, 2005, 05:41 PM
  5. Replies: 7
    Last Post: Aug 21st, 2004, 03:42 AM

Posting Permissions

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