读取/更新时出现Hibernate并发(?)问题

时间:2013-03-04 09:24:14

标签: java database spring hibernate concurrency

情况:

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

因此,当用户创建当天的第一个条形码时,会为当前日期添加一行,其中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;
}
}

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

1 个答案:

答案 0 :(得分:1)

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

您有一些选择:

考虑不要将Hibernate用于相关部分。许多DB都有设施来执行原子“插入 - 如果不存在 - 更新 - 如果存在”这种逻辑。

OR

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

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

  3. 关于使用对象实例处理插入/更新操作的尝试解决方案,您的方式无法正常工作。

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

    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
    

    请参阅?你仍然面临着同样的问题。

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