Archiv der Kategorie: Pitfall

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);

Maven-PlugIn und transitive Workspace-Abhängigkeiten

Die Fehler-Situation war eine Exception im XStream/CGLIB. Sowohl der POM-Editor, die Eclipse „Maven-Dependencies“ und auch sonst zeigten XStream 1.3 an, wie man es erwarten konnte.
Im POM für Projekt A ist XStream 1.3 als Abhängigkeit eingetragen. A hängt noch von B ab, in welchem XStream 1.2.2 eingetragen ist. Der Dependency-Eintrag im POM von A enthält einen entsprechenden Exclude auf XStream aus B. Soweit so gut.

Wenn man nun aber in Eclipse und dem „Maven Integration for Eclipse“-PlugIn die „Workspace Resolution“ eingeschaltet hat, dann kann es passieren, das über diesen Bypass ein zweites XStream in den Classpath wandert. Das kostet dann Zeit, weil es auf dem einen Eclipse-Helios-Workspace geht und auf dem anderen nicht. Das macht sich beim Kunden besonders schlecht, wenn man für das Deployment einen solchen Workspace benutzt (wie bei mir geschehen) und Stunden benötigt, auf das Problem zu kommen.

Ich tendiere immer mehr dazu, dass man mit Maven einen festen überprüfbaren Abhängigkeitsbaum initial erzeugt und diesen dann fest in das Projekt einbindet. Sollten neue Abhängigkeiten nötig werden, so geht man wieder diesen Weg: get dependencies, test it and freeze.

Calendar.isLenient()

Es gibt leider bei Date und Calendar in Java eine Menge Ungemach. Hierzu gehört auch das Aufweichen der Validität von Datumsangaben Leniency genannt.
In all cases, arguments given to methods for these purposes need not fall within the indicated ranges; for example, a date may be specified as January 32 and is interpreted as meaning February 1. java.util.Calendar

Aber hier haben die Designer in Java zwei Stolpersteine eingebaut.

1. isLenient() ist standardmäßig auf true(!) gesetzt. Das gibt bei fehlerhafter oder fehlender Validierung durch die höheren Layer lustige Effekte.

Gut, denkt man sich, dann mache ich einen Wrapper und kann hierbei noch diverse andere Klippen gleich mit umschiffen. Dann kommt aber manchmal ein Stolperstein 2 zum Tragen.

2. Und dieses ist, dass java.util.Calendar.isLenient() auch wirklich manchmal eingeschaltet werden muss. Weil es sonst (zumindest unter einer Java 1.5-Version, die wir just auch produktiv eingesetzt haben) eine interne Exception gibt, wenn nach mehreren SetXXX-Aufrufen der interne Calendar-Status bei periodEnd.getTime(); nicht mehr konsistent ist. Unter Java 1.6 tritt dies nicht mehr auf.

Calendar.getTime()/Date immer UTC

Was man sich vielleicht immer mal wieder klar machen sollte, wenn man nicht in die Date-Timestamp-Calendar-Hölle kommen will:
Calendar.getTime() ist immer UTC und hat nichts mit irgendeiner Timezone zu tun.

Although the Date class is intended to reflect coordinated universal time (UTC), it may not do so exactly, depending on the host environment of the Java Virtual Machine. java.util.Date

Hier gibt es ein paar der gesammtelten Irrtümer über die Datums und Zeitangaben, die ich immer noch in Legacy- und auch aktuellem Code finde (torsten-horn.de). Z. B. fängt bei meinem aktuellen Projekt der Tag – als Gas-Tag – entweder um 08:00 Uhr bzw. 06:00 Uhr an. Analog beginnt das Gas-Jahr am 1. Oktober des jeweiligen Kalender-Jahres. Von Lieferjahren und den darin vorkommenden Quartalen wollen wir gar nicht reden.

Oracle: Timezones and the Java Runtime Environment

Outboxing mit NPE

Ein Beispiel, welches mir gerade passiert ist:

long along = aObject.getAintegerObject();

Wenn aObject.getAintegerObject() null zurück liefert, dann knallt es mit einer NPE.
Es wird automatisch versucht, ein Integer in ein long zu überführen.

Es gibt Features, die besser abzustellen wären. Man stelle sich Matrixmanipulationen vor, wie z. B. Multiplikation, bei denen elementweise In- und Outboxing stattfindet, ohne, dass man eine Warnung vom Compiler erhält.

Wenn man mit Eclipse arbeitet, dann kann man hier den gewünschten Level Warn oder Error einschalten:

Preferences->Java->Compiler->Errors/Warnings->Potential Programming Problems->Boxing and unboxing conversions

Siehe auch stackoverflow.com

JNDI vs. LDAP-Binding

Wenn man z. B. einen Rollbacktest baut und dieser programmatisch JNDI-Ressourcen bindet, dann funktioniert eventuell ein ebenfalls gebundener LDAP nicht.


final SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
final Session mailSession = javax.mail.Session.getDefaultInstance(mailProperties);
builder.bind("java:comp/env/mailSession", mailSession);

Wenn man nun in seinem LdapAccess sich einen Context geben lässt, dann ist dies der zum SimpleNamingContextBuilder Gehörende und nicht wie angedacht ein LDAP-Directory-Context.

env.put(Context.INITIAL_CONTEXT_FACTORY, LdapCtxFactory.class.getCanonicalName());
anonCtx = new InitialDirContext(env);

Ohne das programmatische JNDI-Binding funktioniert die Sache wie gedacht und man erhält den LDAP-Context.

Java += mit implizitem Cast

Ein Freund von der Universität schickte mir folgenden Schnipsel, der eine überraschende Ausgabe erzeugt.


long g = 287654322;
g += 0f;
System.err.println(g);

Dem Problem kam ich nicht sofort auf die Spur. Aber ein Blick in die Spec verrät es uns:

E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.(Java Spec)

Also ist unsere Lösung das implizite Casten von float nach long.

final float a = (g + 1f);
g = (long)a; // ((long)2.87654336E8)

Dabei ist es natürlich egal, ob man 0f, 1f oder sonstige kleine Zahl wie 42f verwendet. Das Ergebnis ist dasselbe.

Auf alle Fälle ist mir wie an vielen anderen Stellen klar, dass man „Verkürzungen“ oder andere coole aber zulässige Schreibweisen einfach lassen sollte.
Keep it small and simple heißt hier, dass die „Verkürzung“ eine Verkomplizierung ist, deren Tragweite man manchmal nicht absieht.

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.

Ernsthaftes Problem: Maven2 und transitive Repositories

Plötzlich erhält man vom seinem Bamboo-Build mittels Maven2 folgende Meldung:
Unable to get dependency information: Unable to read local copy of metadata

Beim Blick in die Datei stellt sich heraus, dass es sich bei den maven-metadata-jaspersoft.xml um eine HTML-Datei handelt.
Diese beinhaltet den Hinweis, dass eine bestimmte URL nicht gefunden wird. Wir dachten, dass wir mit Nexus und unseren POMs auf der sicheren Seite wären, aber weit gefehlt. Dies kann übrigens auch passieren, wenn man zu Hause mit seinem neuen DSL spielt und der Provider einem statt 404 eine HTML-Seite durchreicht. So landen dann auch schon einmal HTML-Seiten als JAR-Dateien im Maven2-Repository.

Im Moment existiert noch keine befriedigende Lösung, soweit ich das gesehen habe.

Selbst wenn man sein Nexus-Repository-Proxy abgeschottet hat, muss man natürlich darauf achten, dass die POMs abgeschottet sind.

  • alle repository-Einträge zeigen nur auf gekapselte Nexus-Repositories
  • „central“ ist gesperrt


<repository>
<id>central</id>
<name>Maven Repository Switchboard</name>
<layout>default</layout>
<url>http://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>

Das Gleiche gilt natürlich auch für die Plug-In-Repositories.

Aber dies nützt einem alles nichts bei dem Problem der transitiven-Repositories.


[INFO] artifact commons-collections:commons-collections: checking for updates from jaspersoft
[DEBUG] Checking for pre-existing User-Agent configuration.
[DEBUG] Adding User-Agent configuration.
[DEBUG] Connecting to repository: 'jaspersoft' with url: 'http://www.jasperforge.org/maven2'.

Besonders unangenehm ist uns das bei jaspersoft (http://www.jasperforge.org/maven2) aufgefallen.
(Siehe: jasperforge.org espforum, http://stackoverflow.com)

Currently, there's no way to exclude more than one transitive dependency at a time, but there is a feature request for this on the Maven JIRA site:

http://jira.codehaus.org/browse/MNG-2315

Da mogelt sich doch tatsächlich ein weiteres Repository in die Abarbeitungskette. Und das ist eine fatale Sicherheitslücke. Es bleibt einem nichts anderes übrig, als auf mögliche Transitivitäten zu achten mit dem Scannen seines JAR-Dependency-Baumes z. B. unter WEB-INF/lib.

Auf alle Fälle zeigt dieser Vorfall, dass eine solch komplexe und transitive Struktur, wie die Maven-Abhhängigkeiten, gepflegt und überwacht sein will. Wäre uns dieser Crash beim Deployment zum Kunden passiert …