我有一个简单的休眠实体:
@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);
}
}
}
所以理论上它应该可以解决问题,但在实践中,一旦被抛出,就会在另一个 Hibernate 异常中ConstraintViolationException
调用结果:getKeyword(keyword)
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)
如何解决这个问题呢?