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