对具有重音字符的JDBC-ODBC Bridge查询失败

我正在通过JDBC-ODBC Bridge向Java发送查询到Access数据库,如下所示:

"SELECT * FROM localities WHERE locName='" + cityName + "'" 

当cityName是没有重音字符的普通字符串时,结果集是正确的。 但是当cityName恰好是LEÓNSAHAGÚN类的东西,其中带有重音字符,那么我就没有结果。 在这些情况下,查询似乎失败了。 在MS Access中运行时相同的查询工作正常,我也尝试使用Ms Data Acces SKD并且这些查询完美运行。

它们仅在通过JDBC-ODBC Bridge时失败。 据我所知,Java使用UTF-8作为字符串,Access也是如此。 他们都使用Unicode。 有谁知道这个问题的任何解决方案?

听起来你的Java源文件被编码为UTF-8,所以当cityName字符串包含LEÓN它被编码为

 LE Ó N -- -- ----- -- 4C 45 C3 93 4E 

这不是Access存储值的方式。 Access将字符存储为Unicode,但不使用UTF-8编码。 它使用UTF-16LE编码的变体,其中代码点U + 00FF及以下的字符存储在单个字节中,代码点高于U + 00FF的字符存储为Null(0x0)值,后跟其UTF-16LE字节对。 在这种情况下, Ó是U + 00D3,低于U + 00FF,因此Access将字符串的所有四个字符存储为单个字节:

 LE Ó N -- -- -- -- 4C 45 D3 4E 

实际效果是,Access数据库中字符串的编码与ISO 8859-1字符集的编码相同。

这可以通过以下使用JDBC-ODBC Bridge的Java代码来确认。 当Java源文件编码为UTF-8 ,它无法找到所需的记录,但是当Java源文件在Eclipse中编码为cp1252时它可以工作:

 import java.sql.*; public class accentTestMain { public static void main(String[] args) { String connectionString = "jdbc:odbc:Driver={Microsoft Access Driver (*.mdb, *.accdb)};" + "DBQ=C:\\__tmp\\test\\accented.accdb;"; try { Connection con = DriverManager.getConnection(connectionString); PreparedStatement stmt = con.prepareStatement("SELECT * FROM localities WHERE locName=?"); String cityName = "LEÓN"; stmt.setString(1, cityName); stmt.execute(); ResultSet rs = stmt.getResultSet(); if (rs.next()) { System.out.println(String.format("Record found, ID=%d", rs.getInt("ID"))); } else { System.out.print("Record not found."); } } catch (SQLException e) { e.printStackTrace(); } } } 

如果你只能支持cp1252字符集中表示的重音字符,那么你应该能够简单地使用cp1252作为Java源文件的编码设置。

另一方面, 如果您确实需要使用Access数据库的完整Unicode字符支持,那么JDBC-ODBC Bridge将无法为您完成工作 。 这是JDBC-ODBC Bridge和Access ODBC驱动程序之间长期存在的互操作性问题,并且不会修复。 ( 这里有更多细节。)

在这种情况下,您可能需要考虑使用UCanAccess ,它是Access的纯Java JDBC驱动程序。 使用带有UTF-8编码源文件的UCanAccess的相应代码将是

 // assumes... // import java.sql.*; Connection conn=DriverManager.getConnection( "jdbc:ucanaccess://C:/__tmp/test/accented.accdb"); PreparedStatement ps = conn.prepareStatement( "SELECT ID FROM localities WHERE locName=?"); ps.setString(1, "LEÓN"); ResultSet rs = ps.executeQuery(); if (rs.next()) { System.out.println(String.format( "Record found, ID=%d", rs.getInt("ID"))); } else { System.out.println("Record not found."); } 

有关使用UCanAccess的更多信息,请参阅此处的相关问题。

另一种解决方案是使用Jackcess来操作Access数据库(同样,Java源文件编码为UTF-8):

 import java.io.File; import java.io.IOException; import com.healthmarketscience.jackcess.*; public class accentTestMain { public static void main(String[] args) { Database db; try { db = DatabaseBuilder.open(new File("C:\\__tmp\\test\\accented.accdb")); try { Table tbl = db.getTable("localities"); Cursor crsr = CursorBuilder.createCursor(tbl.getIndex("locName")); if (crsr.findFirstRow(tbl.getColumn("locName"), "LEÓN")) { System.out.println(String.format("Record found, ID=%d", crsr.getCurrentRowValue(tbl.getColumn("ID")))); } else { System.out.println("Record not found."); } } catch (Exception e) { e.printStackTrace(); } finally { db.close(); } } catch (IOException e) { e.printStackTrace(); } } } 

尝试使用PreparedStatement。

我刚刚使用Jython 2.5测试了MS Access Northwind数据库,它使用了JDBC-ODBC桥:

 c = db.createStatement() TRADH = u'Tradi\xe7\u0103o Hipermercados' pstm = db.prepareStatement("SELECT CustomerID, CompanyName FROM customers WHERE CompanyName=?") pstm.setString(1, TRADH) rs = pstm.executeQuery() while (rs.next()): try: s1 = rs.getString(1) s2 = rs.getString(2) print('[%s] [%s]' % (s1, s2)) except UnicodeEncodeError: print('[%s] [%s] !!!' % (s1, repr(s2))) c.close() 

在您的代码中,它将如下所示:

 pstm = db.prepareStatement("SELECT * FROM localities WHERE locName=?"); pstm.setString(1, locName); rs = pstm.executeQuery(); ...