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?