java - 读取/更新时的 Hibernate 并发(?)问题

标签 java database spring hibernate concurrency

情况:

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

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

问题:

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

尝试过的解决方案:

获取当前 id、递增 id 以及更新行的代码均在同一方法中执行。这是 Spring bean 内部的一个方法。由于 Spring bean 是单例,我尝试使该方法同步。这没有帮助。

方法:

@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;
}
}

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

最佳答案

我认为这与 Hibernate 无关。即使你不使用 Hibernate,你仍然会遇到这样的问题。

有一些选择供您选择:

考虑不将 Hibernate 用于相关部分。许多数据库都有执行原子“insert-if-absent-update-if-exists”逻辑的设施。

或者

如果您确实想使用 Hibernate,那么您需要:

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

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

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

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

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

除了我的方法之外,还有其他方法可以解决,但至少,您使用的方法不会有帮助。

关于java - 读取/更新时的 Hibernate 并发(?)问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15198194/

相关文章:

java - 忽略Java中的大写和小写

java - Java中 'throws'语句的用途是什么?

mysql - MySQL 中的 Oracle 序列等效项

MySQL in 和 order by 子句索引

Spring MVC DispatcherServlet 映射/vs/*

java - JHipster/React - 匿名从服务器获取数据(登录前)

java - 有没有一种优雅的方法来打开包裹在 2 个嵌套的 Optionals 中的对象?

java - 文件结构更改时是否需要刷新 Eclipse 中的 Project Explorer Pane(错误或功能)?

mysql - 拥有 "relational database?"是否安全

java - jasper pdf报告生成,传递模型和 View 时状态404