使用 Hibernate 和 Spring 实现乐观锁
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19454003/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Implementing Optimistic lock using Hibernate and Spring
提问by Leejoy
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 doc11.3.4. Customizing automatic versioning section.
我试图通过阅读文档11.3.4来实现这一点。自定义自动版本控制部分。
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(); } }
配置文件
<?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 @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 实现
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
提前致谢
回答by Alan Hay
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.
JPA/Hibernate Optimistic 锁定的工作原理是使用一些字段来存储最后修改的版本(例如时间戳、长),然后将会话中实体的版本与数据库中的实体进行比较,以查看是否可以保存更改。
For this to work you need to have a field in your entity annotated with @Version.
为此,您需要在实体中使用@Version 注释一个字段。
See below for an example.
请参阅下面的示例。
http://www.javacodegeeks.com/2012/11/jpahibernate-version-based-optimistic-concurrency-control.html
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.
要使其在 Web 应用程序中工作需要进一步考虑,但是如果两个人加载同一个实体进行编辑,然后一段时间后提交他们的编辑,他们很可能都会成功,除非您使用某种长时间运行的会话,实体正在编辑的表单将在提交、填充和保存时从数据库中重新加载。
e.g. Entity at Revision 1
例如修订版 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.
- 用户 1 加载以进行编辑:修订为 1
- 用户 2 加载以进行编辑:修订为 1
- 用户 2 提交表单:实体(在 r1 处)已加载,字段已绑定,实体已保存:修订版为 2。
- 用户 1 提交表单:加载实体(在 r2),绑定字段,保存实体:修订版为 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.
因此,为此,您可以查看提交一个隐藏字段,其中包含在加载时存储实体修订的表单。因此,在上面的最后一步,当用户 1 提交时,修订字段将重新设置为 1,更新将失败,因为数据库中的记录与 r2 相同。

