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

Java 如果数据已更改,则不更新ResultSet中的行,java,jdbc,sqltransaction,Java,Jdbc,Sqltransaction,我们正在从各种数据库类型(Oracle、MySQL、SQL Server等)提取数据。一旦它成功写入文件,我们希望将其标记为已传输,因此我们更新特定列 我们的问题是,用户有可能同时更改数据,但可能忘记提交。记录被select for update语句阻止。因此,我们可能会将某些东西标记为已传输,而不是 这是我们代码的摘录: Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR

我们正在从各种数据库类型(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,因为从技术上讲,用户尚未更改任何内容。 有没有办法不更新行并检测数据是否在此后期发生了更改

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

所以您希望

  • 运行表中所有尚未导出的行
  • 将此数据导出到某个位置
  • 标记这些已导出的行,以便下次迭代不会再次导出它们
  • 由于行上可能有挂起的更改,您不想弄乱这些信息
那么:

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
这可能会进一步降低导出速度,因为您将不得不遍历所有内容,而不是遍历更新状态为NULL的所有内容
,但您可能可以完成两项工作-一项(快速)
迭代更新状态为空的
,以及更新状态为非空的一个缓慢而彻底的
(哈希重新检查到位)

如果要避免存储失败/等待,可能需要将散列/更新的信息存储到第二个表中,复制主键加上散列字段值-用户就是这样 主表上的锁根本不会干扰您的更新(就像在另一个表上一样)

“用户[…]可能忘记提交”>用户要么提交,要么不提交。“忘记”提交等同于他的软件中的一个bug

要解决这一问题,您需要:

  • 启动具有隔离级别的事务,并在该事务中:
    • 读取数据并将其导出。以这种方式读取的数据被阻止更新
    • 更新您处理的数据。注意:不要使用可更新的
      ResultSet
      执行此操作,请使用
      UPDATE
      语句执行此操作。这样,您就不需要
      CONCUR\u updateable+TYPE\u SCROLL\u SENSITIVE
      ,它比
      CONCUR\u READ\u ONLY+TYPE\u FORWARD\u ONLY
      慢得多
  • 提交事务
这样,错误软件将被阻止更新您正在处理的数据

另一种方式

  • 在较低的隔离级别(默认值
    读取提交的
    )和该事务内启动
    事务
    • 使用适当的表提示选择数据,例如SQL Server:
      TABLOCKX+HOLDLOCK
      (大数据集),或
      ROWLOCK+XLOCK+HOLDLOCK
      (小数据集),或
      PAGLOCK+XLOCK+HOLDLOCK
      。使用
      HOLDLOCK
      作为表提示实际上相当于使用
      SERIALIZABLE
      事务。请注意,如果锁的数量过高,锁升级可能会将后两个锁升级为表锁
    • 更新您处理的数据;注意:使用
      UPDATE
      语句。丢失可更新/滚动敏感的结果集
  • 提交事务

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

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

虽然时间戳可能并不总是乐观锁定的可靠来源,但我们还是使用了它。在我们的环境中,在一秒钟内发生的几次变化是不现实的

因为我们必须知道主键,而不必事先描述它,所以我们必须访问resultset元数据。我们的一些数据库不支持这一点(例如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();
    }
}
语句stmt=conn.createStatement(ResultSet.TYPE\u SCROLL\u敏感,ResultSet.CONCUR\u可更新); ResultSet extractedData=stmt.executeQuery(sql); 写入数据文件(提取数据); extractedData.beforeFirst(); while(extractedData.next()){ if(tableMetaData.getVersion()!=null){ markDataAsExported(extractedData、tableMetaData); }否则{ MarkResultStatesExported(extractedData、tableMetaData); } } //在where子句中构建包含version列的update语句的新方法 私有void markDataAsExported(ResultSet extractedData,TableMetaData TableMetaData)引发SQLException{ ResultSet resultSetPrimaryKeys=null; PreparedStatement versionedUpdateStatement=null; 试一试{ ResultSetMetaData extractedMetaData=extractedData.getMetaData(); resultSetPrimaryKeys=conn.getMetaData().getPrimaryKeys(null,null,tableMetaData.getTable()); ArrayList primaryKeyList=新的ArrayList(); String sqlStatement=“update”+tableMetaData.getTable()+“set”+tableMetaData.getUpdateColumn() +“=?何处”; if(resultSetPrimaryKeys.isBeforeFirst()){ while(resultSetPrimaryKeys.next()){ 添加(resultSetPrimaryKeys.getString(4)); sqlStatement+=resultSetPrimaryKeys.getString(4)+“=”和“; } sqlStatement+=tableMetaData.getVersionColumn()+“=?”; versionedUpdateStatement=conn.prepareStatement(sqlStatement); while(extractedData.next()){