【问题标题】:Hibernate concurrency(?) issue when reading/updating读取/更新时出现休眠并发(?)问题
【发布时间】:2013-03-04 09:24:14
【问题描述】:

情况:

我们有一个 web 应用程序 (Java EE-Spring-Hibernate) 可以生成带有条形码的 PDF。条形码包含在运行时生成的 ID。我们有一个 Java 服务,它使用 Hibernate 来检查是否有当前日期的 ID,如果不存在,则创建一个条目。如果存在,则递增和更新。 每天有一行包含以下字段:id、coverPageId、日期

因此,当用户创建当天的第一个条形码时,会为当前日期添加一行coverPageId=1。然后所有后续的条形码将从数据库中获取最后一个 id,将其递增并保存具有递增值的行。

问题:

当多个用户同时创建 PDF 时,Hibernate 将为两个用户获取相同的值,从而在 PDF 上生成相同的条形码。

尝试过的解决方案:

获取当前 id、递增和更新行的代码都在同一个方法中执行。这是 Spring bean 中的一个方法。由于 Spring bean 是单例,我尝试使用 synchronized 方法。这没有帮助。

方法:

@Transactional(isolation=Isolation.SERIALIZABLE)
public synchronized long getNextId(Date date)
{
    List<CoverpageID> allCoverpageIds = this.getAllCurrentIds(date);

    if(allCoverpageIds.size() == 0)
    {
        loggingService.warn(String.format("Found no existing ID for date '%tD', creating new record", date));
        System.out.println(String.format("Found no existing ID for date '%tD', creating new record", date));
        return this.createNewCoverpageIdRecord(new Date());
    }
    else if(allCoverpageIds.size() == 1)
    {
        CoverpageID coverpageId = allCoverpageIds.get(0);
        loggingService.debug(String.format("Found existing ID for date '%tD': %d, incrementing and persisting", date, coverpageId.getCoverPageId()));
        System.out.println(String.format("Found existing ID for date '%tD': %d, incrementing and persisting", date, coverpageId.getCoverPageId()));
        coverpageId.setCoverPageId(coverpageId.getCoverPageId() + 1);
        dao.save(coverpageId);
        loggingService.debug(String.format("Saved existing ID for date '%tD' with incremented ID: %d", date, coverpageId.getCoverPageId()));
        return coverpageId.getCoverPageId();
    }
    else
    {
        loggingService.warn(String.format("Found multiple records for date '%tD'", date));
        return -1;
    }
}

private List<CoverpageID> getAllCurrentIds(Date date)
{
    String exClause = "where date = :date";
    Map<String, Object> values = new HashMap<String, Object>();
    values.put("date", date);
    return dao.getAllEx(CoverpageID.class, exClause, values);
}

private long createNewCoverpageIdRecord(Date date)
{
    dao.save(new CoverpageID(new Date(), new Long(1)));
    return 1;
}

CoverpageID.class(条码的实体):

@NamedQueries({
@NamedQuery(
    name = "getCoverPageIdForDate",
    query = "from CoverpageID c where c.date = :date",
    readOnly = true
)})
@Entity
public class CoverpageID
{
private Long id;
private Date date;
private long coverPageId;

public CoverpageID() {}

public CoverpageID(Date date, Long coverPageId)
{
    this.date = date;
    this.coverPageId = coverPageId;
}

public void setId(Long id)
{
    this.id = id;
}

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Long getId()
{
    return id;
}

@Temporal(TemporalType.DATE)
public Date getDate()
{
    return date;
}

public void setDate(Date date)
{
    this.date = date;
}

public long getCoverPageId()
{
    return coverPageId;
}

public void setCoverPageId(long coverPageId)
{
    this.coverPageId = coverPageId;
}
}

有没有人知道如何防止这种并发问题的发生?

【问题讨论】:

    标签: java database spring hibernate concurrency


    【解决方案1】:

    我相信这与 Hibernate 无关。即使你没有使用 Hibernate,你仍然会遇到这样的问题。

    您有一些选择:

    考虑不将 Hibernate 用于相关部分。许多数据库具有执行原子“插入如果不存在更新如果存在”类逻辑的工具。

    或

    如果你真的想使用 Hibernate,这里是你需要的:

    1. 确保您在 DB 中有唯一约束以避免重复记录
    2. 在您的程序逻辑中,如果您发现没有记录并尝试插入,请准备捕获重复数据/违反约束的异常。在这种情况下,不要将您的逻辑视为失败。请改为进行后续更新。

    关于您尝试使用对象实例处理插入/更新操作的解决方案,您的方法行不通。

    首先,如果您实际上将所有 ID 保留为该 bean 的状态,并且如果您的应用程序只有进程,那么它就可以工作。您的代码每次都会到数据库进行检查,并相应地进行插入/更新。即使这段代码是同步的,它也不起作用。请记住,在不同的数据库会话中,一个会话的更改只有在事务实际提交时才对其他会话可见。所以你会有这样的情况

    THREAD 1                    THREAD 2
    -------------------------------------------------
    enter method
    check DB, no record found   enter method (wait coz synchronized)
    insert record
    exit method
                                check DB, no record found (coz prev txn not commited yet)
                                insert record
                                exit method
    commit
                                commit
    

    看到了吗?您仍然面临同样的问题。

    除了我的,还有其他方法可以解决,但至少,你使用的方法没有帮助。

    【讨论】:

    • 即使这不是 Hibernate 的错,我的同步方法也应该可以解决问题,不是吗?一个对象实例(单例),因此所有调用都通过同一个对象的方法。如果已经在处理呼叫,同步应该让所有呼叫者等待......
    • 这不会有帮助。我将更新我的答案。
    • 如果我在方法内部提交事务?
    • 可以,但显然这不是一个好方法。使用事务的一个重要原因是为您的工作单元提供原子性。以这种方式提交将使您的应用程序容易出错且难以维护。但是,实际上您可以尝试另一种类似的解决方案:在其自己的事务中创建“插入/更新”逻辑(独立于您的原始 txn)。但是您必须注意,即使需要回滚整个 txn,这种方法也会使您获得更新的记录。如果这对您来说没问题,这是您可以考虑的一种解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多