我正在尝试通过MySQL中的Hibernate解决冲突的并发数据库插入问题。
我有一段代码可以同时由多个线程轻松执行。它正在检查数据库是否存在记录,如果不存在则会插入新记录。对相关的子记录执行相同的insert-if-nonxistent操作。如果两个线程同时尝试持久保存子记录,我会得到一个ConstraintViolationException,因为两个线程在查询时都看不到记录,所以两个线程都试图保存违反唯一约束的相同记录,其中一个失败了。
我正在尝试使用受保护的块同步应用程序级别的查询插入操作,以便线程在查询数据库之前等待另一个线程完成插入记录。但即使我看到同步工作,查询记录仍然不会返回任何结果,即使该记录已在另一个线程中持久存在。因此约束违规仍然发生。
这是一个代码示例:
在每个同步操作中,如果产品不存在,我会尝试保留产品,如果不存在,则尝试保留相关的父产品供应商。
public class UpdateProcessor extends HttpServlet {
// Indicator used for synchronizing read-insert operations
public static Boolean newInsertInProgress = false;
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) {
Session hbSession = null;
Transaction tx = null;
try {
hbSession = HibernateUtils.getNewSession();
UpdateProcessor.waitForInsert(); // if there is an insert in progress, wait for it to finish
UpdateProcessor.notifyInsertStarted(); // obtain lock
tx = hbSession.beginTransaction();
Product existingProduct = findProductBySKU(sku);
if(existingProduct == null) {
Product newProduct = new Product();
newProduct.setSKU(sku);
Supplier existingSupplier = findSupplierByName(name);
if(existingSupplier == null) {
Supplier newSupplier = new Supplier();
newSupplier.setName(name);
db.save(newSupplier);
newProduct.setSupplier(newSupplier);
} else {
newProduct.setSupplier(existingSupplier);
}
db.save(newProduct);
}
tx.commit();
} catch (Exception t) {
// <rollback transaction>
response.sendError(500);
} finally {
// Safeguard to avoid thread deadlock - release lock always, if obtained
if(UpdateProcessor.newInsertInProgress) {
UpdateProcessor.notifyInsertFinished(); // release lock and notify next thread
}
// <close session>
}
}
private static synchronized void waitForInsert() {
if(!UpdateProcessor.newInsertInProgress) {
log("Skipping wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
return;
}
while(UpdateProcessor.newInsertInProgress) {
boolean loggedEntering = false;
if(!loggedEntering) {
log("Entering wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
loggedEntering = true;
}
try {
UpdateProcessor.class.wait();
} catch (InterruptedException e) {}
}
log("Exiting wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
}
private static synchronized void notifyInsertStarted() {
UpdateProcessor.newInsertInProgress = true;
UpdateProcessor.class.notify();
log("Notify start - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
}
private static synchronized void notifyInsertFinished() {
UpdateProcessor.newInsertInProgress = false;
UpdateProcessor.class.notify();
log("Notify finish - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
}
}
同时发出请求后的输出:
Skipping wait - thread 254 - 1478171162713
Notify start - thread 254 - 1478171162713
Entering wait - thread 255 - 1478171162713
Entering wait - thread 256 - 1478171162849
Notify finish - thread 254 - 1478171163050
Exiting wait - thread 255 - 1478171163051
Notify start - thread 255 - 1478171163051
Entering wait - thread 256 - 1478171163051
Error - thread 255:
org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '532-supplier-name-1' for key 'supplier_name_uniq'
保留新的供应商记录仍然在线程255中引发异常,因为违反了唯一约束(id,name)。
为什么SELECT在同步插入后仍然没有返回任何记录?保护锁是否正确避免多插入问题?
答案 0 :(得分:0)
基于Mechkov的答案:
简短回答:我需要在同步的代码段中包含Hibernate会话创建。
答案很长: 受保护的块正确地同步了查询插入块,但问题是即使一个线程完成持久化记录,第二个线程也无法在创建新的Hibernate会话之前看到数据库中的更改。因此并发数据库修改的效果不会立即对所有线程可见。通过在某个其他线程中进行插入后创建会话,可以获得最新的数据库状态。在同步代码中包含会话创建确保了这种情况。