Archiv der Kategorie: JDBC

SQL hat BOM gemacht: „Unexpected Token:“

Man hat mit Spring eine Testinfrastruktur schnell hochgezogen und immer mehr kommt hinzu. Schließlich schickt ein Kollege für einen speziellen Unit-Test eine SQL-Datei (PostgreSQL). SQL eingeladen und „Unexpected Token:“. Nichts weiter, kein Anhaltspunkt, leerer String. Ok, ich verwende HSQL-DB aber einfach SQL-Updates sollten nun wirklich nicht so verschieden sein.

Da erinnerte ich mich, eines ganz bestimmten Falles und sah mir die Datei im ‚vi‘ an. Keine Auffälligkeiten. Anschließend benutzete ich den Befehl ‚file‘. Und siehe da: Der Kollege benutzte ein Apfel-Betriebssystem und schickte mir das SQL mit einem heimtückischen BOM. Das brachte es mit sich, dass die ersten unsichtbaren 3 Bytes mit zur Datenbank gelangten, somit gleich der Anfang der ein defektes Token darstellt und folgerichtig das unexpected Token leer angezeigt wurde. Nun diesen unerwünschten Prefix des Datenstromes gelöscht und alles funktioniert, wie es soll.

Übrigens könnte man sich doch manchmal an Befehle wie ‚iconv -f ISO-8859-1 -t utf-8 FILE‘ erinnern.

JBoss AS7, PostgreSQL als Modul

Wenn man im JBoss PostgreSQL in zentral definierten Datasources (z. B. standalone.xml) verwenden will, z. B. für JNDI-Lookup, muss der entsprechende Treiber auch zentral eingetütet werden.

Hierfür kann man den Treiber als Modul im JBoss einfügen. Die geschieht relativ einfach, indem man das Treiber-Jar in einen Ordner $JBOSS_HOME/modules/org/postgresql/main kopiert.

Dann legt man an gleichem Ort einen Modul-Deskriptor modules.xml an.


<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.postgresql">
<resources>
<resource-root path="postgresql-9.1-903.jdbc4.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="javax.transaction.api"/>
<module name="javax.servlet.api" optional="true"/>
</dependencies>
</module>

Dieses Modul kann man nun in der standalone.xml einfügen (Pfad des Modules muss übereinstimmen, wie auch der Name):


<driver name="postgresql" module="org.postgresql"><xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class></driver>

Dieses Modul kann nun in der Definition der Datasource (standalone.xml) verwendet werden:


<datasource jndi-name="java:jboss/datasources/MyDataSource" pool-name="MyDataSource" enabled="true" use-java-context="true">
<connection-url>jdbc:postgresql://localhost:5432/testdb?charSet=utf-8</connection-url>
<driver-class>org.postgresql.Driver</driver-class>
<driver>postgresql</driver>
<security>
<user-name>USER</user-name>
<password>PASSWORD</password>
</security>
</datasource>

Aber: Es scheint von Belang zu sein, dass man als Modul einen JDBC-4-Treiber-JAR verwendet. Jedenfalls funktioniert nun folgendes JAR postgresql-9.1-903.jdbc4.jar.

Ein klasse Quelle für Informationen ist die JBoss-Community, herzlichen Dank.

Pitfall: Escaping in JDBC (Spring-JDBC-Template)

In Java benutzt man gerne den ‚_‘ als logischem Trenner. In ORACLE ist dies leider ein Platzhalter für ein Zeichen und sorgt so für eine gefährliche Tücke.
Wenn man unglücklicherweise eine Mustersuche hat mit „%_XXXX_%“ nach welcher gelöscht werden soll, dann löscht man im Zweifelsfalle mehr Kundendaten, als einem lieb ist.

In der Tabelle haben wir 2 Einträge:
AAA_175679_A
AAA_75679_A.

Mit einem Query-String derart „%_75679_%“ erhalten wir selbstverständlich beide Einträge von der Datenbank.

Kein Ergebnis, obwohl ‚\‘ der Default-Escape-Charakter in ORACLE ist:

List<String> queryForList = minervaJdbcTemplate.queryForList(
"select * from t_test where name like '%\\_75679\\_%'", String.class);

Wieder kein Ergebnis, obwohl ‚\\‘ als Escape-Charakter benutzt werden sollte:

queryForList = minervaJdbcTemplate.queryForList("select * from t_test where name like '%\\\\_75679\\\\_%'",
String.class);

Genau unser eines Ergebnis AAA_75679_A, da wir mit der Spezial-Notation {ESCAPE ‚&‘} dem JDBC ‚&‘ als Escape-Charakter vorgeben:

queryForList = minervaJdbcTemplate.queryForList(
"select * from t_test where name like '%&_75679&_%' {ESCAPE '&'}", String.class);

Pitfall getDate()

Lange kein JDBC mehr gemacht und prompt in die Date-Time-Timestamp-Falle getappt.

1. Date vs. Timestamp

Haben wir in der Datenbank z. B. PostgreSQL ein Date, dann ist es auch nur ein Datum ohne Zeit. In Oracle hingegen sagt DATE, dass wir auch eine Zeit anbei haben.
Besitzt nun die Datenbank-Spalte einen Zeitanteil und man benutzt java.sql.ResultSet.getDate(String), dann erhält man ein java.sql.Date nur mit einem Datum. Ruft man java.sql.ResultSet.getTimestamp(String) auf, dann erhält man ein java.sql.Timestamp, der ein java.util.Date ist und klaglos dem Konstruktor in meinem Row-Mapper übergeben wird.

Dieser Timestamp besitzt nun einen Zeitanteil wie gewünscht, bring aber einen Gotcha mit. Das Gemeine an der Sache ist, dass dieser Timestamp nicht kommutativ bzgl. der equals() Methode ist.


public boolean Timestamp.equals(java.lang.Object ts) {
if (ts instanceof Timestamp) {
return this.equals((Timestamp)ts);
} else {
return false;
}
}

Siehe hierzu auch Martin Odersky: How to Write an Equality Method in Java bzw. Joshua Bloch: Effective Java Programming.

Mittlerweile benutze ich, wenn es geht, ein Long in der Datenbank, bzw. eine String-Repräsentanz wie z. B. „yyyyDDMMHH24MI“. Letzeres ist zwar langsamer, aber vernünftig zu lesen, wenn man wie ich gerne in die DB selbst schaut.

Das kann man auch sehr schön als org.hibernate.usertype.UserType modellieren CalendarUserType -> long.

Aber es gibt durchaus auch andere Weisen, damit umzugehen Convert a java.sql.Timestamp Object to a java.util.Date Object?. 😀

2. java.sql.ResultSet.getDate(…, Calendar)

Gibt man bei obiger Methode keinen Calendar an, dann wird der der VM benutzt. Das zeitigt zum Teil üble Fehler, besonders wenn der Hosting-Dienstleister virtualisert oder ein neues Blech mit geänderter Timezone usw. hinstellt. Man sollte hier „immer“ den richtigen Calendar explizit übergeben. In der Praxis habe ich das bisher sehr selten gesehen und wundere mich, warum das alles überhaupt funktioniert.