Les principales versions de logiciel utilisées pour rédiger cet article sont les suivantes. Docker et Oracle Official Docker Image sont utilisés pour créer la base de données Oracle.
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 |
Comme il existe différentes méthodes de configuration pour les paramètres NLS d'Oracle Database, "Je ne sais pas quelle valeur de paramètre affecte comment!" Se produit, mais en utilisant le pilote JDBC pour se connecter à Oracle Database Si tel est le cas, les paramètres NLS peuvent changer en fonction des paramètres régionaux Java du client.
[Guide d'installation de la base de données pour la langue de connexion client Linux-Setting et les préférences Localt](https://docs.oracle.com/cd/E96517_01/ladbi/setting-language-preferences-for-client- Extrait de connections.html # GUID-78A71337-2199-4FED-B0F1-D313F769B22C):
Les applications Java qui utilisent Oracle JDBC pour se connecter à la base de données Oracle n'utilisent pas NLS_LANG. Au lieu de cela, Oracle JDBC mappe les paramètres régionaux par défaut de la machine virtuelle Java exécutant votre application aux paramètres de langue et de territoire de la base de données Oracle.
Expérimentons. Voici une application Java qui utilise JDBC et génère tout le contenu de V $ NLS_PARAMETERS
qui stocke les paramètres actuels des paramètres NLS.
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();
}
}
}
Faisons cela en modifiant les paramètres régionaux Java. Tout d'abord, le japonais.
$ 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
Vient ensuite l'anglais.
$ 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
J'ai trouvé que les paramètres NLS changent en fonction des paramètres régionaux côté client, même si je n'ai modifié aucun paramètre du côté de la base de données Oracle.
Ce qui est effrayant avec ce comportement, c'est que les choses suivantes peuvent se produire:
Le fait est que les applications Java peuvent se comporter différemment selon l'environnement. Si vous êtes un programmeur Java expérimenté, vous devriez essayer une programmation qui ne dépend pas du SGBD ou des paramètres régionaux, mais il y a une réalité qui ne l'est pas (´ ・ ω ・ `)
Nous concluons par un exemple plus concret de la façon dont ce comportement cause des problèmes.
Supposons que vous ayez une table appelée ʻuserscomme ceci: Supposons que cette table ait un nom d'utilisateur
nom et une heure de mise à jour record ʻupdated_at
(une configuration courante).
CREATE TABLE users (
name VARCHAR2(256 CHAR),
updated_at TIMESTAMP
)
Le Main2.java
suivant est une application Java qui insère des données dans cette table ʻusers`.
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();
}
}
}
__ Ce à quoi vous devez faire attention ici, c'est que vous essayez d'insérer une valeur de type chaîne (" 20200117 "
) dans la colonne de type TIMESTAMP
ʻupdated_at. __ Dans ce cas, la fonction "conversion de type implicite" d'Oracle Database renvoie le type de chaîne au type
TIMESTAMP`. Et la manière dont la "conversion de type implicite" est effectuée dépend fortement des paramètres NLS.
Maintenant, exécutons ce Main2.java
dans une langue différente. Tout d'abord, le japonais.
$ java -Duser.language=ja -Duser.country=JP -cp .:../lib/ojdbc8.jar Main2
PreparedStatement::executeUpdate = 1
Il semble que les données ont été insérées correctement. Ensuite, exécutez-le en anglais.
$ 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
Dans le cas de locale = Japan, cela a fonctionné comme prévu, mais dès que je suis passé à locale = English, j'ai obtenu une exception. De plus, il est difficile de dire en un coup d'œil que le contenu de l'exception est plutôt déroutant, ou du moins un problème lié aux paramètres régionaux. La solution n'est pas de faire une conversion de type implicite, ou d'utiliser le type «Date» de Java.
Recommended Posts