使用Hibernate和Spring实现乐观锁

8
我正在尝试实现乐观锁定,以避免丢失更新的情况。在我的应用中,当两个用户获取相同的记录并且第一个用户使用一些更改进行更新时,第二个用户看到相同的记录却无法看到第一个人的更改,并自己进行了一些更改并更新了它。由此导致了第一个人的更改被覆盖。为了防止这种情况,我编写了以下代码,但问题仍然存在。我对这个概念还很陌生,无法确定问题所在。
我尝试通过阅读doc 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();
        }
    }
    

提前感谢

1个回答

14
JPA / Hibernate乐观锁通过使用某些字段(例如时间戳,长整型)来存储最后修改的版本,并将会话中实体的版本与数据库中的实体进行比较,以查看是否可以保存更改。
要使此功能正常工作,您需要在实体中注释带有@Version的字段。
请参见下面的示例。

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

"To make this work in a web application, further consideration is required. If two people load the same entity for editing and submit their changes at different times, both may succeed because the edited entity will be reloaded from the database on form submission unless a long running session is used. Here's an example: an entity is at Revision 1. User 1 loads it for editing at Revision 1, then user 2 also loads it for editing at Revision 1. User 2 submits the form, the entity (at r1) is loaded, fields are bound and saved, generating Revision 2. User 1 submits the form, the entity (at r2) is loaded, fields are bound and saved, generating Revision 3. To avoid conflicts, you can add a hidden field to the form that stores the entity revision at the time it was loaded. So when user 1 submits the form, the revision field will be set back to 1 and the update will fail because the record in the database is at Revision 2."

3
我建议避免使用隐藏字段,因为恶意用户可以更改这些值并使系统进入意外状态。为了安全起见,状态应该在服务器端进行维护。请参阅https://www.owasp.org/index.php/Web_Parameter_Tampering。 - Nathan
嗨,Nathan。我熟悉参数篡改的危险性。考虑到用户能够到达此屏幕显然具有编辑权限,你能否建议一下用户篡改版本号的后果? - Alan Hay
除非我漏掉了什么,否则用户没有必要能够将版本号设置为任意值,因此版本号不应从用户输入中读取。服务器应该在会话中维护版本号,而不是让用户改变他们不需要的东西。 - Nathan
当然,你可以将它放在会话中,但我不太喜欢把东西放在会话中。此外,将其放入会话中并不能解决问题:与其编辑隐藏字段,我可以添加一个额外的参数。事实上,在给定的示例中,我不关心用户是否将版本从10更改为100:这是一个没有意义的任意数字。 - Alan Hay
4
@Nathan,对我来说隐藏字段版本号不是问题。你如何防止用户简单地刷新页面,忽略所做的修改,将所有字段设置为它们原来的值并再次提交?你无法防止那些具有写入数据权限的人故意损坏数据。 - plalx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接