- Direct Known Subclasses:
- CreateIfNecessaryTemplate
public abstract class UpsertTemplate<T,D extends OnmsDao<T,?>>
extends Object
A UpsertTemplate for handling the update if it exists/insert if it doesn't case in the midst of
concurrent database updates. The pattern for solving this is known as an Upsert.. update or insert.
Example Scenario: During a node scan an interface is found on a node and various information about
this interface is gathered. This information needs to be persisted to the database. There are two
cases:
1. The interface is not yet in the database so it needs to be inserted
2. The interface is already in the database so it needs to be updated
The naive implementation of this is something like the following (this is just pseudo code)
// find an interface in the db matching our scanned info
SnmpInterface dbIf = m_dao.query(scannedIf);
if (dbIf != null) {
// found an if in the db... updated with found info
retrun update(dbIf, scannedIf);
} else {
// no if in the db.. insert the one we found
return insert(scannedIf);
}
Problem: The naive implementation above has the problem that it fails in the midst of concurrency.
Consider the following scenario where two different provisioning threads decide to update/insert
the same interface that does not yet exist in the db:
1 Thread 1 attempts to find the if and finds it is not there
2 Thread 2 attempts to find the if and finds it is not there
3 Thread 1 inserts the if into the database
4 Thread 1 completes and moves onto further work
5 Thread 2 attempts to insert the if into the database and a duplication exception is thrown
6 All work done in Thread 2's transactions is rolled back.
Most people assume the 'transactions' will handle this case but they do not. The reason for this is
because transactions lock the information that is found to ensure that this information is not changed
by others. However when you perform a query that returns nothing there is nothing to lock. So this case
is not protected by the transaction.
Solution: To handle this we must execute the insert in a 'sub transaction' and retry in the event of failure: The
basic loop looks something like the following pseudo code:
while(true) {
SnmpInterface dbIf = m_dao.query(scannedIf);
if (dbIf != null) {
return update(dbIf, scannedIf);
} else {
try {
// start a new sub transaction here that rolls back on exception
return insert(scannedIf)
} catch(Exception e) {
// log failure and let the loop retry
}
}
}
This is simplified loop because it does not show all of the code to start transactions and such nor to it show
the real.
As far as code goes this solution has a great deal of boiler plate code. This class contains this boilerplate
code using the Template Method pattern and provides abstract methods for filling running the query and for doing the
insert and/or the update. To use this class to do the above would look something like this:
final SnmpInterface scannedIf = ...;
return UpsertTemplate(transactionManager) {
- Author:
- brozow