*Markus: (MYSQL) UTF8-, bzw Umlaut-Problem

Hallo,

eigentlich wollte ich herausfinden, wie ich dem Tomcat beibringen kann, UTF-8 zu verwenden. URIEncoding="UTF-8" in der server.xml scheint nicht zu helfen (funktioniert angeblich nur bei GET). In den JSPs hilft folgende Zeile:

<%@ page language="java" contentType="text/html; charset=UTF-8"  
    pageEncoding="UTF-8"%>

ebenfalls nicht.
Beim Herumspielen kam ich aber plötzlich auf etwas ganz anderes, noch viel schlimmeres drauf. Offensichtlich scheinen Umlaute am Schluss einer Eingabe in einem HTML-Input-Feld, die über ein Servlet in eienr MySQL-Db landet den Tomcat völlig aus dem Tritt zu bringen. Ein "ÄÖÜ" löst folgende Exception aus:

  
ava.sql.SQLException: Incorrect string value: '\xC2\x84\xC3\x83\xC2\x96...' for column 't_beschreibung' at row 1

Der Code sieht so aus:

Servlet.....

  
 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
   ThemenGebietDao dao = new ThemenGebietDaoImpl();  
  
   if (request.getParameter("themaanlegen") != null)   {  
    String thema = request.getParameter("themengebiet");  
    if (thema != null && !thema.trim().equals(""))   {  
    ThemenGebietBo bo = new ThemenGebietBo();  
    bo.setBeschreibung(thema);  
    dao.setNewThemengebiet(bo);  
    request.getRequestDispatcher("themengebiet_erfolgreich.html").forward(request, response);  
    }  
    else  {  
    request.getRequestDispatcher("themengebiet_fehler.html").forward(request, response);  
    }  
   }

Gespeichert wird der Wert folgendermaßen:

  
 @Override  
 public void setNewThemengebiet(ThemenGebietBo themenGebiet) {  
        Connection con = ConnectionFactory.getConnection();  
        if (con != null)   {  
         String conString = "INSERT INTO Themengebiet SET t_beschreibung = ?";  
         try {  
    PreparedStatement pst = con.prepareStatement(conString);  
    pst.setString(1, themenGebiet.getBeschreibung());  
             pst.execute();  
  
   } catch (SQLException e) {  
    e.printStackTrace();  
   }  
   finally  {  
    try { con.close(); }  
    catch (SQLException e) { e.printStackTrace(); }  
   }  
        }  
 }  

Will ich einen String, der "fünf" heißt speichern, funktioniert es, d.h. es steht richtig in der MySQL-DB als "fünf".
Angezeigt wird es aber trotzdem falsch. Die Kodierung im Browser UTF-8, MySQL ist standardmäßig auf utf8. Scheint wohl ein Tomcat-Problem zu sein.
Jedenfalls ist es mir ein größeres Anliegen, das Problem mit der Tomcat-Exception zu beheben. Will ich ÄÖÜ abspeichern, bekomme ich folgendes als StackTrace:

  
java.sql.SQLException: Incorrect string value: '\xC2\x84\xC3\x83\xC2\x96...' for column 't_beschreibung' at row 1  
 at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1055)  
 at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)  
 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3491)  
 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3423)  
 at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1936)  
 at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2060)  
 at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2542)  
 at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1734)  
 at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:995)  
 at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:169)  
 at dao.ThemenGebietDaoImpl.setNewThemengebiet(ThemenGebietDaoImpl.java:44)  
 at servlets.ThemenServlet.doPost(ThemenServlet.java:50)  
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)  
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)  
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)  
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)  
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)  
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)  
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)  
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)  
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)  
 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286)  
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:845)  
 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)  
 at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)  
 at java.lang.Thread.run(Thread.java:619)  

Kann sich das jemand erklären?

Markus

--
  1. Hallo Markus,

    zu Tomcat kann ich Dir leider nicht weiterhelfen, aber ich habe mir mal den String etwas genauer angeschaut, den Du gepostet hast:

    ava.sql.SQLException: Incorrect string value: '\xC2\x84\xC3\x83\xC2\x96...' for column 't_beschreibung' at row 1[/code]

    \xc2 \x84 evaluiert durch UTF8 auf \x84, das ignoriere ich vorerst.

    \xc3 \x83 evaluiert auf \xc3 und
    \xc2 \x96 evaluiert auf \x96.
    Das interessante daran ist, dass die „dekodierten“ \xc3 und \x96 zusammen wieder als UTF-8 Zeichen interpretiert werden können, was dann zu \xdc evaluiert, und das ist das große Ü.
    Extrapoliere ich jetzt auf ein \xc3\x93, das VOR dem String steht, den Du gepostet hast, ergibt das oben erwähnte \x84 ein großes Ä.

    Meine Vermutung ist also, dass Du oder irgendeine beteiligte Komponente fälschlicherweise 2 Mal von einem Quellzeichensatz zu UTF-8 konvertiert.
    Allerdings erklärt das nicht die Exception, weil das ganze durchaus ein valider String ist.
    Aber vielleicht bringt Dich das auf eine Fährte.

    Viele Grüße,
    Gero

    1. Hallo,

      danke für den Tipp, aber leider bin ich noch genauso ratlos wie gestern, denn man kann ja schließlich nicht versehentlich UTF-8 "zwei Mal setzen".
      Bin mal gespannt, ob ich heute Abend noch zur Lösung komme.

      Markus

      --
      1. echo $begrüßung;

        danke für den Tipp, aber leider bin ich noch genauso ratlos wie gestern, denn man kann ja schließlich nicht versehentlich UTF-8 "zwei Mal setzen".

        Doch, durchaus. Wenn ein Empfänger beispielsweise aufgrund einer Defaulteinstellung annimmt, er bekäme ISO-8859-1, bekommt aber UTF-8, wandelt nun das UTF-8 nochmal nach UTF-8, weil er das Übergebene in UTF-8 speichern soll, dann ist es schon passiert. Er hat nun Daten, die er als UTF-8 betrachtet, die aber im Prinzip Datenmüll sind, das aber eigentlich korrekt UTF-8-kodiert.

        Bei MySQL kann das passieren, wenn die Kodierung eines Feldes auf utf8 eingestellt wird, man nicht beachtet, dass der Default-Wert für Client-Verbindungen latin1 ist, den nicht explizit aushandelt und nun vom Client aus UTF-8-kodiertes Zeug sendet. Aber auch andere Konstellationen sind als Ursache möglich.

        echo "$verabschiedung $name";

        1. Hallo,

          Bei MySQL kann das passieren, wenn die Kodierung eines Feldes auf utf8 eingestellt wird, man nicht beachtet, dass der Default-Wert für Client-Verbindungen latin1 ist, den nicht explizit aushandelt und nun vom Client aus UTF-8-kodiertes Zeug sendet. Aber auch andere Konstellationen sind als Ursache möglich.

          Ja, wir sind auch bereits davon überzeugt, dass es mit der Verbindung, also mit dem Treiber zu tun hat. Ohne, dass ich jetzt in der Manpage nachgelesen habe, wo kann ich die Kodierung bei der Übertragung ändern?

          Nichts desto trotz werde ich auch noch überprüfen, ob die Werte über POST überhaupt richtig übertragen werden, indem ich mir mal den Inhalt im Servlet ausgeben lasse.

          Markus

          --
          1. echo $begrüßung;

            Ja, wir sind auch bereits davon überzeugt, dass es mit der Verbindung, also mit dem Treiber zu tun hat. Ohne, dass ich jetzt in der Manpage nachgelesen habe, wo kann ich die Kodierung bei der Übertragung ändern?

            Zu Java kann ich nichts beitragen, nur allgemein zu dieser Thematik unter MySQL.
            Normalerweise muss es im Client-Umfeld die Möglichkeit geben, die Kodierung so einzustellen, dass das letztlich mit der Funktion mysql_set_character_set() der MySQL-API passiert. Nur so ist sichergestellt, dass mysql_real_escape_string() unter allen Kodierungen korrekt arbeiten kann. Für ISO-8859-x und UTF-8 reicht jedoch als Ersatz nach dem Verbindungsaufbau ein SET NAMES Statement abzusetzen.

            echo "$verabschiedung $name";

            1. Hallo,

              nun ich schätze mal wenn ich in der Programmlogik bereits gelandet bin, sei es PHP oder Java, dann ist es ja bereits zu spät. Das Problem müsste wesentlich "früher" angepackt werden.
              Vielleicht finde ich noch den Übeltäter, mal sehen.

              Markus

              --
              1. echo $begrüßung;

                nun ich schätze mal wenn ich in der Programmlogik bereits gelandet bin, sei es PHP oder Java, dann ist es ja bereits zu spät. Das Problem müsste wesentlich "früher" angepackt werden.

                Nein, denn die Verbindungskodierung ist ein verbindungsindividueller Wert, der erst nach dem erfolgreichen Öffnen einer Verbindung für eben diese ausgehandelt werden kann. Wenn die Verbindungsparameter an definierter Stelle konfiguriert werden (beispielsweise im Connection String unter ADO.NET), dann sollte die Verbindungskodierung auch dort konfiguriert werden können.

                echo "$verabschiedung $name";

                1. Also wenn ich im Servlet folgendes schreibe:

                  request.setCharacterEncoding("UTF-8");
                  response.setCharacterEncoding("UTF-8");

                  Dann funktioniert es jedenfalls bei der Ausgabe auf der JSP-Seite.
                  Komischerweise steht es jetzt wieder falsch in der MySQL-DB.
                  Da sieht man jetzt nur noch gefüllte Fragezeichen anstatt Umlaute?!

                  Markus

                  --
                  1. Übrigens, ich frage mich wieso das Auslesen genau dieser Daten dann wieder als richtig angezeigt werden?
                    Die Daten sind als nicht zerstört, sondern werden in der Datenbank aus irgend einem Grund nicht richtig gespeichert/angezeigt.
                    Ich verstehe das nicht.

                    --
                    1. echo $begrüßung;

                      Übrigens, ich frage mich wieso das Auslesen genau dieser Daten dann wieder als richtig angezeigt werden?

                      Wenn du beim Auslesen den gleichen Fehler wie beim Eintragen gemacht hast, kann es gut sein, dass sich dessen Auswirkungen gegenseitig aufheben. Beispielsweise wenn die Verbindungskodierung auf Latin1 steht, du aber UTF-8-kodierte Daten sendest, stehen die Bytes des Datenstrom als Zeichen nach Latin1 interpretiert in dem Feld. Funktionen wie CHAR_LENGTH() oder eine Sortierung liefern dir falsche Ergebnisse, aber beim Auslesen bekommst du wieder Latin1-Daten zurück, die du jedoch als UTF-8 interpretierst und damit die Bytes zweier Latin1-Zeichen wieder als Bytesequenz eines UTF-8-Zeichens ansehen wirst.

                      Die Daten sind als nicht zerstört, sondern werden in der Datenbank aus irgend einem Grund nicht richtig gespeichert/angezeigt.

                      "In der Datenbank angezeigt" geht im Prinzip nicht, da sich MySQL gemeinhin als Blackbox darstellt. Du brauchst immer eine Verbindung zu ihr, um den Inhalt abzuholen, und ihn dann irgendwo irgendwie darstellen zu können. Die Verbindung ist eine der 10 verschiedenartigen Stellen in einem MySQL-Server, an denen eine Kodierung individuell eingestellt werden kann. Und dann hast du ja noch eine weitere Schnittstelle zum anzeigenden Terminal oder zum Browser, bei der ebenfalls bei fehlerhafter Deklaration die Zeichenkodierung fehlinterpretiert werden kann.

                      Da der phpMyAdmin ein beliebtes Werkzeug ist und außerdem seine Hausaufgaben im Bereich der Kodierung gemacht hat, kann man ihn aber durchaus als Referenz verwenden, um die Richtigkeit der in der Blackbox gespeicherten Daten zu prüfen. Er kann aber auch nicht zaubern und eine als UTF-8-deklarierte aber ungültige Sequenz besser darstellen.

                      Wenn du kannst, schalte mal das Query-Log ein (ausschalten hinterher nicht vergessen, das müllt dir sonst (ohne logrotate) nur die Festplatte voll). Du siehst darin alle Verbindungsstarts und -enden sowie alle Querys. Ein mysql_set_character_set()-Aufruf ist da auch als SET NAMES-Query zu sehen. Siehst du kein SET NAMES, gelten die Werte der Systemvariablen character_set_client und character_set_connection sowie character_set_results für den Rückweg. Wenn du diese Variablen mit dem PMA abfragst, und der für diese Werte eine Zeile "Globaler Wert" anzeigt, gilt das für dich, und der andere Wert speziell nur für die aktuelle PMA-Verbindung. Die Bedeutung der drei Konfigurationsvariablen sind im Kapitel Connection Character Sets and Collations erläutert. (Der Teilbereich Kollation ist für das Zeichenkodierungproblem nicht von Bedeutung.) Mit aktiviertem Query-Log (MySQL-Server neu starten, nach Ändern der Konfigurationsdatei) solltest du nun erkennen, welche Kodierung ausgehandelt wird oder auch wenn das nicht passiert. Jetzt müsstest du das nur noch mit dem vergleichen, was du an Daten hinsendest und gegebenenfalls Differenzen in der Kodierung(sangabe) beseitigen. Dann sollte zumindest der Teil Datenbank problemlos funktionieren, was du mit dem PMA überprüfen kannst. Wenn zwischen Browser und Server noch Probleme sind, ist das eine andere Baustelle, die sich mit passenden charset-Angaben im Content-Type-HTTP-Header, im gleichnamigen Meta-Element und im accept-charset-Attribut von Formularen lösen lassen sollte.

                      echo "$verabschiedung $name";

                      1. Hallo,

                        danke für die ausführliche Beschreibung. Ich hatte zwar noch keine Zeit es auszuprobieren, aber werde es bald nachholen.

                        Markus

                        --