如果数据已更改,则不要更新ResultSet中的行

时间:2016-02-11 12:02:31

标签: java jdbc sqltransaction

我们从各种数据库类型(Oracle,MySQL,SQL-Server,...)中提取数据。一旦成功写入文件,我们希望将其标记为已传输,因此我们更新特定列。

我们的问题是,用户可以在此期间更改数据但可能忘记提交。使用select for update语句阻止记录。所以它可能发生,我们将某些东西标记为已传输,而不是。

这是我们代码的摘录:

Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet extractedData = stmt.executeQuery(sql);
writeDataToFile(extractedData);
extractedData.beforeFirst();
while (extractedData.next()) {
    if (!extractedData.rowUpdated()) {
        extractedData.updateString("COLUMNNAME", "TRANSMITTED");
        // code will stop here if user has changed data but did not commit
        extractedData.updateRow();
        // once committed the changed data is marked as transmitted
    }
}

方法extractedData.rowUpdated()返回false,因为从技术上讲,用户还没有改变任何内容。 有没有办法不更新行并检测数据是否在此后期更改?

不幸的是,我无法更改用户用来更改数据的程序。

3 个答案:

答案 0 :(得分:1)

所以你想要

  • 遍历表中尚未导出的所有行
  • 将此数据导出到某处
  • 标记导出的这些行,以便您的下一次迭代不会再次导出它们
  • 由于某一行可能有未决的更改,您不想搞砸这些信息

怎么样:

You iterate over all rows. 

for every row 
   generate a hash value for the contents of the row
   compare column "UPDATE_STATUS" with calulated hash
   if no match
     export row
     store hash into "UPDATE_STATUS" 
      if store fails (row locked) 
         -> no worries, will be exported again next time
      if store succeeds (on data already changed by user) 
         -> no worries, will be exported again as hash will not match

这可能会进一步降低您的导出速度,因为您必须迭代所有内容而不是所有内容WHERE UPDATE_STATUS IS NULL,但您可以完成两项工作 - 一项(快速) 迭代WHERE UPDATE_STATUS IS NULL和一个缓慢而彻底的WHERE UPDATE_STATUS IS NOT NULL(哈希重新检查到位)

如果您想避免存储失败/等待,您可能希望将散列/更新信息存储到复制主键加散列字段值的第二个表中 - 这样用户  主表上的锁不会干扰你的更新(因为那些会在另一个表上)

答案 1 :(得分:0)

"用户可能忘记提交" >用户提交或不提交。 "遗忘"提交相当于他的软件中的错误。

要解决此问题,您需要:

  • 启动隔离级别为SERIALIZABLE的事务,并在该事务中:
    • 读取数据并导出。以这种方式读取的数据被阻止更新。
    • 更新您处理的数据。注意:不要使用可更新的ResultSet执行此操作,请使用UPDATE语句执行此操作。这样您就不需要CONCUR_UPDATABLE + TYPE_SCROLL_SENSITIVE,这比CONCUR_READ_ONLY + TYPE_FORWARD_ONLY慢得多。
  • 提交交易。

这样可以阻止有缺陷的软件更新您正在处理的数据。

另一种方式

  • 在较低的隔离级别(默认TRANSACTION)和该事务中启动READ COMMITTED
    • 选择具有适当表格提示的数据,例如SQL Server theseTABLOCKX + HOLDLOCK(大数据集)或ROWLOCK + XLOCK + HOLDLOCK(小数据集)或PAGLOCK + XLOCK + HOLDLOCK。将HOLDLOCK作为表提示实际上等同于进行SERIALIZABLE事务。请注意,如果锁的数量过多,锁升级可能会将后两个升级为表锁。
    • 更新您处理的数据;注意:使用UPDATE语句。丢失可更新/ scroll_sensitive结果集。
  • 提交TRANSACTION。

同样的交易,将阻止有缺陷的软件更新您正在处理的数据。

答案 2 :(得分:0)

最后我们必须实现乐观锁定。在某些表中,我们已经有一个存储版本号的列。其他一些表有一个时间戳列,用于保存上次更改的时间(由触发器更改)。

虽然时间戳可能并不总是乐观锁定的可靠来源,但无论如何我们都去了它。在一秒钟内进行的一些更改在我们的环境中不太现实。

由于我们必须在不事先描述主键的情况下知道主键,因此我们必须访问结果集元数据。我们的一些数据库不支持此功能(例如DB / 2遗留表)。我们仍在使用旧系统。

注意:tableMetaData是一个XML配置文件,其中存储了我们对表的描述。这与数据库中表的元数据没有直接关系。

Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet extractedData = stmt.executeQuery(sql);
writeDataToFile(extractedData);
extractedData.beforeFirst();
while (extractedData.next()) {
    if (tableMetaData.getVersion() != null) {
        markDataAsExported(extractedData, tableMetaData);
    } else {
        markResultSetAsExported(extractedData, tableMetaData);
    }
}

// new way with building of an update statement including the version column in the where clause
private void markDataAsExported(ResultSet extractedData, TableMetaData tableMetaData) throws SQLException {
    ResultSet resultSetPrimaryKeys = null;
    PreparedStatement versionedUpdateStatement = null;
    try {
        ResultSetMetaData extractedMetaData = extractedData.getMetaData();
        resultSetPrimaryKeys = conn.getMetaData().getPrimaryKeys(null, null, tableMetaData.getTable());
        ArrayList<String> primaryKeyList = new ArrayList<String>();
        String sqlStatement = "update " + tableMetaData.getTable() + " set " + tableMetaData.getUpdateColumn()
                + " = ? where ";
        if (resultSetPrimaryKeys.isBeforeFirst()) {
            while (resultSetPrimaryKeys.next()) {
                primaryKeyList.add(resultSetPrimaryKeys.getString(4));
                sqlStatement += resultSetPrimaryKeys.getString(4) + " = ? and ";
            }
            sqlStatement += tableMetaData.getVersionColumn() + " = ?";
            versionedUpdateStatement = conn.prepareStatement(sqlStatement);
            while (extractedData.next()) {
                versionedUpdateStatement.setString(1, tableMetaData.getUpdateValue());
                for (int i = 0; i < primaryKeyList.size(); i++) {
                    versionedUpdateStatement.setObject(i + 2, extractedData.getObject(primaryKeyList.get(i)),
                            extractedMetaData.getColumnType(extractedData.findColumn(primaryKeyList.get(i))));
                }
                versionedUpdateStatement.setObject(primaryKeyList.size() + 2,
                        extractedData.getObject(tableMetaData.getVersionColumn()), tableMetaData.getVersionType());
                if (versionedUpdateStatement.executeUpdate() == 0) {
                    logger.warn(Message.COLLECTOR_DATA_CHANGED, tableMetaData.getTable());
                }
            }
        } else {
            logger.warn(Message.COLLECTOR_PK_ERROR, tableMetaData.getTable());
            markResultSetAsExported(extractedData, tableMetaData);
        }
    } finally {
        if (resultSetPrimaryKeys != null) {
            resultSetPrimaryKeys.close();
        }
        if (versionedUpdateStatement != null) {
            versionedUpdateStatement.close();
        }
    }
}

//the old way as fallback
private void markResultSetAsExported(ResultSet extractedData, TableMetaData tableMetaData) throws SQLException {
    while (extractedData.next()) {
        extractedData.updateString(tableMetaData.getUpdateColumn(), tableMetaData.getUpdateValue());
        extractedData.updateRow();
    }
}
相关问题