基于列标签的CachedRowSetImpl getString抛出“无效的列名”

当我执行以下代码时,一切都很好:

ResultSet rs = con.prepareStatement("SELECT r.UID AS R FROM r").executeQuery(); System.out.println(rs.getMetaData().getColumnLabel(1)); rs.next(); System.out.println(rs.getString("R")); 

结果是:

 R 23 

但是当我执行以下代码时:

 ResultSet rs = con.prepareStatement("SELECT r.UID AS R FROM r").executeQuery(); CachedRowSetImpl rslt = new CachedRowSetImpl(); rslt.populate(rs); System.out.println(rslt.getMetaData().getColumnLabel(1)); rslt.next(); System.out.println(rslt.getString("R")); 

结果是:

 R java.sql.SQLException: Invalid column name 

为什么会在这里抛出exception?

问题是CachedRowSetcom.sun.rowset.CachedRowSetImpl )的引用实现包含一个错误:当你按名称检索列时,它使用columnName不是 columnLabel ,因此它违反JDBC规范的其余部分。使用columnLabel检索值。 此错误使得无法通过columnLabel从行columnLabel检索值。

Oracle的错误是http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7046875 ,但是(惊讶,惊讶)他们已经无法公开查看。

有两种可能的解决方法。 一个是检查你的驱动程序是否提供了一个属性,也让ResultSetMetaData.getColumnName(..)方法返回columnLabel值,第二个解决方法是创建一个CachedRowSetImpl的子类(遗憾的是需要很多重写方法)。

以下版本是从以下消息复制而来的: http : //tech.groups.yahoo.com/group/Firebird-Java/message/10715

 import java.math.BigDecimal; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; import java.sql.Ref; import java.sql.SQLException; import java.util.Calendar; import java.util.Collection; import java.util.Hashtable; import javax.sql.rowset.RowSetMetaDataImpl; import com.sun.rowset.CachedRowSetImpl; public class FixedCachedRowSetImpl extends CachedRowSetImpl { private static final long serialVersionUID = -9067504047398250113L; private RowSetMetaDataImpl RowSetMD; public FixedCachedRowSetImpl() throws SQLException { super(); } public FixedCachedRowSetImpl(Hashtable env) throws SQLException { super(env); } private int getColIdxByName(String name) throws SQLException { RowSetMD = (RowSetMetaDataImpl) this.getMetaData(); int cols = RowSetMD.getColumnCount(); for (int i = 1; i <= cols; ++i) { String colName = RowSetMD.getColumnLabel(i); if (colName != null) if (name.equalsIgnoreCase(colName)) return (i); else continue; } throw new SQLException(resBundle.handleGetObject("cachedrowsetimpl.invalcolnm").toString()); } @Override public Collection toCollection(String column) throws SQLException { return toCollection(getColIdxByName(column)); } @Override public String getString(String columnName) throws SQLException { return getString(getColIdxByName(columnName)); } @Override public boolean getBoolean(String columnName) throws SQLException { return getBoolean(getColIdxByName(columnName)); } @Override public byte getByte(String columnName) throws SQLException { return getByte(getColIdxByName(columnName)); } @Override public short getShort(String columnName) throws SQLException { return getShort(getColIdxByName(columnName)); } @Override public int getInt(String columnName) throws SQLException { return getInt(getColIdxByName(columnName)); } @Override public long getLong(String columnName) throws SQLException { return getLong(getColIdxByName(columnName)); } @Override public float getFloat(String columnName) throws SQLException { return getFloat(getColIdxByName(columnName)); } @Override public double getDouble(String columnName) throws SQLException { return getDouble(getColIdxByName(columnName)); } @Override public BigDecimal getBigDecimal(String columnName, int scale) throws SQLException { return getBigDecimal(getColIdxByName(columnName), scale); } @Override public byte[] getBytes(String columnName) throws SQLException { return getBytes(getColIdxByName(columnName)); } @Override public java.sql.Date getDate(String columnName) throws SQLException { return getDate(getColIdxByName(columnName)); } @Override public java.sql.Time getTime(String columnName) throws SQLException { return getTime(getColIdxByName(columnName)); } @Override public java.sql.Timestamp getTimestamp(String columnName) throws SQLException { return getTimestamp(getColIdxByName(columnName)); } @Override public java.io.InputStream getAsciiStream(String columnName) throws SQLException { return getAsciiStream(getColIdxByName(columnName)); } @Override public java.io.InputStream getUnicodeStream(String columnName) throws SQLException { return getUnicodeStream(getColIdxByName(columnName)); } @Override public java.io.InputStream getBinaryStream(String columnName) throws SQLException { return getBinaryStream(getColIdxByName(columnName)); } @Override public Object getObject(String columnName) throws SQLException { return getObject(getColIdxByName(columnName)); } @Override public int findColumn(String columnName) throws SQLException { return getColIdxByName(columnName); } @Override public java.io.Reader getCharacterStream(String columnName) throws SQLException { return getCharacterStream(getColIdxByName(columnName)); } @Override public BigDecimal getBigDecimal(String columnName) throws SQLException { return getBigDecimal(getColIdxByName(columnName)); } @Override public boolean columnUpdated(String columnName) throws SQLException { return columnUpdated(getColIdxByName(columnName)); } @Override public void updateNull(String columnName) throws SQLException { updateNull(getColIdxByName(columnName)); } @Override public void updateBoolean(String columnName, boolean x) throws SQLException { updateBoolean(getColIdxByName(columnName), x); } @Override public void updateByte(String columnName, byte x) throws SQLException { updateByte(getColIdxByName(columnName), x); } @Override public void updateShort(String columnName, short x) throws SQLException { updateShort(getColIdxByName(columnName), x); } @Override public void updateInt(String columnName, int x) throws SQLException { updateInt(getColIdxByName(columnName), x); } @Override public void updateLong(String columnName, long x) throws SQLException { updateLong(getColIdxByName(columnName), x); } @Override public void updateFloat(String columnName, float x) throws SQLException { updateFloat(getColIdxByName(columnName), x); } @Override public void updateDouble(String columnName, double x) throws SQLException { updateDouble(getColIdxByName(columnName), x); } @Override public void updateBigDecimal(String columnName, BigDecimal x) throws SQLException { updateBigDecimal(getColIdxByName(columnName), x); } @Override public void updateString(String columnName, String x) throws SQLException { updateString(getColIdxByName(columnName), x); } @Override public void updateBytes(String columnName, byte x[]) throws SQLException { updateBytes(getColIdxByName(columnName), x); } @Override public void updateDate(String columnName, java.sql.Date x) throws SQLException { updateDate(getColIdxByName(columnName), x); } @Override public void updateTime(String columnName, java.sql.Time x) throws SQLException { updateTime(getColIdxByName(columnName), x); } @Override public void updateTimestamp(String columnName, java.sql.Timestamp x) throws SQLException { updateTimestamp(getColIdxByName(columnName), x); } @Override public void updateAsciiStream(String columnName, java.io.InputStream x, int length) throws SQLException { updateAsciiStream(getColIdxByName(columnName), x, length); } @Override public void updateBinaryStream(String columnName, java.io.InputStream x, int length) throws SQLException { updateBinaryStream(getColIdxByName(columnName), x, length); } @Override public void updateCharacterStream(String columnName, java.io.Reader reader, int length) throws SQLException { updateCharacterStream(getColIdxByName(columnName), reader, length); } @Override public void updateObject(String columnName, Object x, int scale) throws SQLException { updateObject(getColIdxByName(columnName), x, scale); } @Override public void updateObject(String columnName, Object x) throws SQLException { updateObject(getColIdxByName(columnName), x); } @Override public Object getObject(String columnName, java.util.Map> map) throws SQLException { return getObject(getColIdxByName(columnName), map); } @Override public Ref getRef(String colName) throws SQLException { return getRef(getColIdxByName(colName)); } @Override public Blob getBlob(String colName) throws SQLException { return getBlob(getColIdxByName(colName)); } @Override public Clob getClob(String colName) throws SQLException { return getClob(getColIdxByName(colName)); } @Override public Array getArray(String colName) throws SQLException { return getArray(getColIdxByName(colName)); } @Override public java.sql.Date getDate(String columnName, Calendar cal) throws SQLException { return getDate(getColIdxByName(columnName), cal); } @Override public java.sql.Time getTime(String columnName, Calendar cal) throws SQLException { return getTime(getColIdxByName(columnName), cal); } @Override public java.sql.Timestamp getTimestamp(String columnName, Calendar cal) throws SQLException { return getTimestamp(getColIdxByName(columnName), cal); } @Override public void updateRef(String columnName, java.sql.Ref ref) throws SQLException { updateRef(getColIdxByName(columnName), ref); } @Override public void updateClob(String columnName, Clob c) throws SQLException { updateClob(getColIdxByName(columnName), c); } @Override public void updateBlob(String columnName, Blob b) throws SQLException { updateBlob(getColIdxByName(columnName), b); } @Override public void updateArray(String columnName, Array a) throws SQLException { updateArray(getColIdxByName(columnName), a); } @Override public java.net.URL getURL(String columnName) throws SQLException { return getURL(getColIdxByName(columnName)); } } 

您还可以查看org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet ,它还说:

注意:自JDBC 4.0以来,已经阐明任何使用String来标识列的方法都应该使用列标签。 使用SQL查询字符串中的ALIAS关键字分配列标签。 当查询不使用ALIAS时,默认标签是列名。 大多数JDBC ResultSet实现都遵循这种新模式,但有一些例外,例如com.sun.rowset.CachedRowSetImpl类,它只使用列名,忽略任何列标签。 从Spring 3.0.5开始, ResultSetWrappingSqlRowSet会将列标签转换为正确的列索引,以便为com.sun.rowset.CachedRowSetImpl提供更好的支持,这是JdbcTemplate在使用RowSet时使用的默认实现。

你可以使用内部选择:

 ResultSet rs = con.prepareStatement("SELECT * FROM (SELECT r.UID AS R FROM r) AA").executeQuery(); CachedRowSetImpl rslt = new CachedRowSetImpl(); rslt.populate(rs); System.out.println(rslt.getMetaData().getColumnLabel(1)); rslt.next(); System.out.println(rslt.getString("R")); 

一种解决方法似乎是将列包装在函数或数学运算中。 然后,您可以在CachedRowSetImpl使用别名。

如果您的SQL是这样的:

 SELECT id AS student_id, cost - discount AS total_cost, first_name AS name FROM students 

您将能够引用studentRow.getBigDecimal("total_cost") ,但studentRow.getLong("student_id")studentRow.getString("name")将失败,并显示SQLException “无效的列名”。

但是如果你的SQL是这样的:

 SELECT id + 0 AS student_id, cost - discount AS total_cost, CONCAT(first_name) AS name FROM students 

然后它就像你期望的那样工作。

我不确定这会对性能造成什么影响,但它会起作用。

这里我的改进版本的getColIdxByName支持MySQL 5.x名称,如“tbl.column”:

 private int getColIdxByName(String name) throws SQLException { RowSetMD = (RowSetMetaDataImpl) this.getMetaData(); int cols = RowSetMD.getColumnCount(); for (int i = 1; i <= cols; ++i) { String colLabel = RowSetMD.getColumnLabel(i); String colName = RowSetMD.getColumnName(i); if (colName != null) if (name.equalsIgnoreCase(colName) || name.equalsIgnoreCase(RowSetMD.getTableName(i) + "." + colName)) { return (i); } else if (colLabel != null) if (name.equalsIgnoreCase(colLabel)) { return (i); } else continue; } throw new SQLException(resBundle.handleGetObject("cachedrowsetimpl.invalcolnm").toString()); } 

最新的JDK 1.7已经实现了CachedRowSet,标签名称bug已经修复!

 import java.sql.ResultSet; import javax.sql.rowset.CachedRowSet; import javax.sql.rowset.RowSetFactory; import javax.sql.rowset.RowSetProvider; ResultSet rs = con.prepareStatement("SELECT r.UID AS R FROM r").executeQuery(); RowSetFactory rowSetFactory = RowSetProvider.newFactory(); CachedRowSet crs = rowSetFactory.createCachedRowSet(); crs.populate(rs);