我需要一些帮助来管理我刚刚发现的混乱。
我想在我的实体中实现一个软锁定机制。我不想使用Hibernate的锁定功能,因为我的进程是由长时间运行的复合事务组成的。用简短的话来说:我需要在方法运行时将实体标记为Locked
,因此并发调用将阻止运行。
所以我已将 lock 定义为时间戳(将来用于检测锁定超时)列
@Column(name="LOCK_TIME")
private Date lockTime;
到目前为止一切顺利。现在...
@Transactional
public void doSomething(Long entityId){
Object lockInfo = lockManager.acquireLock(entityId); //@Transactional(REQUIRES_NEW)
if (lockInfo == null) // e.g. lock not acquired
throw new ObjectLockedException();
try{
Entity e = entityDao.findById(entityId);
....
entityDao.update(e);
} finally {
lockManager.unlock(lockInfo); //@Transactional(REQUIRES_NEW)
}
}
LockManager实现
@Override
public LockInfo lock(final Class<? extends Lockable> clazz, final Serializable id) throws NotFoundException
{
try
{
return hibernateTemplate.execute(new HibernateCallback<LockInfo>()
{
@Override
public LockInfo doInHibernate(Session session) throws HibernateException, SQLException
{
Lockable object = (Lockable) session.load(clazz, id);
if (object == null)
throw new HibernateException(new NotFoundException(clazz, id));
if (object.getLockTime() != null)
return null;
object.setLockTime(new Date());
session.update(object);
return new LockInfo(clazz, id, object.getLockTime());
}
});
}
catch (HibernateException ex)
{
if (ex.getCause() instanceof NotFoundException)
throw (NotFoundException) ex.getCause();
throw ex;
}
}
@Override
public void unlock(final LockInfo lock) throws NotFoundException, InvalidOperationException, IllegalArgumentException, ObjectNotLockedException
{
try
{
hibernateTemplate.execute(new HibernateCallback<Void>()
{
@Override
public Void doInHibernate(Session session) throws HibernateException, SQLException
{
Lockable object = (Lockable) session.load(lock.getClazz(), lock.getId());
if (object == null)
throw new HibernateException(new NotFoundException(lock.getClazz(), lock.getId()));
if (object.getLockTime() == null || !lock.getLockDate()
.equals(object.getLockTime()))
throw new HibernateException(new ObjectNotLockedException(lock.getId()));
object.setLockTime(null);
session.update(object);
return null;
}
});
}
catch (HibernateException ex)
{
if (ex.getCause() instanceof NotFoundException)
throw (NotFoundException) ex.getCause();
throw ex;
}
}
很少的调试显示我所犯的错误:方法执行后对象仍然被锁定。
我已经考虑过并绘制了一个状态序列:
| Statement | value of e | DB state |
|-----------------------------------|:----------:|-------------:|
| lockManager.acquireLock(entityId) | | lock = null |
| e = entityDao.findById(entityId) | locked | lock != null |
| lockManager.unlock | locked | lock = null |
| commit doSomething | locked | lock != null |
基本上即使entityDao.update(e)
在实体解锁之前(我不会显示lock()
和unlock()
方法,因为它们很简单),实际更新只有在方法结束后发生。由于变量e
拥有自己的锁定信息,而不是数据库的最新版本,因此Hibernate将其用作更新的一部分。在纯SQL中,这种情况永远不会发生,因为你不会碰触。
我发现我设计的锁系统非常糟糕:我想问你如何根据以下要求改进我的设计:
finally
子句)不惜一切代价删除锁我正在考虑使用单独的锁表(entityClass
和entityId
列),但我想知道我的设计模式(锁定列)是否可以调整
答案 0 :(得分:0)
在发布问题之前,我花了一天的时间重新检查我的设计,最终自己找到了解决方案。我想为了后代分享它,以防万一。
我之前的分析(新方法被方法的事务覆盖)是正确的,并且基于这样一个事实,即方法的事务最终在解锁事务之后提交,而tx序列应该是:锁定,处理,解锁
两个简单的代码修改使事情发生:
lockTime
@Column(updatable=false)
字段不可变
这是最后的LockManager
课程
@Override
public LockInfo lock(final Class<? extends Lockable> clazz, final Serializable id) throws NotFoundException
{
try
{
return hibernateTemplate.execute(new HibernateCallback<LockInfo>()
{
@Override
public LockInfo doInHibernate(Session session) throws HibernateException, SQLException
{
Date lockTime = new Date();
Query q = session.createQuery(String.format("update %1s item set item.lockTime = :lock where item.id = :id and item.lockTime is null", clazz.getSimpleName()))
.setDate("lock", lockTime)
.setParameter("id", id);
return q.executeUpdate() > 0 ? new LockInfo(clazz, id, lockTime) : null;
}
});
}
catch (HibernateException ex)
{
if (ex.getCause() instanceof NotFoundException)
throw (NotFoundException) ex.getCause();
throw ex;
}
}
@Override
public void unlock(final LockInfo lock) throws NotFoundException, InvalidOperationException, IllegalArgumentException, ObjectNotLockedException
{
try
{
hibernateTemplate.execute(new HibernateCallback<Void>()
{
@Override
public Void doInHibernate(Session session) throws HibernateException, SQLException
{
Query q = session.createQuery(String.format("update %1s item set item.lockTime = null where item.id = :id and item.lockTime = :lock", lock.getClazz()
.getSimpleName()))
.setDate("lock", lock.getLockDate())
.setParameter("id", lock.getId());
if (q.executeUpdate() == 1)
return null;
else
throw new HibernateException(new ObjectNotLockedException(lock.getId()));
}
});
}
catch (HibernateException ex)
{
if (ex.getCause() instanceof NotFoundException)
throw (NotFoundException) ex.getCause();
throw ex;
}
}
Hibernate会自动通过updatable=false
排除标记为Session
的列。但是如果在不可修改的字段上运行普通的旧HQL语句,则不会注意到