Stesso codice Java, stesso database, risultati diversi: quando Oracle dipende dalla sessione
Marco Magnarelli
@ContattamiDurante il lavoro quotidiano può capitare di imbattersi in problemi che, a prima vista, sembrano contraddire uno dei principi più intuitivi dello sviluppo software: a parità di codice e di dati, il risultato dovrebbe essere lo stesso. Questo articolo nasce proprio da uno di questi casi.
Il problema
In un’applicazione Spring Boot, interrogando una vista Oracle tramite JPA (findById), per recuperare dei valori per uno specifico identificativo, ottenevo risultati differenti a seconda dell’ambiente di esecuzione. In ambiente locale veniva restituito un certo valore, mentre sul server di sviluppo, pur puntando allo stesso database, il risultato era diverso.
L’aspetto più disorientante era che:
- il codice Java in entrambi gli ambienti era identico
- la vista interrogata era la stessa
- il database era unico e condiviso
Dopo aver escluso problemi di versionamento, caching applicativo e differenze di configurazione evidenti, risultava chiaro che l’anomalia non fosse causata dal codice applicativo.
A questo punto è sorta una domanda fondamentale: il database Oracle può restituire risultati diversi eseguendo la stessa query, sullo stesso database, ma da due server differenti?
La risposta è sì, e il motivo risiede nei parametri di sessione, in particolare nei parametri NLS.
Oracle e il concetto di sessione
Ad ogni connessione Oracle apre una sessione. All’interno di questa sessione il database valuta viste, funzioni e query sulla base di una serie di parametri contestuali, come lingua, territorio, formato delle date e dei timestamp, separatori numerici, regole di confronto e ordinamento delle stringhe, fuso orario.
Queste impostazioni influenzano direttamente il risultato delle query, soprattutto quando:
- vengono confrontati DATE o TIMESTAMP
- si utilizzano funzioni come TO_DATE, TO_CHAR, TRUNC
- sono presenti confronti o ordinamenti su stringhe
I parametri NLS
I parametri NLS (National Language Support) definiscono come Oracle interpreta e restituisce dati dipendenti dal contesto linguistico e geografico. Quelli più rilevanti in questo scenario erano:
- NLS_DATE_FORMAT
- NLS_TIMESTAMP_FORMAT
- NLS_NUMERIC_CHARACTERS
- NLS_SORT
- NLS_COMP
- NLS_LANGUAGE
- NLS_TERRITORY
- TIME_ZONE
È sufficiente che una vista utilizzi anche solo uno di questi aspetti affinché il risultato possa variare se la stessa query viene eseguita in sessioni con parametri diversi.
Verifica e identificazione del problema: loggare i parametri di sessione
Per confermare il sospetto, è stato utile ispezionare direttamente i parametri NLS della sessione Oracle aperta dall’applicazione. Ho aggiunto temporaneamente questo codice per interrogare la vista di sistema Oracle nls_session_parameters e loggare i valori utilizzati dalla sessione:
List<Object[]> resultQN = entityManager
.createNativeQuery(
"SELECT parameter, value FROM nls_session_parameters ORDER BY parameter"
)
.getResultList();
for (Object[] row : resultQN) {
String parameter = (String) row[0];
String value = (String) row[1];
log.info("NLS {} = {}", parameter, value);
}
con l’EntityManager iniettato tramite:
@Autowired
private EntityManager entityManager;
Il risultato ha mostrato differenze evidenti:
In locale la sessione era configurata con lingua e parametri italiani:
NLS_LANGUAGE = ITALIAN
NLS_TERRITORY = ITALY
NLS_NUMERIC_CHARACTERS = ,.
NLS_SORT = WEST_EUROPEAN
NLS_DATE_LANGUAGE = ITALIAN
NLS_TIMESTAMP_FORMAT = DD-MON-RR HH24:MI:SSXFF
Mentre sul server di sviluppo la sessione risultava impostata su lingua e territorio americani:
NLS_LANGUAGE = AMERICAN
NLS_TERRITORY = AMERICA
NLS_NUMERIC_CHARACTERS = .,
NLS_SORT = BINARY
NLS_DATE_LANGUAGE = AMERICAN
NLS_TIMESTAMP_FORMAT = DD-MON-RR HH.MI.SSXFF AM
A questo punto il comportamento divergente della vista risultava perfettamente spiegabile.
La causa reale era legata al fatto che il driver Oracle JDBC Thin eredita Locale e TimeZone della JVM che apre la connessione al database.
Nel mio caso, la JVM locale era configurata in it_IT, mentre quella del server utilizzava en_US. Di conseguenza Oracle inizializzava le sessioni con parametri NLS diversi, pur puntando allo stesso database.
Perciò Oracle non stava interpretando diversamente la query, ma la stava eseguendo in due contesti semantici differenti.
Come risolvere
Per risolvere il problema ho valutato diverse opzioni.
1. Cambiare localizzazione del sistema operativo
Una prima possibilità consiste nell’allineare la localizzazione del sistema operativo tra server e ambiente locale, ad esempio se ci trovassimo in ambiente Linux potremmo esportare le seguenti variabili prima dell’avvio della nostra applicazione.
export LANG=it_IT.UTF-8
export LC_ALL=it_IT.UTF-8
export TZ=Europe/Rome
E verificare il buon esito della configurazione attraverso i comandi locale e date.
Questa soluzione è valida quando si vuole influenzare solo i processi avviati in un certo contesto.
2. Impostare la localizzazione della JVM
Una soluzione più immediata è invece forzare la localizzazione della JVM impostando esplicitamente le opzioni:
-Duser.language=it
-Duser.country=IT
-Duser.timezone=Europe/Rome
Questo garantisce che il driver JDBC apra sessioni Oracle coerenti tra i diversi ambienti.
Esempio 1: comando start dell’ applicazione
java -Duser.language=it -Duser.country=IT -Duser.timezone=Europe/Rome -jar myapp-springboot.jar
3. Forzare i parametri NLS a livello di sessione
La soluzione più ottimale e definitiva rimane però quella di impostare i parametri NLS subito dopo l’apertura della connessione.
In Spring Boot questo può essere fatto tramite DataSourceInitializer.
Esempio 2: Soluzione con DataSourceInitializer
@Configuration
public class OracleNlsConfig {
@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ByteArrayResource((
"ALTER SESSION SET " +
"NLS_LANGUAGE = 'ITALIAN' " +
"NLS_TERRITORY = 'ITALY' " +
"NLS_DATE_LANGUAGE = 'ITALIAN' " +
"NLS_NUMERIC_CHARACTERS = ',.' " +
"NLS_SORT = 'WEST_EUROPEAN' " +
"NLS_TIMESTAMP_FORMAT = 'DD-MON-RR HH24:MI:SSXFF' " +
"NLS_TIMESTAMP_TZ_FORMAT = 'DD-MON-RR HH24:MI:SSXFF TZR'"
).getBytes()));
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(populator);
return initializer;
}
}
Questa soluzione agisce in fase di inizializzazione del DataSource ed è generalmente preferibile, poiché garantisce che ogni connessione JDBC venga associata a una sessione Oracle in modo coerente.
Conclusione
Il comportamento osservato non era un’anomalia né un bug, era la conseguenza diretta di parametri NLS differenti tra le sessioni.
ll punto chiave è che: Oracle non valuta una vista in modo assoluto, ma sempre nel contesto della sessione che la esegue.
Quando applicazioni diverse accedono allo stesso database, garantire l’allineamento dei parametri di sessione è fondamentale per evitare risultati inattesi, difficili da diagnosticare e spesso sottovalutati.
Questo caso dimostra quanto il contesto di esecuzione sia parte integrante del risultato, anche quando il codice e i dati sembrano identici.