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 |
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.
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.
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 (´ ・ ω ・ `).
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