简体   繁体   中英

Hibernate concurrency(?) issue when reading/updating

Situation:

We have a webapp (Java EE-Spring-Hibernate) that generates a PDF with a barcode. The barcode contains an ID that is generated at runtime. 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. If one exists it is incremented and updated. There is one row per day containing the following fields: id, coverPageId, date

So when a user creates the first barcode of the day, a row is added for the current date with coverPageId=1. Then all subsequent barcodes will get the last id from the database, increment it and save the row with the incremented value.

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.

Tried Solution:

The code that fetches the current id, increments it and updates the row is all executed in the same method. This is a method inside a Spring bean. Since Spring beans are singletons I tried making the method 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):

@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. Even you are not using Hibernate you will still face such problem.

There are some choices for you:

Consider not using Hibernate for related parts. 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:

  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. Your code is going to DB to check everytime, and do insert/update accordingly. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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