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
In order to use optimistic locking, we need to have an entity including a property with @Version annotation. While using it, each transaction that reads data holds the value of the version property. Before the transaction wants to make an update, it checks the version property again.
With optimistic locking, records are freely given out to whoever wants them. Every record has a version field that can be represented with a unique number, timestamp, or some sort of a hash. Upon a successful save of the record, the version is incremented or updated.
Optimistic locking is a technique for SQL database applications that does not hold row locks between selecting and updating or deleting a row. The application is written to optimistically assume that unlocked rows are unlikely to change before the update or delete operation.
Solution. To resolve this error we have two ways: Get the latest object from the database and set the old object values if you need those values to be persisted to the new object and merge it. For the old object set the latest version from Database.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With