Google

Feb 22, 2013

Handling Concurrent modifications in Java



There are scenarios where you need to deal with concurrent modifications in Java. Here are 2 scenarios that I can currently think of.

Scenario 1: Looping through a list of items and removing an item in the list could lead to "ConcurrentModificationException". Here is an example.

Code that throws an Exception:


 private void removeDetailSummaryRecordsWithAllZeroAmounts(CashForecastSummaryVO cfVo)
    {
        List<cashforecastsummaryaccountvo> accounts = cfVo.getAccounts();
        for (CashForecastSummaryAccountVO cfAcctVO : accounts)
        {
            List<cashforecastsummaryrecordvo> summaryRecords = cfAcctVO.getSummaryRecords();
          
            for (CashForecastSummaryRecordVO recordVO:summaryRecords)
            {
                if (recordVO.getRecordtype() == RecordType.DETAILS)
                {
                    List<bigdecimal> amounts = recordVO.getAmounts();
                    boolean foundNonZero = false;
                    for (BigDecimal amount : amounts)
                    {
                        if (BigDecimal.ZERO.compareTo(amount) != 0)
                        {
                            foundNonZero = true;
                        }
                    }
                    
                    if (!foundNonZero)
                    {
                        summaryRecords.remove(recordVO); // throws aoncurrentModificationException
                    }
                }
            }
        }
    }


Code that fixes the above issue: Using an iterator and remove from the iterator to prevent the exception.


 private void removeDetailSummaryRecordsWithAllZeroAmounts(CashForecastSummaryVO cfVo)
    {
        List<cashforecastsummaryaccountvo> accounts = cfVo.getAccounts();
        for (CashForecastSummaryAccountVO cfAcctVO : accounts)
        {
            List<cashforecastsummaryrecordvo> summaryRecords = cfAcctVO.getSummaryRecords();
            Iterator<cashforecastsummaryrecordvo> it = summaryRecords.iterator();  // get the iterator
            CashForecastSummaryRecordVO recordVO = null;
            while (it.hasNext())
            {
                recordVO = it.next();
                if (recordVO.getRecordtype() == RecordType.DETAILS)
                {
                    List<bigdecimal> amounts = recordVO.getAmounts();
                    boolean foundNonZero = false;
                    for (BigDecimal amount : amounts)
                    {
                        if (BigDecimal.ZERO.compareTo(amount) != 0)
                        {
                            foundNonZero = true;
                        }
                    }
                    
                    if (!foundNonZero)
                    {
                        it.remove();  // an iterator is used
                    }
                }
            }
        }
    }
 

Scenario 2: Two users try to modify the same record in the database. In this scenario, you want one modification to go through and the other modification to notify the user as shown below.

"This record was not updated as the record you are trying to update has been updated by another user. Try refreshing your data, and update again."

This will require a number of steps.

Step 1: You would require a "version number" or a "timestamp"  column in the database table to detect concurrent modifications.

Step 2: When a record is initially read, the time stamp or version number also read.


    @Override
    public List<AdjustmentDetail> getAdjustmentRecords(final AdjustmentCriteria criteria)
    {
        String sql = "select a.detailid, a.portfolioCd, a.accountCd, a.PositionIndicator, a.cashValue, TmStamp = convert(int,substring(a.Timestamp,5,4))" +
                "from AdjustmentDetail a " +
                "Where a.portfoliocd = ? " +
                "and   a.valuationDttm = ? " +
                "and   a.inactiveFlag = 'N' ";
        
        List<Object> parametersList = new ArrayList<Object>();
        parametersList.add(criteria.getPortfolioCode());
        parametersList.add(criteria.getValuationDate());
        
        Object[] parameters = parametersList.toArray(new Object[parametersList.size()]);
        
        List<AdjustmentDetail> adjustments = jdbcTemplateSybase.query(sql, parameters,
                new RowMapper<AdjustmentDetail>()
                {
                    public AdjustmentDetail mapRow(ResultSet rs, int rowNum) throws SQLException
                    {
                        AdjustmentDetail record = new AdjustmentDetail();
      record.setDetailId(BigInteger.valueOf(rs.getLong("DetailId")));
                        record.setPortfolioCode(criteria.getPortfolioCode());
                        record.setAccountcd(rs.getString("accountCd"));
                        record.setAmount(rs.getBigDecimal("cashValue"));
      record.setPositionIndicator(rs.getString("PositionIndicator"));
                     record.setTimestamp(rs.getInt("TmStamp"));  // timestamp to detect any later modifications 

                        return record;

                    }
                });

        return adjustments;
        
    } 


Step 3: After the record has been modified, when ready to update the record, do a select query first to read the time stamp or version number for the same record to ensure that it has not been modified. if the "timestamp" or the "version number" has changed, you need to throw the above exception and abort modifying the record as it had been modified by another user.


@Override
    public AdjustmentDetail modifyAdjustment(AdjustmentDetail adjDetail)
    {
        if (adjDetail == null)
        {
            throw new RuntimeException("adjDetail is null");
        }
        
        int noOfRecords = 0;
        
        String inactiveFlag;
        
        
    
        try
        {
         //check if the record has been modified.
         Integer adjustmentModifiedTimestamp = getAdjustmentModifiedTimestamp(adjDetail.getDetailId());
            
            //logic to modify adjustments go here
   //every time the record is modified, the timestamp or version number is incremented.
        }
        catch (Exception e)
        {
            logger.error("Error updating adjustment  detail: ", e);
        }
        
        if (noOfRecords == 0) throw new ValidationException("The adjustment was not updated. It may be the record you are trying to update has been updated by another user. Try refreshing your data and update again.");
        
        
        logger.info("No of adjustment details updated = " + noOfRecords);
        
        return adjDetail;
    }
 

now the sample method that retrieves the timestamp.

//retrieve the timestamp value for the given datialid to detect if it has been modified.
 private Integer getAdjustmentModifiedTimestamp(BigInteger adjustmentDetailId) {

  String sql = "SELECT TmStamp = convert(int,substring(Timestamp,5,4)) from AdjustmentDetail where DetailId = ?";

  List<Object> parametersList = new ArrayList<Object>();
  parametersList.add(cashForecastDetaillId.intValue());

  Object[] parameters = parametersList.toArray(new Object[parametersList.size()]);

  List<Integer> ts = jdbcTemplateSybase.query(sql, parameters, new RowMapper<Integer>() {
   public Integer mapRow(ResultSet rs, int rowNum) throws SQLException {
    Integer tsValue = rs.getInt("TmStamp");
    return tsValue;

   }
  });
  return ts.get(0);
 }
 

Labels:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home