1

我有一个简单的休眠实体:

@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)

如何解决这个问题呢?

4

2 回答 2

2

您可以使用某种悲观锁定机制来使用数据库/休眠,或者如果您在单台机器上运行,您可以使服务方法 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_

于 2013-01-15T17:11:17.997 回答
1

解决方案是一旦发生就丢弃当前会话ConstraintViolationException并在新会话中再次检索关键字。Hibernate Documentation也指出了这一点:

如果 Session 抛出异常,则必须回滚事务并丢弃会话。异常发生后,Session的内部状态可能与数据库不一致。

于 2013-01-29T09:21:22.917 回答