【问题标题】:Spring Jpa concurrent transaction and stale dataSpring Jpa 并发事务和陈旧数据
【发布时间】:2013-09-10 20:51:55
【问题描述】:

我在我的项目中使用 spring jpa。

我有一个同步方法的服务:

@Service("venditaCustom")
@Transactional
public class VenditaCustomRepositoryImpl implements VenditaCustomRepository {

@Override   
public synchronized <S extends Vendita> S salva(S vendita, Long idPrenotazione)      throws Exception {
    if (idPrenotazione == null || idPrenotazione < 0) {
        return salva(vendita);
    } else {
        return salvaDaPrenotazione(vendita, idPrenotazione);
    }
}

此方法向方法 salva 或 salvaDaPrenotazione 发送请求。

public <S extends Vendita> S salva(S vendita) throws Exception {
....do many operation on object Vendita.....
int numeroFiscale = getNumeroBiglietto();   
...
manager.persist(vendita);
manager.flush();

getNumeroBiglietto() 方法查询最后一个序列号:

private int getNumeroBiglietto() {      
    String sQuery = "SELECT MAX(numero) FROM Biglietto WHERE anno = :anno ";
    Query q = manager.createQuery(sQuery);
    q.setParameter("anno", new GregorianCalendar().get(Calendar.YEAR));
    Integer maxNum = 0;
    try {
        maxNum = (Integer) q.getSingleResult();         
        if (maxNum == null)
            maxNum = 0;
            } catch (NoResultException e) {

        maxNum = 0;
    } catch (Exception e) {
        log.error("", e);
        maxNum = 0;
    }
    if (maxNum == null || maxNum == 0) {
        maxNum = PRIMO_NUMERO_FISCALE_BIGLIETTI;
    } else {
        maxNum++;
    }
    log.trace("maxNum " + maxNum);
    return maxNum;
}

方法salvaDaPrenotazione与salva()非常相似:

    public <S extends Vendita> S salvaDaPrenotazione(S vendita, Long idPrenotazione)     throws Exception {
 ...do many action on object Vendita
int numeroFiscale = getNumeroBiglietto();
.....
manager.persist(vendita);
manager.flush();

问题:我按顺序做这个动作:

  • 使用大数据间接调用方法salvaDaPrenotazion(),因此该方法需要很长时间才能完成(大约7s)
  • 在第一个方法完成之前,我间接调用方法 salva()

你可以看到: - 当您输入方法 salvaDaPrenotazione() 时,您会得到正确的 MAX(numero)(是保存在 db 上的序列的最后一个值) - 在方法salvaDaPrenotazion() 的末尾,在persist 和flush 调用getNumeroBiglietto() 之后,我从db 中获得了正确的更新值 - 当您输入方法 salva() 并且我从 db 获得值 MAX(numero) 时,我的值错误!我有一个过时的值,就像我在方法 salvaDaPrenotazione() 开始时得到的一样,好像这个事务没有看到另一个事务的提交。 所以交易失败了

org.hibernate.exception.ConstraintViolationException

注意:

  • 有一个独特的进入点是

    public synchronized S salva(S vendita, Long idPrenotazione) throws Exception {

并且这个方法是同步的。

  • 事务隔离在 spring jpa 中为 DEFAULT,在 my.cnf 中为 READ_COMMITTED(我使用的是 Mysql)
  • 如果您在第一次失败后重试间接调用方法 salva(),它会起作用!

我不明白问题出在哪里。我认为与缓存问题无关,可能与隔离问题有关。

【问题讨论】:

    标签: java hibernate jpa transactions spring-data


    【解决方案1】:

    您是否尝试将其用于测序?它不会起作用,因为调用该查询的每个进程都将获得相同的值,除非您保证它们仅通过锁定顺序执行。需要对查询进行悲观锁定,这样在第一个进程完成更新之前,其他进程都不能发出相同的查询。
    与其重新发明轮子,不如只使用数据库或 JPA 排序策略,请参阅http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing

    【讨论】:

    • 你说得对,克里斯,我使用的是一个序列,但非常特殊。将我的序列视为发票的财务编号。所以数字必须是连续的,它们之间不能有空洞,首先添加的对象的序列号必须小于之后添加的对象。此外,每年的序列必须从 1 开始。为此,我创建了一个同步方法。无论如何我不明白为什么在第一个请求结束时,当开始第二个请求/事务时,查询 MAX(numero) 返回旧值!
    • 时机。每个事务都是隔离的,在事务完全提交之前,您无法看到事务外部的更改。在发出第二个查询之前检查提交是否已发生。您需要一种方法来强制它在提交之后发生,例如在数据库级别锁定
    • 嗨,克里斯,我认为使用 Spring Data 的 @Transactional 提交是在其拦截器中的我的方法之外进行的。所以我认为最好的办法是手动管理交易的开和关。
    • 在这种情况下会有所帮助。您可能希望输入代码以重试排序逻辑并在发生重复时插入,并使包含插入的事务尽可能快地减少冲突的机会,以防您希望在另一个进程中重用此代码。跨度>
    猜你喜欢
    • 1970-01-01
    • 2019-02-24
    • 2011-07-05
    • 1970-01-01
    • 1970-01-01
    • 2014-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多