/**
   * This is called when a client looks up a key which isn't contained in the grid. The grid then
   * calls this method to fetch the values for the keys. If a key doesn't exist in the backend then
   * Loader.KEY_NOT_FOUND is returned.
   */
  @SuppressWarnings("unchecked")
  public List get(TxID tx, List keys, boolean arg2) throws LoaderException {
    LoaderMBeanImpl mbean =
        WXSUtils.getLoaderMBeanManager()
            .getBean(tx.getSession().getObjectGrid().getName(), mapName);
    try {
      mbean.getGetSizeMetrics().logTime(keys.size());
      long startNS = System.nanoTime();
      // Get a Data instance for these database selects
      Connection conn = getConnection(tx);
      // list for the results
      ArrayList<Object> rc = new ArrayList<Object>();
      Iterator<Object> iter = keys.iterator();
      ValueHolder v = new ValueHolder();

      NamedParameterStatement s = new NamedParameterStatement(conn, selectSQL);
      // for each key in the list
      while (iter.hasNext()) {
        Object keyValue = iter.next();
        Object value = null;
        // wrap with a Key object for purequery if needed
        if (keyAttributeColumn != null) {
          v._wxsutil_value = keyValue;
          copyPojoToStatement(s, v, keyFieldNames, keyFields);
        } else {
          copyPojoToStatement(s, keyValue, keyFieldNames, keyFields);
        }
        ResultSet rs = s.executeQuery();
        if (rs.first()) {
          value = copyResultSetToPojo(rs, theClass, normalFields);
        }
        // if we found the value then add it otherwise add KEY_NOT_FOUND
        if (value != null) {
          rc.add(value);
          if (logger.isLoggable(Level.FINE))
            logger.fine("Found " + keyValue + " in " + tableName + " = " + value);
        } else {
          rc.add(Loader.KEY_NOT_FOUND);
          if (logger.isLoggable(Level.FINE))
            logger.fine("Cant find " + keyValue + " in " + tableName);
        }
      }
      mbean.getGetMetrics().logTime(System.nanoTime() - startNS);
      return rc;
    } catch (Exception e) {
      logger.log(Level.SEVERE, "Unexpected exception in get", e);
      mbean.getGetMetrics().logException(e);
      throw new LoaderException(e);
    }
  }
 /**
  * This is called to write changes at the end of a transaction or a flush to the backend this
  * Loader works with which in this case in a database. We use purequery to flush everything in a
  * single batch statement. Purequery does the POJO attribute to SQL parameter work for us.
  */
 public void batchUpdate(TxID tx, LogSequence ls)
     throws LoaderException, OptimisticCollisionException {
   mapName = ls.getMapName();
   LoaderMBeanImpl mbean =
       WXSUtils.getLoaderMBeanManager()
           .getBean(tx.getSession().getObjectGrid().getName(), ls.getMapName());
   mbean.getBatchSizeMetrics().logTime(ls.size());
   long startNS = System.nanoTime();
   // start a hetero batch.
   // make empty lists for the objects that are inserted or updated or deleted
   ArrayList<LogElement> iList = new ArrayList<LogElement>(1000);
   ArrayList<LogElement> uList = new ArrayList<LogElement>(1000);
   ArrayList<LogElement> dList = new ArrayList<LogElement>(1000);
   try {
     // get the Data instance for this transaction
     Connection conn = getConnection(tx);
     // data.startBatch(HeterogeneousBatchKind.heterogeneousModify__);
     // get the changes in this transaction
     Iterator<LogElement> iter = ls.getPendingChanges();
     // the key object. We may need to wrap primitive POJOs (Long, Integer etc) with a wrapped
     // with a key attribute for purequery.
     while (iter.hasNext()) {
       // get next transaction change
       LogElement e = iter.next();
       switch (e.getType().getCode()) {
         case LogElement.CODE_INSERT: // add to insert POJO list
           iList.add(e);
           break;
         case LogElement.CODE_UPDATE:
           uList.add(e); // add to update POJO list
           break;
         case LogElement.CODE_DELETE: // add to delete POJO list
           dList.add(e);
           break;
       }
     }
     // execute the statements within the batch for each type of operation
     if (iList.size() > 0) {
       if (logger.isLoggable(Level.FINE))
         logger.fine("Inserting " + ls.getMapName() + " " + iList.toString());
       copyPojoListToBatch(
           conn, insertSQL, iList, normalFields, normalFieldNames, keyFields, keyFieldNames);
     }
     if (uList.size() > 0) {
       if (logger.isLoggable(Level.FINE))
         logger.fine("Updating " + ls.getMapName() + " " + uList.toString());
       copyPojoListToBatch(
           conn, updateSQL, uList, normalFields, normalFieldNames, keyFields, keyFieldNames);
     }
     if (dList.size() > 0) {
       if (logger.isLoggable(Level.FINE))
         logger.fine("Deleting " + ls.getMapName() + " " + dList.toString());
       copyPojoListToBatch(
           conn, deleteSQL, dList, normalFields, normalFieldNames, keyFields, keyFieldNames);
     }
     mbean.recordOperationRows(iList.size(), uList.size(), dList.size());
     mbean.getBatchUpdateMetrics().logTime(System.nanoTime() - startNS);
   } catch (SQLException e) {
     logger.log(Level.SEVERE, "SQL exception processing changes", e);
     mbean.getBatchUpdateMetrics().logException(e);
     throw new LoaderException("Map " + ls.getMapName(), e);
   } catch (Exception e) {
     logger.log(Level.SEVERE, "Unexpected exception processing update", e);
     mbean.getBatchUpdateMetrics().logException(e);
     throw new LoaderException("Map " + ls.getMapName(), e);
   }
 }