JDBC select 语句中的数字溢出
JDBC numeric overflow in select statement
我有一个 java 应用程序,它执行用户在 UI 上输入的 SQL select 语句。应用程序获取元数据以了解列类型:
ResultSetMetaData metadata = rs.getMetaData();
int numColumns = metadata.getColumnCount();
for (int i=1;i<=numColumns;i++){
column[i] = metadata.getColumnType(i);
}
获得结果集后,我会针对每一行遍历列以了解类型。根据类型,我使用 getInt()、getDate() 或 getString() 获取值。
问题是在某些情况下 select 有一个很大的 SUM(),当我执行 getInt() 时会出现数字溢出(我从 JDBC 驱动程序中得到一个错误, Sybase 在我的例子中)。问题是getInt()要获取的数大于int的容量
一个解决方案是使用 getLong() 而不是 getInt(),但是由于我事先不知道用户输入的 select 列,我可能正在使用 getLong()对于非常小的数值,如果数量很大,这将完全浪费 64 位字段。
有什么办法解决这个问题吗?
在64位CPU时代,也许getLong()
终究不会这么浪费。但即使 getLong()
也可能溢出,所以你更安全但不完全安全。 BigDecimal
让您安全无虞。并且由于您将应用程序描述为 UI,其中 用户输入语句 ,瓶颈很可能是用户本身或 JDBC,而不是 Java。
您可以使用 ResultSetMetaData.getColumnType(int column)
来确定列的类型并根据此选择 Java 类型。您可以使用 ResultSetMetaData.getColumnClassName(int column)
找出驱动程序建议的 Java class,并使用 ResultSet.getObject(int column)
检索数据。是否真的方便也可能取决于驱动程序的质量。
当用户想要在数字列上使用 SUM
时,我建议您始终使用下一个更大的数据类型(long
在本例中用于 int
列)。
原因是因为您将至少花费那么多内存和处理来尝试找出无论如何要使用的数据类型。您不妨保持代码简单并提升到下一个更大的类型。
您可以为您的列做 "optimistic" 缓冲:开始为您的列数据使用 int
(甚至 short
)缓冲区,并将您的获取循环包装在 try
/catch
表示溢出异常。如果你抓住了一个,通过尝试检索它作为越来越多的缓冲区数据类型来检查你需要多宽 "wide" (short
-> int
-> long
-> BigInteger
/BigDecimal
) 类型。然后调整该列的缓冲区大小(通过分配一个更宽类型的新缓冲区并复制前几行的值)并重新开始检索。
但这是相当复杂的代码。如果您负担得起内存,最好只使用 long
或 BigInteger
/BigDecimal
来代替。您应该检查一些示例数据集的实际大小和由此产生的内存节省。 (并且请注意,当您最终不得不扩大时,缓冲区重新分配会产生一些成本。)如果结果集大小在实践中很容易适合您的用户使用更简单的 long
或更广泛的检索,那就没有问题了,不用你去解决。
我有一个 java 应用程序,它执行用户在 UI 上输入的 SQL select 语句。应用程序获取元数据以了解列类型:
ResultSetMetaData metadata = rs.getMetaData();
int numColumns = metadata.getColumnCount();
for (int i=1;i<=numColumns;i++){
column[i] = metadata.getColumnType(i);
}
获得结果集后,我会针对每一行遍历列以了解类型。根据类型,我使用 getInt()、getDate() 或 getString() 获取值。
问题是在某些情况下 select 有一个很大的 SUM(),当我执行 getInt() 时会出现数字溢出(我从 JDBC 驱动程序中得到一个错误, Sybase 在我的例子中)。问题是getInt()要获取的数大于int的容量
一个解决方案是使用 getLong() 而不是 getInt(),但是由于我事先不知道用户输入的 select 列,我可能正在使用 getLong()对于非常小的数值,如果数量很大,这将完全浪费 64 位字段。
有什么办法解决这个问题吗?
在64位CPU时代,也许getLong()
终究不会这么浪费。但即使 getLong()
也可能溢出,所以你更安全但不完全安全。 BigDecimal
让您安全无虞。并且由于您将应用程序描述为 UI,其中 用户输入语句 ,瓶颈很可能是用户本身或 JDBC,而不是 Java。
您可以使用 ResultSetMetaData.getColumnType(int column)
来确定列的类型并根据此选择 Java 类型。您可以使用 ResultSetMetaData.getColumnClassName(int column)
找出驱动程序建议的 Java class,并使用 ResultSet.getObject(int column)
检索数据。是否真的方便也可能取决于驱动程序的质量。
当用户想要在数字列上使用 SUM
时,我建议您始终使用下一个更大的数据类型(long
在本例中用于 int
列)。
原因是因为您将至少花费那么多内存和处理来尝试找出无论如何要使用的数据类型。您不妨保持代码简单并提升到下一个更大的类型。
您可以为您的列做 "optimistic" 缓冲:开始为您的列数据使用 int
(甚至 short
)缓冲区,并将您的获取循环包装在 try
/catch
表示溢出异常。如果你抓住了一个,通过尝试检索它作为越来越多的缓冲区数据类型来检查你需要多宽 "wide" (short
-> int
-> long
-> BigInteger
/BigDecimal
) 类型。然后调整该列的缓冲区大小(通过分配一个更宽类型的新缓冲区并复制前几行的值)并重新开始检索。
但这是相当复杂的代码。如果您负担得起内存,最好只使用 long
或 BigInteger
/BigDecimal
来代替。您应该检查一些示例数据集的实际大小和由此产生的内存节省。 (并且请注意,当您最终不得不扩大时,缓冲区重新分配会产生一些成本。)如果结果集大小在实践中很容易适合您的用户使用更简单的 long
或更广泛的检索,那就没有问题了,不用你去解决。