8

I am trying to implement Optimistic locking in-order to avoid lost update situation. In my application when two user fetch same record and the first user updates it with some changes. This change is not visible to the second user who view the same record and he makes some changes by himself and updates it. Due to which the first persons change is lost. In-order to prevent this I have written the following but still the issue persist. I am new to this concept, not able to identify the issue.

I have tried to achieve this by reading doc 11.3.4. Customizing automatic versioning section.

  • The configuration file

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
    
    <tx:annotation-driven transaction-manager="txManager"/>
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
      <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
      <property name="dataSource" ref="dataSource"/>
      <property name="annotatedClasses">
        <list>
            <value>server.bo.Dept</value>
            <value>server.bo.Emp</value>
        </list>
      </property>
      <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.SQLServer2008Dialect</prop>
            <prop key="hibernate.show_sql">false</prop>
        </props>
      </property>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"/>
      <property name="url" value="${db.url}"/>
      <property name="username" value="${db.username}"/>
      <property name="password" value="${db.password}"/>
    </bean>
    <bean id="deptDAO" class="server.dao.DeptDAOImpl">
      <property name="hibernateTemplate" ref="hibernateTemplate"/>
    </bean>
    </beans>
    
  • Entity Class

    @Entity
    @Table(name = "Dept")
    @org.hibernate.annotations.Entity(dynamicUpdate = true,optimisticLock = OptimisticLockType.ALL)
    public class Dept{
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name = "ID")
        Long id;
    
        @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, mappedBy = "deptId")
        @Fetch(FetchMode.SELECT)
        @OrderBy(value = "id DESC")
        List<Emp> Emplist;
    
        public Dept() {}
        // Getters and setters
    }
    
  • DAO Impl

    public class DeptDAOImpl extends HibernateDaoSupport implements DeptDAO {
        @Transactional(readOnly = true, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
        public Dept getDeptById(Long id) {
                Object param[] = new Object[]{id};
                String  query = "select d from Dept d where d.id=? and d.deleted='0'";
                List<Dept> deptDetailsList = getHibernateTemplate().find(query,param);
                Dept deptDetails = null;
                if(deptDetailsList !=null && deptDetailsList .size()>0)
                    deptDetails = (Dept)deptDetailsList.get(0);
                return deptDetails ;
        }
    
        @Transactional(readOnly = false, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
        public long updateDept(Dept dept) {
                if (dept.getId() == null) { 
                    getSession().save(dept);
                } else {
                    getSession().update(dept);
                }
                if (dept.getEmplist() != null) {
                        final int size = dept.getEmplist().size();
                        for (int i = size - 1; i >= 0; i--) { 
                            Emp emp = dept.getEmplist().get(i);
                            if (emp.getDeptId() == null) {
                                emp.setDeptId(dept.getId());
                        }
                        if (RecordStatus.NEW.equals(emp.getRecordStatus())) {
                            getSession().save(emp);
                        } else if (RecordStatus.DELETED.equals(emp.getRecordStatus())) {
                            getSession().delete(emp);
                        } else if (RecordStatus.MODIFIED.equals(emp.getRecordStatus())) {
                            getSession().update(emp);
                        }
                    }
            }
            return dept.getId();
        }
    }
    

Thanks in advance

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Leejoy
  • 1,356
  • 5
  • 23
  • 36

1 Answers1

14

JPA/Hibernate Optmistic locking works by using some field to store the last modified version (e.g. timestamp, long) and then comparing the version of the entity in the session with the entity in the database to see if the change can be saved.

For this to work you need to have a field in your entity annotated with @Version.

See below for an example.

http://www.javacodegeeks.com/2012/11/jpahibernate-version-based-optimistic-concurrency-control.html

For this to work in a web application requires further thought however as if two people load the same entity for editing and then some time later submit their edits they will likely both succeed as, unless you are using some kind of long running session, the entity being edited will be reloaded from the database on form submit, populated and saved.

e.g. Entity at Revision 1

  • user 1 loads for edit: revision at 1
  • user 2 loads for edit: revision at 1
  • user 2 submits form: entity (at r1) loaded, fields are bound, entity is saved: revision is 2.
  • user 1 submits form: entity (at r2) is loaded, fields are bound, entity is saved: revision is 3.

So for this to work you could look at submitting a hidden field with the form which stores the entity revision at the time it was loaded. So, on the last step above, when user 1 submits, the revision field will be set back to 1 and the update will fail because the record in the DB as at r2.

Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • 3
    I would avoid using hidden fields because a malicious user can change these values and get the system into an unintended state. State should be maintained on the server side for security. See https://www.owasp.org/index.php/Web_Parameter_Tampering – Nathan Jul 08 '14 at 03:35
  • Hi Nathan. I am familiar with the dangers of parameter tampering. Given that a user reaching this screen obviously has permissions to edit, can you suggest any consequences of the user tampering with the version number? – Alan Hay Jul 08 '14 at 07:51
  • Unless I'm missing something, there isn't a requirement for the user to be able to set the version number to an arbitrary value, so the version shouldn't be read from the user input. Rather than give the user the ability to change something they don't need to, the server should maintain the version number in the session (for example). – Nathan Jul 08 '14 at 11:40
  • Well yes you can put it in the session however I don't really like putting things in the session. Besides, putting it in the session does not address the issue: rather than editing a hidden field I can just append an extra param. The fact is in the example given I do not care if the user changes the version from 10 to 100: it is some arbitrary number with no meaning. – Alan Hay Jul 08 '14 at 11:52
  • 4
    @Nathan The hidden field version number is a non-issue to me. How would you prevent the user simply refreshing the page, ignore modifications that were done, set all the fields to what they were and submit again? You cannot prevent someone who have write access to the data to corrupt the data intentionnaly. – plalx Nov 26 '14 at 02:54