Запросы моста JDBC-ODBC к Access завершаются ошибкой, если в них есть символы с диакритическими знаками.

Я отправляю запрос через мост JDBC-ODBC в базу данных Access из Java, например:

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

Когда cityName является обычной строкой без диакритических знаков, результирующий набор правильный. Но когда cityName оказывается чем-то вроде LEÓN, SAHAGÚN, то есть с акцентированными символами в них, я не получаю никаких результатов. Кажется, что запрос терпит неудачу в этих случаях. Те же запросы при запуске в MS Access работают нормально, я также пробовал с Ms Data Acces SKD, и эти запросы работают отлично.

Они терпят неудачу только при прохождении моста JDBC-ODBC. Насколько я понимаю, Java использует UTF-8 для строк, как и Access. И они оба используют Unicode. Кто-нибудь знает какое-нибудь решение этой проблемы?


person user3098656    schedule 09.01.2014    source источник
comment
Итак, вы разобрались с этим?   -  person Gord Thompson    schedule 26.01.2014


Ответы (2)


Похоже, ваш исходный файл 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.

Это можно подтвердить с помощью следующего кода Java, который использует мост JDBC-ODBC. Не удается найти нужную запись, когда исходный файл Java закодирован как UTF-8, но он работает, когда исходный файл Java закодирован как cp1252 в Eclipse:

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.

С другой стороны, если вам действительно нужна полная поддержка символов Unicode в базе данных Access, то JDBC-ODBC Bridge не сделает за вас эту работу. Это давняя проблема взаимодействия между JDBC-ODBC Bridge и драйвером Access ODBC, и она не будет исправлена. (Подробнее здесь.)

В этом случае вы можете рассмотреть возможность использования UCanAccess, который является чисто Java-драйвером JDBC для Access. . Соответствующий код, использующий UCanAccess с исходным файлом в кодировке UTF-8, будет

// 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();
        }
    }

}
person Gord Thompson    schedule 10.01.2014

Попробуйте использовать PreparedStatement.

Я только что протестировал базу данных MS Access Northwind с Jython 2.5, которая использует мост 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();
...
person Michał Niklas    schedule 10.01.2014