Class UpsertTemplate<T,​D extends OnmsDao<T,​?>>

  • 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
    • Field Summary

      Fields 
      Modifier and Type Field Description
      protected D m_dao  
      protected org.springframework.transaction.PlatformTransactionManager m_transactionManager  
    • Constructor Summary

      Constructors 
      Constructor Description
      UpsertTemplate​(org.springframework.transaction.PlatformTransactionManager transactionManager, D dao)
      Create an UpsertTemplate using the PlatformTransactionManager for creating transactions.
    • Field Detail

      • m_transactionManager

        protected final org.springframework.transaction.PlatformTransactionManager m_transactionManager
      • m_dao

        protected final D extends OnmsDao<T,​?> m_dao
    • Constructor Detail

      • UpsertTemplate

        public UpsertTemplate​(org.springframework.transaction.PlatformTransactionManager transactionManager,
                              D dao)
        Create an UpsertTemplate using the PlatformTransactionManager for creating transactions. This will retry a failed insert no more than two times.
    • Method Detail

      • execute

        public T execute()
        After creating the UpsertTemplate call this method to attempt the upsert.
      • query

        protected abstract T query()
        Override this method to execute the query that is used to determine if there is an existing object in the database
      • doUpdate

        protected abstract T doUpdate​(T dbObj)
        Override this method to update the object in the database. The object found in the query is passed into this method so it can be used to do the updating.
      • doInsert

        protected abstract T doInsert()
        Override this method to insert a new object into the database. This method will be called when the query method returns null. A DataIntegrityViolationException should be thrown if the insert has already occurred. (This is the normal exception thrown when to objects with the same id are inserted).