当具有重音字符时,JDBC-ODBC桥对Access的查询将失败



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

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

当cityName是一个没有重音字符的普通字符串时,结果集是正确的。但是,当cityName恰好是类似于LEÓNSAHAGÚN的东西,其中包含重音字符时,我不会得到任何结果。在这些情况下,查询似乎失败了。当在MS Access中运行时,同样的查询也可以正常工作,我也尝试了MS Data Accels SKD,这些查询工作得很好。

它们只有在通过JDBC-ODBC大桥时才会失效。据我所知,Java对字符串使用UTF-8,Access也是如此。它们都使用Unicode。有人知道这个问题的解决办法吗?

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

L  E  Ó     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将字符串的所有四个字符存储为单个字节:

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

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

这可以通过以下使用JDBC-ODBC桥的Java代码来确认。当Java源文件编码为UTF-8时,它无法找到所需的记录,但当Eclipse中Java源文件代码为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桥将无法为您完成这项工作。这是JDBC-ODBC桥和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的更多信息,请参阅此处的相关问题。

另一种解决方案是使用Jackess来操作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'Tradixe7u0103o 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();
...

最新更新