我有一个简单的Hibernate实体:
@Entity
@Table(name = "keyword",
uniqueConstraints = @UniqueConstraint(columnNames = { "keyword" }))
public class KeywordEntity implements Serializable {
private Long id;
private String keyword;
public KeywordEntity() {
}
@Id
@GeneratedValue
@Column(unique = true, updatable=false, nullable = false)
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name="keyword")
public String getKeyword() {
return this.keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
DAO:
@Component
@Scope("prototype")
public class KeywordDao {
protected SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public KeywordEntity findByKeyword(String keyword) throws NotFoundException {
Criteria criteria = sessionFactory.getCurrentSession()
.createCriteria(KeywordEntity.class)
.add(Restrictions.eq("keyword", keyword));
KeywordEntity entity = (KeywordEntity) criteria.uniqueResult();
if (entity == null) {
throw new NotFoundException("Not found");
}
return entity;
}
public KeywordEntity createKeyword(String keyword) {
KeywordEntity entity = new KeywordEntity(keyword);
save(entity);
return entity;
}
}
和服务,将所有内容置于@Transactional
:
@Repository
@Scope("prototype")
public class KeywordService {
@Autowired
private KeywordDao dao;
@Transactional(readOnly = true)
public KeywordEntity getKeyword(String keyword) throws NotFoundException {
return dao.findByKeyword(keyword);
}
@Transactional(readOnly = false)
public KeywordEntity createKeyword(String keyword) {
return dao.createKeyword(keyword);
}
@Transactional(readOnly = false)
public KeywordEntity getOrCreateKeyword(String keyword) {
try {
return getKeyword(keyword);
} catch (NotFoundException e) {
return createKeyword(keyword);
}
}
}
在单线程环境中,此代码运行正常。问题,当我在多线程环境中使用它时。当有许多并行线程时,使用相同的关键字,其中一些是同时使用相同的关键字调用getOrCreateKeyword
并发生以下情况:
2个线程同时使用相同的关键字调用关键字服务,两者都首先尝试获取现有关键字,两者都没有找到,并且两者都尝试创建新关键字。第一个成功,第二个成功 - 导致ConstraintViolationException
被抛出。
所以我尝试稍微改进getOrCreateKeyword
方法:
@Transactional(readOnly = false)
public KeywordEntity getOrCreateKeyword(String keyword) {
try {
return getKeyword(keyword);
} catch (NotFoundException e) {
try {
return createKeyword(keyword);
} catch (ConstraintViolationException ce) {
return getKeyword(keyword);
}
}
}
理论上它应该解决问题,但实际上,一旦抛出ConstraintViolationException
,调用getKeyword(keyword)
会导致另一个Hibernate异常:
AssertionFailure - an assertion failure occured (this may indicate a bug in Hibernate,
but is more likely due to unsafe use of the session)org.hibernate.AssertionFailure:
null id in KeywordEntity entry (don't flush the Session after an exception occurs)
如何解决这个问题?
答案 0 :(得分:2)
您可以使用数据库/ hibernate使用某种悲观锁定机制,或者如果您在一台计算机上运行,则可以使服务方法getOrCreateKeyword()同步。
以下是一些参考资料。
暂停文档http://docs.jboss.org/hibernate/core/3.3/reference/en/html/transactions.html#transactions-locking
本文介绍如何对查询结果中的特定实体和所有实体进行锁定,这可能对您有所帮助。 http://www.objectdb.com/java/jpa/persistence/lock#Locking_during_Retrieval_
答案 1 :(得分:1)
解决方案是在ConstraintViolationException
发生后丢弃当前会话,并在新会话中再次检索该关键字。 Hibernate Documentation也指出了这一点:
如果会话抛出异常,则必须回滚事务并丢弃会话。发生异常后,Session的内部状态可能与数据库不一致。