简体   繁体   English

读取/更新时休眠并发(?)问题

[英]Hibernate concurrency(?) issue when reading/updating

Situation: 情况:

We have a webapp (Java EE-Spring-Hibernate) that generates a PDF with a barcode. 我们有一个webapp(Java EE-Spring-Hibernate),可以生成带有条形码的PDF。 The barcode contains an ID that is generated at runtime. 条形码包含在运行时生成的ID。 We have a Java service that uses Hibernate to check if there's an ID for the current date, if none exists, an entry is created. 我们有一个Java服务,该服务使用Hibernate来检查当前日期是否存在ID,如果不存在,则创建一个条目。 If one exists it is incremented and updated. 如果存在,则对其进行递增和更新。 There is one row per day containing the following fields: id, coverPageId, date 每天有一行包含以下字段:id,coverPageId,日期

So when a user creates the first barcode of the day, a row is added for the current date with coverPageId=1. 因此,当用户创建当天的第一个条形码时,将为当前日期添加一行,其中coverPageId = 1。 Then all subsequent barcodes will get the last id from the database, increment it and save the row with the incremented value. 然后,所有后续条形码将从数据库中获取最后一个ID,对其进行递增,并使用递增的值保存行。

Problem: 问题:

When multiple users are creating PDF's at the same time, Hibernate will fetch the same value for both users resulting in the same barcode on the PDFs. 当多个用户同时创建PDF时,Hibernate将为两个用户获取相同的值,从而导致PDF上的条形码相同。

Tried Solution: 尝试过的解决方案:

The code that fetches the current id, increments it and updates the row is all executed in the same method. 获取当前ID,对其进行递增并更新该行的代码都以相同的方法执行。 This is a method inside a Spring bean. 这是Spring bean中的一种方法。 Since Spring beans are singletons I tried making the method synchronized . 由于Spring bean是单例,因此我尝试使该方法synchronized This did not help. 这没有帮助。

Method: 方法:

@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 (entity for the barcode): 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;
}
}

Does anyone have any other idea how we can prevent this concurrency issue from happening? 有谁知道我们如何防止发生并发问题?

I believe it has nothing to do with Hibernate. 我相信这与Hibernate无关。 Even you are not using Hibernate you will still face such problem. 即使您没有使用Hibernate,您仍然会遇到这样的问题。

There are some choices for you: 有一些选择供您选择:

Consider not using Hibernate for related parts. 考虑不将Hibernate用于相关部分。 Many DB has facilities to do atomic "insert-if-absent-update-if-exists" kind of logic. 许多数据库具有执行原子性的“如果不存在则进行更新,如果存在”的逻辑。

OR 要么

If you actually want to use Hibernate, here is what you need: 如果您确实想使用Hibernate,则需要以下内容:

  1. Make sure you have unique constraint in DB to avoid duplicated records 确保您在数据库中具有唯一约束,以避免重复记录
  2. In your program logic, if you found there is no record and try to insert, do prepare to catch the exception of duplicated data/constraint violation. 在程序逻辑中,如果发现没有记录并尝试插入,请做好准备以捕获重复数据/约束冲突的异常。 In such case, don't treat your logic as failed. 在这种情况下,请勿将您的逻辑视为失败。 Do an subsequent update instead. 而是进行后续更新。

With respect to your tried solution by using a object instance for handling the insert/update action, your way is not going to work. 对于通过使用对象实例来处理插入/更新操作的已尝试解决方案,您的方法将行不通。

First, if you actually keep all IDs as the state of that bean, and if your application will have only process, then it is going to work. 首先,如果您实际上将所有ID保留为该bean的状态,并且您的应用程序只有一个进程,那么它将正常工作。 Your code is going to DB to check everytime, and do insert/update accordingly. 您的代码将每次都发送给DB进行检查,并相应地进行插入/更新。 Even this piece of code is synchronized, it is not going to work. 即使这段代码是同步的,也无法正常工作。 Please remember that among different DB sessions, changes of one session is only visible to other when the transaction is actually committed. 请记住,在不同的数据库会话之间,只有在实际提交事务后,一个会话的更改才对其他会话可见。 So you will have situation like this 所以你会有这样的情况

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

See? 看到? You are still facing the very same issue. 您仍然面临着同样的问题。

There are other way to solve apart from mine, but at least, the way you are using is not going to help. 除了我的以外,还有其他解决方法,但至少,您使用的方法无济于事。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM