我在使用hibernate域类的小型Grails应用程序中发现乐观锁定对我不起作用。域类很简单:
@Entity
@Table(name = "DELME", uniqueConstraints = @UniqueConstraint(columnNames = "ID"))
public class DelmeV implements java.io.Serializable {
private int id;
private Long version;
private String d2;
private String dsc;
// ... constructors goes here
@Id
@Column(name = "ID", nullable = false)
public int getId() {...}
public void setId(int id) {...}
@Version
@Column(name = "VERSION")
public Long getVersion() { ... }
public void setVersion(Long version) { ... }
@Column(name = "D2")
// appropriate getters and setters goes here ...
@Column(name = "DSC")
// appropriate getters and setters goes here ...
}
控制器代码是脚手架:
@Transactional(readOnly = true)
class DelmeVController {
// methods which are out of interest skipped...
@Transactional
def update(DelmeV delmeVInstance) {
if (delmeVInstance == null) {
notFound()
return
}
if (delmeVInstance.hasErrors()) {
respond delmeVInstance.errors, view:'edit'
return
}
delmeVInstance.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'DelmeV.label', default: 'DelmeV'), delmeVInstance.id])
redirect controller:"delmeV", id:delmeVInstance.id
}
'*'{ respond delmeVInstance, [status: OK] }
}
}
}
测试程序如下:
所以,问题1 :上面有什么问题?为什么乐观锁定不适用于我?
好吧,我放弃了默认的乐观锁定行为并写了我自己的(考虑到我们的实际业务表没有'版本'列,悲观锁不是OLTP env中的一个选项): 我将'version'设为瞬态,将其填入onLoad方法并检入preUpdate:
@Entity
@Table(...)
public class DelmeV implements org.hibernate.classic.Lifecycle {
// unisteresting stuff...
@Transient
private String version;
// getters, setters ...
public void onLoad (Session s, java.io.Serializable id) {
if (this != null) {
try {
com.fasterxml.jackson.databind.ObjectMapper om = new com.fasterxml.jackson.databind.ObjectMapper();
om.configure (com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
this.version = om.writeValueAsString (this);
} catch (com.fasterxml.jackson.core.JsonProcessingException e) {
System.out.println ("Jackson exception caught on #" + this.id);
e.printStackTrace();
this.version = "/x/";
}
}
@javax.persistence.PreUpdate
public void actualOnUpdate (Session s) throws StaleObjectStateException {
SessionFactory sessionFactory = s.getSessionFactory();
Session ns = null;
Object newInstance = null;
String dbVersion = null;
try {
ns = sessionFactory.openSession();
Transaction tx = ns.beginTransaction ();
newInstance = ns.get (this.getClass(), this.id);
dbVersion = ((DelmeV) newInstance).getVersion();
tx.commit();
} catch (Throwable ex) {
System.out.println ("Session creation exception caught on " + this.getClass().getName() + ", id=" + this.id);
ex.printStackTrace();
} finally {
ns.close();
}
boolean eq = dbVersion.equals (this.version);
if (!eq) {
throw new StaleObjectStateException (this.getClass().getName(), this.id);
}
}
}
我注册了用于扫描@preUpdate方法的域类的事件侦听器,只要Grails没有自动调用它就调用它。到目前为止这么好,解决方案正在运行,但它恰好与其他框架(Spring Batch,特别是)不兼容,因为JPA要求@preUpdate方法使用空参数列表,我不知道如何找出当前Session或SessionFactory(或者EntityManagerFactory,只要涉及JPA,来自preUpdate方法(在Grails中我将Session作为参数从事件监听器调用preUpdate方法)。那么问题2 :如何以一致的,独立于框架的方式从域方法中找出Session / SessionFactory / EntityManagerFactory?