我有一个包含以下字段的类:
@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);
}
}
答案 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个