Bei Verwendung von JDBC werden NLS-Parameter vom Java-Gebietsschema beeinflusst.

Ausführungsumgebung

Die wichtigsten Softwareversionen, die beim Schreiben dieses Artikels verwendet werden, sind folgende. Docker und Oracle Official Docker Image werden zum Erstellen der Oracle-Datenbank verwendet.

software version, edition
Oracle Database 12c Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production
ojdbc8.jar (12c JDBC Driver) Oracle 12.2.0.1.0 JDBC 4.2 compiled with javac 1.8.0_91 on Tue_Dec_13_06:08:31_PST_2016
javac javac 11.0.4
java openjdk version "11.0.4" 2019-07-16

Überblick

Da es verschiedene Einstellungsmethoden für NLS-Parameter von Oracle Database gibt, tritt "Ich weiß nicht, welcher Einstellungswert wie wirkt!" Auf, verwendet jedoch den JDBC-Treiber, um eine Verbindung zu Oracle Database herzustellen In diesem Fall können sich die NLS-Parameter je nach Java-Gebietsschema des Clients ändern.

[Datenbankinstallationshandbuch für Linux-Einstellungen für Clientverbindungssprache und Lokalisierungseinstellungen](https://docs.oracle.com/cd/E96517_01/ladbi/setting-language-preferences-for-client- Auszug aus Connections.html # GUID-78A71337-2199-4FED-B0F1-D313F769B22C):

Java-Anwendungen, die Oracle JDBC verwenden, um eine Verbindung zur Oracle-Datenbank herzustellen, verwenden NLS_LANG nicht. Stattdessen ordnet Oracle JDBC das Standardgebietsschema der Java-VM, auf der Ihre Anwendung ausgeführt wird, den Sprach- und Gebietseinstellungen der Oracle-Datenbank zu.

Experiment

Lass uns experimentieren. Das Folgende ist eine Java-Anwendung, die JDBC verwendet und den gesamten Inhalt von V $ NLS_PARAMETERS ausgibt, in dem die aktuellen Einstellungen der NLS-Parameter gespeichert sind.

Main1.java


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Main1 {
    public static void main(String[] args) {
        String url = "jdbc:oracle:thin:@//192.168.99.100:1521/ORCLPDB1";
        String user = "dev1";
        String password = "password";

        try (Connection c = DriverManager.getConnection(url, user, password)) {
            Statement stmt = c.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT * FROM V$NLS_PARAMETERS ORDER BY PARAMETER");
            while (rs.next()) {
                String parameter = rs.getString("PARAMETER");
                String value = rs.getString("VALUE");
                System.out.printf("%s = %s%n", parameter, value);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Lassen Sie uns dies tun, indem Sie das Java-Gebietsschema ändern. Zuallererst Japanisch.

$ java -Duser.language=ja -Duser.country=JP -cp .:../lib/ojdbc8.jar Main1
NLS_CALENDAR = GREGORIAN
NLS_CHARACTERSET = AL32UTF8
NLS_COMP = BINARY
NLS_CURRENCY = ¥
NLS_DATE_FORMAT = RR-MM-DD
NLS_DATE_LANGUAGE = JAPANESE
NLS_DUAL_CURRENCY = \
NLS_ISO_CURRENCY = JAPAN
NLS_LANGUAGE = JAPANESE
NLS_LENGTH_SEMANTICS = BYTE
NLS_NCHAR_CHARACTERSET = AL16UTF16
NLS_NCHAR_CONV_EXCP = FALSE
NLS_NUMERIC_CHARACTERS = .,
NLS_SORT = BINARY
NLS_TERRITORY = JAPAN
NLS_TIMESTAMP_FORMAT = RR-MM-DD HH24:MI:SSXFF
NLS_TIMESTAMP_TZ_FORMAT = RR-MM-DD HH24:MI:SSXFF TZR
NLS_TIME_FORMAT = HH24:MI:SSXFF
NLS_TIME_TZ_FORMAT = HH24:MI:SSXFF TZR

Als nächstes kommt Englisch.

$ java -Duser.language=en -Duser.country=US -cp .:../lib/ojdbc8.jar Main1
NLS_CALENDAR = GREGORIAN
NLS_CHARACTERSET = AL32UTF8
NLS_COMP = BINARY
NLS_CURRENCY = $
NLS_DATE_FORMAT = DD-MON-RR
NLS_DATE_LANGUAGE = AMERICAN
NLS_DUAL_CURRENCY = $
NLS_ISO_CURRENCY = AMERICA
NLS_LANGUAGE = AMERICAN
NLS_LENGTH_SEMANTICS = BYTE
NLS_NCHAR_CHARACTERSET = AL16UTF16
NLS_NCHAR_CONV_EXCP = FALSE
NLS_NUMERIC_CHARACTERS = .,
NLS_SORT = BINARY
NLS_TERRITORY = AMERICA
NLS_TIMESTAMP_FORMAT = DD-MON-RR HH.MI.SSXFF AM
NLS_TIMESTAMP_TZ_FORMAT = DD-MON-RR HH.MI.SSXFF AM TZR
NLS_TIME_FORMAT = HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT = HH.MI.SSXFF AM TZR

Ich habe festgestellt, dass sich die NLS-Parameter je nach clientseitigem Gebietsschema ändern, obwohl ich auf der Oracle-Datenbankseite keine Einstellungen geändert habe.

Was ist beängstigend?

Das Beängstigende an diesem Verhalten ist, dass Folgendes passieren kann:

Der Punkt ist, dass sich Java-Anwendungen je nach Umgebung unterschiedlich verhalten können. Wenn Sie ein erfahrener Java-Programmierer sind, sollten Sie versuchen, eine Programmierung durchzuführen, die nicht von DBMS oder dem Gebietsschema abhängt. Es gibt jedoch eine Realität, die nicht so ist (´ ・ ω ・ `).

Spezifisches Beispiel: Implizite Typkonvertierung vom Zeichenfolgentyp zum TIMESTAMP-Typ

Wir schließen mit einem konkreteren Beispiel, wie dieses Verhalten Probleme verursacht.

Angenommen, Sie haben eine Tabelle mit dem Namen "Benutzer" wie folgt: Angenommen, diese Tabelle hat einen Benutzernamen "Name" und eine Datensatzaktualisierungszeit "Updated_at" (eine übliche Konfiguration).

CREATE TABLE users (
  name VARCHAR2(256 CHAR),
  updated_at TIMESTAMP
)

Die folgende Main2.java ist eine Java-Anwendung, die Daten in diese` Benutzertabelle einfügt.

Main2.java


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main2 {
    public static void main(String[] args) {
        String url = "jdbc:oracle:thin:@//192.168.99.100:1521/ORCLPDB1";
        String user = "dev1";
        String password = "password";

        try (Connection c = DriverManager.getConnection(url, user, password)) {
            PreparedStatement pstmt = c.prepareStatement("INSERT INTO users (name, updated_at) VALUES (?, ?)");
            pstmt.setString(1, "nekoTheShadow");
            pstmt.setString(2, "20200117");
            int count = pstmt.executeUpdate();

            System.out.printf("PreparedStatement::executeUpdate = %d%n", count);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

__ Was Sie hier beachten sollten, ist, dass Sie versuchen, einen Zeichenfolgentypwert (" 20200117 ") in das update_at einzufügen, das eine Spalte vom Typ TIMESTAMP ist. __ In diesem Fall gibt die Funktion "Implizite Typkonvertierung" von Oracle Database den Zeichenfolgentyp in den Typ "TIMESTAMP" zurück. Und wie "implizite Typkonvertierung" durchgeführt wird, hängt stark von den NLS-Parametern ab.

Lassen Sie uns nun diese Main2.java in einem anderen Gebietsschema ausführen. Zuallererst Japanisch.

$ java -Duser.language=ja -Duser.country=JP -cp .:../lib/ojdbc8.jar Main2
PreparedStatement::executeUpdate = 1

Es scheint, dass die Daten korrekt eingefügt wurden. Führen Sie es dann auf Englisch aus.

$ java -Duser.language=en -Duser.country=US -cp .:../lib/ojdbc8.jar Main2
java.sql.SQLDataException: ORA-01843: not a valid month

        at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:494)
        at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:446)
        at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:1054)
        at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:623)
        at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:252)
        at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:612)
        at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:226)
        at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:59)
        at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:910)
        at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1119)
        at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3780)
        at oracle.jdbc.driver.T4CPreparedStatement.executeInternal(T4CPreparedStatement.java:1343)
        at oracle.jdbc.driver.OraclePreparedStatement.executeLargeUpdate(OraclePreparedStatement.java:3865)
        at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3845)
        at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1061)
        at Main2.main(Main2.java:16)
Caused by: Error : 1843, Position : 51, Sql = INSERT INTO users (name, updated_at) VALUES (:1 , :2 ), OriginalSql = INSERT INTO users (name, updated_at) VALUES (?, ?), Error Msg = ORA-01843: not a valid month

        at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:498)
        ... 15 more

Im Fall von locale = Japan funktionierte es wie erwartet, aber sobald ich zu locale = English wechselte, bekam ich eine Ausnahme. Darüber hinaus ist auf einen Blick schwer zu erkennen, dass der Inhalt der Ausnahme eher verwirrend oder zumindest ein vom Gebietsschema initiiertes Problem ist. Die Lösung besteht nicht darin, eine implizite Typkonvertierung durchzuführen oder den Java-Typ "Date" zu verwenden.

Recommended Posts

Bei Verwendung von JDBC werden NLS-Parameter vom Java-Gebietsschema beeinflusst.
Unterschiede im Code bei Verwendung des Längensystems in Java
Bei Verwendung des Konstruktors der Date-Klasse von Java wird das Datum um 1900 vorverlegt.
Wechseln Sie die von SDKMAN installierte Java-Version, wenn Sie Verzeichnisse verschieben
Sehen wir uns das Ausführungsergebnis an, wenn Sie die vorbereitete Anweisung in JDBC verwenden
So beheben Sie den unbekannten Fehler, der bei der Verwendung von slf4j in Java aufgetreten ist
Ist die von Ihnen verwendete Version von Elasticsearch mit Java 11 kompatibel?
Java-Vergleich mit der compareTo () -Methode
Unerwartete Ausnahme bei Verwendung von DateTimeFormatter in Java