Hibernate:带并行插入的ConstraintViolationException

时间:2013-01-15 16:54:18

标签: spring hibernate transactions parallel-processing

我有一个简单的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)

如何解决这个问题?

2 个答案:

答案 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的内部状态可能与数据库不一致。