Results 1 to 9 of 9

Thread: How to do optimistic locking in Spring + Hibernate?

  1. #1

    Default How to do optimistic locking in Spring + Hibernate?

    Hi, I am new to Spring and Hibernate. I find the very challenging thing in using Spring is configuration and settings. Currently I am doing a
    Spring + Hibernate web application.

    I need to implement optimistic locking to achieve data consistency
    for concurrent users. I have a domain class User. Here is the User.hbm.xml:

    <class name="User" table="USER" optimistic-lock="version">
    <id name="id" type="long" column="ID">
    <meta attribute="scope-set">private</meta>
    <generator class="native"/>
    </id>
    <timestamp name="lastUpdatedDateTime" column="LAST_UPDATED_DATETIME">
    <meta attribute="scope-set">private</meta>
    </timestamp>
    ......


    My web controller class calls a UserManager service class which calls
    a DAO which actually saves the data.

    My web controller class tries to catch OptimisticLockingFailureException, but I did numerous tests and there was never an exception of that class happened. A updated user is always saved without any error. I noticed
    that Hibernate correctly updates timestamp.

    Did I miss something or do something wrong in Spring settings or configurations? How do I do optimistic locking in Spring + Hibernate web
    application?

    Thanks very much for your attention and advice!

    Pete

  2. #2
    Join Date
    Aug 2004
    Location
    Amsterdam, Netherlands
    Posts
    450

    Default

    A timestamp should work, however a version is better. This shouldn't be the problem though.

    Remember, Spring does not do anything special with respect to Hibernate. Have you had a look at the Hibernate doco to see what they say about it?

    regards,
    Alef Arendsen
    Alef Arendsen
    SpringSource
    http://www.springsource.com

  3. #3
    Join Date
    Oct 2004
    Location
    Dallas, TX
    Posts
    20

    Default

    You should check out Hibernate's documentation for the <version> tag. Basically, Hibernate supports a version number or timestamp for its versioning and will manage the <version> values for you.

    There really isn't anything to do in Spring to enable this as it's a Hibernate thing.

  4. #4
    Join Date
    Apr 2009
    Posts
    12

    Default Problem with optimistic locking

    Hi, I am new to Spring and Hibernate.
    I try to use optimistic locking within a Spring MVC Webapplication.
    (I think my problem related to Pete's problem descibed above)

    To realize optimistic locking I added a version column to the customer database table and added a <version>-tag to the hibernate mapping.

    When I try to simulate a lost-update situation the lost update occurs and no exception is thrown

    The version-value is incremented correctly whenever an update occurs.

    The problem is that when I load the same customer in (e.g.) two different browser windows, both FormControllers hold a reference to the same (physical) object.
    when the first browser persists the changes, the version number of the customerObject being referenced by the second Controller "is incremented too" (same object). Therefore there is no reason to throw an exception when the second browser tries to persist the changes.

    above I attached the sourcecode of the (in my opinion) relevant files.


    I hope someone can help me.

    Many thanks in advance.

    Regards

    Thomas

    hibernate mapping:
    Code:
    <class name="Customer" optimistic-lock="version" dynamic-insert="true" dynamic-update="true" >
      	<id name="id" type="long" column="CUSTID">
      		<generator class="increment"/>
      	</id>
      	 
      	<version name="version" access="field" column="version" type="integer"/>
      	
      	<property name="firstname" length="50" type="string"/>
      	<property name="lastname" length="50" type="string"/>
      	
      </class>
    the Customer Class:

    Code:
    package de.uni.erlangen.swtmfisc.exampleapp.domain;
    
    import de.uni.erlangen.swtmfisc.domain.DomainObject;
    
    public class Customer extends DomainObject<Long> {
    	private String firstname;
    	private String lastname;
    	
    	public String getFirstname() {
    		return firstname;
    	}
    	public void setFirstname(String firstname) {
    		this.firstname = firstname;
    	}
    	public String getLastname() {
    		return lastname;
    	}
    	public void setLastname(String lastname) {
    		this.lastname = lastname;
    	}
    	
    	@Override
    	public String toString() {
    		return firstname + " " + lastname + " [" + super.toString() + "]";
    	}
    }
    the DomainObject Class:

    Code:
    package de.uni.erlangen.swtmfisc.domain;
    
    import java.io.Serializable;
    import java.util.Date;
    
    public abstract class DomainObject<I extends Serializable> {
    	private I id;
    	private Integer version;	/* used for optimistic locking */
    	
    	public Integer getVersion() {
    		return version;
    	}
    
    	@Override
    	public boolean equals(Object obj) {
    		if (this == obj) {
    			return true;
    		}
    		if (!(obj instanceof DomainObject)) {
    			return false;
    		}
    		if (this.id == null || ((DomainObject) obj).getId() == null) {
    			return false;
    		}
    		return id.equals(((DomainObject) obj).id);
    	}
    	
    	@Override
    	public int hashCode() {
    		if (id != null) {
    			return id.hashCode();
    		}
    		return super.hashCode();
    	}
    
    	public I getId() {
    		return id;
    	}
    
    	public void setId(I id) {
    		this.id = id;
    	}
    }
    the AbstractHibernateDao (this is the Super-Class of the CustomerHibernateDao being used by the CustomerService to retrieve and store Customer-Objects)

    Code:
    @Transactional	/* All public methods within the class are executed in a separate transaction */
    public abstract class AbstractHibernateDao<T extends DomainObject<I>, I extends Serializable> 
    		extends HibernateDaoSupport implements Dao<T, I> {
    	private final Class<T> objectClass;
    	private final Class<I> keyClass;
    	
    	protected AbstractHibernateDao(final Class<T> objectClass, final Class<I> keyClass) {
    		this.keyClass = keyClass;
    		this.objectClass = objectClass;
    	}
    	
    	protected Class<T> getObjectClass() {
    		return objectClass;
    	}
    
    	protected Class<I> getKeyClass() {
    		return keyClass;
    	}
    	
    	public void insert(final T entity) {
    		if (entity == null) {
    			return;
    		}
    		getHibernateTemplate().saveOrUpdate(entity);
    	}
    	
    	@SuppressWarnings("unchecked")
    	@Transactional(readOnly = true)
    	public final List<T> findAll() {
    		return getHibernateTemplate().loadAll(getObjectClass());
    	}
    
    	@SuppressWarnings("unchecked")
    	public final T findById(I id) {
    		return (T) getHibernateTemplate().get(objectClass, id);
    	}
    	
    	public final void delete(T entity) {
    		getHibernateTemplate().delete(entity);
    	}
    	
    	public final void update(T entity) {
    		if (entity == null) {
    			return;
    		}
    		getHibernateTemplate().update(entity);
    	}
    }

  5. #5
    Join Date
    Mar 2006
    Posts
    312

    Default

    Walk me through exactly how your controller is backing, binding and persisting. Preferably post the related source code.

  6. #6
    Join Date
    Apr 2009
    Posts
    12

    Default

    Hello jglynn

    thanks for your reply.

    The relevant Controller:

    Code:
    package de.uni.erlangen.swtmfisc.exampleapp.web;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /* [...]*/
    
    
    import de.uni.erlangen.swtmfisc.exampleapp.domain.Customer;
    import de.uni.erlangen.swtmfisc.exampleapp.service.CustomerService;
    
    public class EditCustomerController extends SimpleFormController {
    	private CustomerService customerService;
    	private Log logger = LogFactory.getLog(getClass());
    	
    	public CustomerService getCustomerService() {
    		return customerService;
    	}
    
    	public void setCustomerService(CustomerService customerService) {
    		this.customerService = customerService;
    	}
    
    	public EditCustomerController() {
    		setCommandClass(Customer.class);
    		setCommandName("customer");
    		setSessionForm(true);
    	}
    	
    	@Override
    	protected Object formBackingObject(HttpServletRequest request)
    			throws Exception {
    		String custID = request.getParameter("custID");
    		Customer customer = customerService.findCustomerById(Long.parseLong(custID))<D-c>;
    		if (customer != null) {
    			logger.info("version: " + customer.getVersion());
    		}
    		return customer; 
    	}
    	
    	@Override
    	protected ModelAndView onSubmit(HttpServletRequest request,
    			HttpServletResponse response, Object command, BindException errors)
    			throws Exception {
    		Customer customer = (Customer) command;
    		logger.info("before update with version: " + customer.getVersion() + customer);
    		customerService.updateCustomer(customer);
    		logger.info("after update with version: " + customer.getVersion() + customer);
    		return new ModelAndView(getSuccessView());
    	}
    }
    This controller calls a CustomerService to retreive and store a Customer Object
    Customer customer = customerService.findCustomerById(Long.parseLong(cu stID));
    and:
    customerService.updateCustomer(customer);



    The CustomerService-Interface is implemented by CustomerServiceImpl:

    Code:
    package de.uni.erlangen.swtmfisc.exampleapp.service.impl;
    
    import java.util.List;
    
    import de.uni.erlangen.swtmfisc.exampleapp.dao.CustomerDao;
    import de.uni.erlangen.swtmfisc.exampleapp.domain.Customer;
    import de.uni.erlangen.swtmfisc.exampleapp.service.CustomerService;
    
    public class CustomerServiceImpl implements CustomerService {
    	private CustomerDao customerDao;
    	
    	public CustomerDao getCustomerDao() {
    		return customerDao;
    	}
    
    	public void setCustomerDao(CustomerDao customerDao) {
    		this.customerDao = customerDao;
    	}
    
    	public List<Customer> getCustomers() {
    		return customerDao.findAll();
    	}
    
    
    	public Customer findCustomerById(Long custID) {
    		return customerDao.findById(custID);
    	}
    
    	public void createNewCustomer(Customer cust) {
    		customerDao.insert(cust);
    		
    	}
    
    	public void updateCustomer(Customer cust) {	
    		customerDao.update(cust);
    	}
    
    	public void deleteCustomer(Customer cust) {
    		customerDao.delete(cust);
    	}
    }
    This service uses a simple CustomerDAO to retrieve and persist Objects:


    Code:
    package de.uni.erlangen.swtmfisc.exampleapp.dao.hibernate;
    
    import java.util.List;
    
    import org.hibernate.criterion.DetachedCriteria;
    import org.hibernate.criterion.Order;
    import org.hibernate.criterion.Restrictions;
    
    import de.uni.erlangen.swtmfisc.dao.hibernate.AbstractHibernateDao;
    import de.uni.erlangen.swtmfisc.exampleapp.dao.CustomerDao;
    import de.uni.erlangen.swtmfisc.exampleapp.domain.Customer;
    
    public class CustomerHibernateDaoImpl extends AbstractHibernateDao<Customer, Long>
    		implements CustomerDao {
    
    	public CustomerHibernateDaoImpl() {
    		super(Customer.class, Long.class);
    	}
    	
    	@SuppressWarnings("unchecked")
    	public List<Customer> findByFirstName(String firstname) {
    		DetachedCriteria criteria = DetachedCriteria.forClass(getObjectClass());
    	    criteria.add(Restrictions.ilike("firstname", firstname != null ? firstname + "%" : ""));
    	    criteria.addOrder(Order.asc("firstname"));
    	    return getHibernateTemplate().findByCriteria(criteria);
    	}
    
    	@SuppressWarnings("unchecked")
    	public List<Customer> findByFullName(String firstname, String lastname) {
    		DetachedCriteria criteria = DetachedCriteria.forClass(getObjectClass());
    	    criteria.add(Restrictions.ilike("firstname", firstname != null ? lastname + "%" : ""));
    	    criteria.add(Restrictions.ilike("lastname", lastname != null ? lastname + "%" : ""));
    	    criteria.addOrder(Order.asc("lastname"));
    	    criteria.addOrder(Order.asc("firstname"));
    	    return getHibernateTemplate().findByCriteria(criteria);
    	}
    
    	@SuppressWarnings("unchecked")
    	public List<Customer> findByLastName(String lastname) {
    		DetachedCriteria criteria = DetachedCriteria.forClass(getObjectClass());
    	    criteria.add(Restrictions.ilike("lastname", lastname != null ? lastname + "%" : ""));
    	    criteria.addOrder(Order.asc("lastname"));
    	    return getHibernateTemplate().findByCriteria(criteria);
    	}
    }
    The relevant Methods (customerDao.findById(custID); customerDao.update(cust);) are implemented
    in the super-calss of the CustomerHibernateDAO:

    Code:
    package de.uni.erlangen.swtmfisc.dao.hibernate;
    
    import java.io.Serializable;
    import java.util.List;
    
    import org.hibernate.StaleObjectStateException;
    import org.springframework.dao.DataAccessException;
    import org.springframework.dao.OptimisticLockingFailureException;
    import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
    import org.springframework.transaction.annotation.Transactional;
    
    import de.uni.erlangen.swtmfisc.dao.Dao;
    import de.uni.erlangen.swtmfisc.domain.DomainObject;
    
    @Transactional	/* All public methods within the class are executed in a separate transaction */
    public abstract class AbstractHibernateDao<T extends DomainObject<I>, I extends Serializable> 
    		extends HibernateDaoSupport implements Dao<T, I> {
    	private final Class<T> objectClass;
    	private final Class<I> keyClass;
    	
    	protected AbstractHibernateDao(final Class<T> objectClass, final Class<I> keyClass) {
    		this.keyClass = keyClass;
    		this.objectClass = objectClass;
    	}
    	
    	protected Class<T> getObjectClass() {
    		return objectClass;
    	}
    
    	protected Class<I> getKeyClass() {
    		return keyClass;
    	}
    	
    	public void insert(final T entity) {
    		if (entity == null) {
    			return;
    		}
    		getHibernateTemplate().saveOrUpdate(entity);
    	}
    	
    	@SuppressWarnings("unchecked")
    	@Transactional(readOnly = true)
    	public final List<T> findAll() {
    		return getHibernateTemplate().loadAll(getObjectClass());
    	}
    
    	@SuppressWarnings("unchecked")
    	public final T findById(I id) {
    		return (T) getHibernateTemplate().get(objectClass, id);
    	}
    	
    	public final void delete(T entity) {
    		getHibernateTemplate().delete(entity);
    	}
    	
    	public final void update(T entity) {
    		if (entity == null) {
    			return;
    		}
    		getHibernateTemplate().update(entity);
    	}
    }


    These are the my config-files for the application-context:

    Hibernate-Configuration:
    Code:
    <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xsi:schemaLocation="http://www.springframework.org/schema/beans 
               http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
               http://www.springframework.org/schema/tx 
               http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
            
    	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
       		<property name="dataSource" ref="dataSource"/>
       		<property name="mappingResources">
       			<list>
       				<value>/hibernate/customer.hbm.xml</value>
       			</list>
       		</property>   
       		<property name="hibernateProperties">
       			<props>
       				<prop key="hibernate.dialect">
       					${database.dialect}
       				</prop>
       				<prop key="hibernate.show_sql">true</prop>
       				<prop key="hibernate.jdbc.batch_size">100</prop>
       				<prop key="hibernate.hbm2dll.auto">update</prop>
       			</props>
       		</property>
       </bean>  
               
    </beans>
    Persitence Configuration:

    Code:
    <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xsi:schemaLocation="http://www.springframework.org/schema/beans 
               http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
               http://www.springframework.org/schema/tx 
               http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
     	
     	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
          	<property name="driverClassName" value="${database.driver}"/>
    	    <property name="url" value="${database.server}"/>
    	    <property name="username"  value="${database.user}"/>
    	    <property name="password" value="${database.password}"/>
        </bean>
        
    	
        <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        	<property name="locations">
                <list>
                    <value>classpath:/database/localHSQLdatabase.properties</value>
                </list>
        	</property>
        </bean>      
        
       <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
       		<property name="sessionFactory" ref="sessionFactory"/>
       </bean>
    </beans>

  7. #7
    Join Date
    Apr 2009
    Posts
    12

    Default

    Hello jglynn,

    it was my mistake. I used two different browser-tabs to simulate a dirty-update. therefore the two tabs shared one http-session and no optimistic locking exception occured.
    using two different browsers works fine

  8. #8

    Default

    Did you also test the following:

    Perform an update through your application, but set a breakpoint so the update isn't immediately executed.
    Then, directly modify the corresponding record in the database using eq Oracle Sql Developer, incrementing the version number/changing the timestamp and commiting the change, then go back to your webapp, and resume execution.

    Did you then also get an exception ?

  9. #9

    Default

    Did you also test the following:

    Perform an update through your application, but set a breakpoint so the update isn't immediately executed.
    Then, directly modify the corresponding record in the database using eq Oracle Sql Developer, incrementing the version number/changing the timestamp and commiting the change, then go back to your webapp, and resume execution.

    Did you then also get an exception ?

Similar Threads

  1. Replies: 5
    Last Post: Feb 3rd, 2009, 05:19 AM
  2. Replies: 3
    Last Post: Aug 16th, 2007, 12:10 PM
  3. Replies: 14
    Last Post: Feb 21st, 2005, 05:41 PM
  4. Pessimistic locking, Spring, Hibernate
    By beryOli in forum Data
    Replies: 11
    Last Post: Sep 20th, 2004, 05:31 AM
  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
  •