JPA:使用@Version防止同时编辑同一记录的任何完整示例?

时间:2013-06-21 06:16:28

标签: jpa optimistic-locking

我有一个包含以下字段的类:

@Version
private Long version = 1L;

public Long getVersion() {
    return version;
}

public void setVersion(Long version) {
    this.version = version;
}

我使用Spring web + Hibernate。我希望看到一个使用@Version来防止同时编辑同一记录的相对完整的例子。

用Google搜索并搜索了SO。无法找到任何。我在版本字段中使用了隐藏的HTML表单字段,但它无法正常工作。

非常感谢任何信息/指针。

谢谢和问候!

更新1

这就是我所做的。我在两个浏览器窗口中加载了相同的记录。我能够看到两个窗口的HTML表单中都有一个隐藏的版本字段具有相同的值。我在一个窗口中提交了表单并检查了数据库并注意到Version字段已增加。然后我提交了另一个窗口。什么都没发生,记录已成功更新/保存。我期待抛出某种异常。

除版本字段定义外,是否需要配置spring / hibernate组合才能使其正常工作?

更新2

我做了以下内容来跟踪Hibernate如何更新记录:

hibernate.show_sql=true

log4j.logger.org.hibernate.type=trace

现在我可以看到Hibernate如何更新记录。我注意到在Hibernate使用以下SQL

更新记录的那一刻
update ... where id=? and version=?

Hibernate总是使用数据库中的最新版本号进行绑定,即使我可以确认在保存记录之前,在Web层中记录仍然具有旧版本号以及包含新数据的其他字段。

为什么?

我觉得乐观锁定在我的应用程序中以某种方式被禁用,但我没有为此做Hibernate配置。我正在使用Hibernate 4.2.1.Final和Spring 3.2.2.RELEASE。

有没有办法显式启用/禁用Hibernate乐观锁定?

更新3

以下是数据对象和数据库层代码。 “friendGroupDao”自动连接到事务服务层,在该层中调用此DAO对象以将对象加载到Web层并将对象保存到数据库。相关的服务层方法只需调用friendGroupDao.load()和friendGroupDao.save()而无需任何其他代码。

--------------域对象-------------

@MappedSuperclass
public abstract class BaseObject implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Version
    private Long version = 1L;

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
            this.version = version;
    }   

    public BaseObject() {
    }

}

@Entity
public class FriendGroup extends BaseObject {

    @Column(columnDefinition = "NVARCHAR(120)")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String accountName) {
        this.name = accountName;
    }   
}

--------------- database -------------

public interface BaseObjectDao<T extends BaseObject> {

    void delete(T o);

    T load(Long id);

    void save(T o);
}

public class BaseObjectDaoImpl<T extends BaseObject> implements
        BaseObjectDao<T> {

    private Class<T> domainClass;

    @Autowired
    protected SessionFactory sessionFactory;

    public BaseObjectDaoImpl() {
        this.domainClass = (Class<T>) BaseObject.class;
    }

    public BaseObjectDaoImpl(Class<T> domainClass) {
        this.domainClass = domainClass;
    }

    public SessionFactory getSessFactory() {
        return sessionFactory;
    }

    public void setSessFactory(SessionFactory sf) {
        this.sessionFactory = sf;
    }

    public void delete(T object) {
        getSession().delete(object);
    }

    public T load(Long id) {
        return (T) getSession().get(domainClass, id);
    }

    public void save(T object) {    
        getSession().saveOrUpdate(object);
    }

    public Session getSession() {
        return sessionFactory.getCurrentSession();
    }
}

public interface FriendGroupDao extends BaseObjectDao<FriendGroup> {
}

@Repository("friendGroupDao")
public class FriendGroupDaoImpl extends BaseObjectDaoImpl<FriendGroup> implements
    FriendGroupDao {

    public FriendGroupDaoImpl() {
        super(FriendGroup.class);
    }
}

1 个答案:

答案 0 :(得分:3)

以下是如何获得OptimisticLockException的示例 (代码可以用更好/更优雅的方式编写,但出于演示目的,我认为没问题)

    package com.example.jpa;


    import java.util.concurrent.Callable;

    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;

    public class JPASample {

        private EntityManager entityManager;

        private Semaphore semaphore;

        void run(String [] args) throws Exception {
            EntityManagerFactory factory = Persistence.createEntityManagerFactory("JPASample");
            entityManager = factory.createEntityManager();

            try {

                FirstEntity entity = createEntity();

                ReadValueCmd readValueCmd = new ReadValueCmd(entity.getId());
                entityManager.getTransaction().begin();
                FirstEntity e1 = readValueCmd.call();
                entityManager.getTransaction().commit();
                entityManager.detach(e1);

                entityManager.getTransaction().begin();
                FirstEntity e2 = readValueCmd.call();
                entityManager.getTransaction().commit();
                entityManager.detach(e2);

                e1.setValue(e1.getValue()+1);
                e2.setValue(e1.getValue()+1);

                UpdateEntityCmd updateCmd1 = new UpdateEntityCmd(e1);
                entityManager.getTransaction().begin();
                updateCmd1.call();
                entityManager.getTransaction().commit();

                UpdateEntityCmd updateCmd2 = new UpdateEntityCmd(e2);
                entityManager.getTransaction().begin();
                updateCmd2.call();
                entityManager.getTransaction().commit();



            } finally {
                entityManager.close();  
            }       
        }

        public static void main(String[] args) throws Exception{
            new JPASample().run(args);
        }

        private class ReadValueCmd implements Callable<FirstEntity> {

            private long id;

            private ReadValueCmd(long id) {
                this.id = id;
            }

            @Override
            public FirstEntity call() throws Exception {
                FirstEntity entity; 
                entity = entityManager.find(FirstEntity.class, id);
                System.out.println("entity read: " + entity);

                return entity;
            }

        }

        private class UpdateEntityCmd implements Callable<FirstEntity> {

            private FirstEntity entity;

            private UpdateEntityCmd(FirstEntity entity) {
                this.entity = entity;
            }

            @Override
            public FirstEntity call() throws Exception {
                entity = (FirstEntity)entityManager.merge(entity);
                System.out.println("entity merged: " + entity);

                return entity;
            }
        }   



    private FirstEntity createEntity () {
        FirstEntity entity = new FirstEntity();
        entity.setName("initialName");
        entityManager.getTransaction().begin();
        entityManager.persist(entity);
        entityManager.getTransaction().commit();

        return entity;
    }
   }


package com.example.jpa;

import java.io.Serializable;
import java.math.BigInteger;

import javax.persistence.*;

@Entity
public class FirstEntity implements Serializable {


    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long firstEntityId;

    @Version
    private long versionField;

    private long value;

    public long getId() {
        return firstEntityId;
    }

    @Override
    public String toString() {
        return "FirstEntity [firstEntityId=" + firstEntityId
                + ", versionField=" + versionField + ", value=" + value
                + ", name=" + name + "]";
    }

    public long getValue() {
        return value;
    }

    public void setValue(long value) {
        this.value = value;
    }

    private String name;

    public FirstEntity() {
        super();
    }

    public void setName(String newName) {
        name = newName;
    }

    public String getName() {
        return name;
    }

    public long getVersionField() {
        return versionField;
    }

    public void setVersionField(long versionField) {
        this.versionField = versionField;
    }  
}

结果类似于

  

线程“main”中的异常javax.persistence.OptimisticLockException     在   org.hibernate.ejb.AbstractEntityManagerImpl.wrapStaleStateException(AbstractEntityManagerImpl.java:1403)     在   org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1319)     在   org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1300)     在   org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1306)     在   org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:888)     在com.example.jpa.JPASample $ UpdateEntityCmd.call(JPASample.java:124)     在com.example.jpa.JPASample.run(JPASample.java:47)at   com.example.jpa.JPASample.main(JPASample.java:61)引起:   org.hibernate.StaleObjectStateException:行已更新或删除   另一个事务(或unsaved-value映射不正确):   [com.example.jpa.FirstEntity#1] at   org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303)     在   org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)     在   org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)     在org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903)     在org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887)at   org.hibernate.internal.SessionImpl.merge(SessionImpl.java:891)at   org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879)     ......还有3个