1

情况:

我们有一个 webapp (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;
}
}

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

4

1 回答 1

1

我相信它与休眠无关。即使你不使用 Hibernate,你仍然会面临这样的问题。

有一些选择供您选择:

考虑不要对相关部分使用 Hibernate。许多数据库具有执行原子“插入如果不存在更新如果存在”类型的逻辑的设施。

或者

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

  1. 确保您在数据库中有唯一约束以避免重复记录
  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

看?你仍然面临同样的问题。

除了我的之外,还有其他方法可以解决,但至少,您使用的方式无济于事。

于 2013-03-04T09:56:40.420 回答