Ich habs jetzt mal versucht schnell durchzucoden, bin aber auf ein rätselhaftes Problem gestossen.
Probleme macht der DB2-Treiber.
Diese Probleme sind mir bisher unbekannt. Habe diesen unter Websphere4 und 5, Tomcat und aus einer Desktop-App benutzt.
Ich habe mal in einem Projekt mit Oracle via jdbc von Lotus5 aus kommuniziert, da sind solche Schwierigkeiten nicht aufgetreten.
Ich bekome eine
ava.lang.UnsatisfiedLinkError: SQLAllocEnv
at COM.ibm.db2.jdbc.app.DB2Driver.<init>(DB2Driver.java:244)
at
at
at lotus.domino.AgentBase.runNotes(AgentBase.java:160)
at lotus.domino.NotesThread.run(NotesThread.java:203)
Google gibt ein paar Dinge aus, oft in Verbindung mit DB2. Einige Spuren aber noch keine Ergebnisse.
Tja zu Demo-zwecken ist es vielleiht ok erstmal mit jdbc-odbc-Bridge zu arbeiten ( :P ). Diese ist bereits in den Java-Klassen auf Domino dabei (sollte aber in robusten Produktivumgebungen nicht verwendet werden.
Du kannst den Code ja mal ausprobieren.
Vorher musst du noch eine System ODBC Datenquelle für DB2 erzeugen (in Systemsteuerung/Verwaltung).
Als Datenbank verwende ich die SAMPLE-Datenbank. Eine Art Schulungsdatenbank, die Leute meist auf DB2 installiert haben (ähnlich wie Nordwind auf Access).
Sehr dankbar wäre ich, wenn Leute mit DB2 und LoNo5 oder LoNo6 mal den DB2 code ausprobieren. An den beiden markierten Stellen die Kommentare umswitchen (mach das später konfortabler).
Vielleicht liegt es auch an irgendwelchen Konfigurations-Merkwürdigkeiten bei mir.
Einstellungen des Agenten:
- Manually from actions menu
- (Run once @Commands may be used)
import lotus.domino.*;
import java.util.*;
import java.io.*;
import java.sql.*;
public class JavaAgent extends AgentBase {
//private final String jdbcUrl = "jdbc:db2:SAMPLE"; // Syntax siehe IBM DB2 JDBC Treiber Doku
private final String jdbcUrl = "jdbc:db2:SAMPLE";
private final String jdbcUser = "db2admin"; // Name von db2-superuser auf meinem Rechner
private final String jdbcPassword = "kennwort"; // 70% aller meiner kennwörter heissen so :-)
private Driver jdbcDriver = null;
public void NotesMain() {
/*
* Es wird eine JDBC-Verbindung aufgebaut und geschlossen.
* Die Abfolge der einzelnen Schritte ist immer gleich.
* ... auch wenn man z.B. von einer Desktop-Swing-GUI aus jdbc nutzt.
* ... oder von einem Application Server wie IBM-Websphere, Bea Weblogic oder Tomcat.
*/
// 1. JDBC-Treiber laden
// Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
// ---> big OOOOPPPPS: Jdbc-Treiber per Reflection laden geht nicht mit Notes!!!
// Dies hat mit Sycherheitschecks innerhalb von Java und Besonderheiten des VM-loading in Domino zu tun.
// sowas muss man wissen.
try {
//das geht bei mir!!!
jdbcDriver = new COM.ibm.db2.jdbc.app.DB2Driver();
// FUNKTIONIERT NICHT !!! mit db2-jdbc Treiber
//jdbcDriver = new sun.jdbc.odbc.JdbcOdbcDriver();
// Dies lokale Version des Treibers. Wesentlich performanter als net-Version (Paket *.net.* statt *.app.*).
// Kann auch benutzt werden, wenn z.B. DB2 Server auf Box A und Notes-Server auf Box B, aber auf Box B Db2 User-Client, auf
// dem anzusprechende Datenbank katalogisiert.
// 2. Verbindung zur Datenbank herstellen
Properties props = new Properties ();
props.put(jdbcUser, jdbcPassword);
Connection con = jdbcDriver.connect(jdbcUrl, props);
// 3. Sql Statement generieren
Statement stmt = con.createStatement();
// extrem oft ist aus Sicherheits- und Performance-Gründen PreparedStatement, bei LoNo-Agents glaub ich nicht
// 4. Statement gegen Datenbank senden
String query = "SELECT NAME FROM STAFF ORDER BY SALARY";
ResultSet rs = stmt.executeQuery(query);
// ZWISCHENSCHRITT START
// irgendwas mit dem ResultSet machen
while (rs.next())
System.out.println(rs.getString(1));
// ZWISCHENSCHRITT Ende
// 5. Aufräumen --> sehr, sehr wichtig, gerade für connections
rs.close();
stmt.close();
con.close();
// Notes-Besonderheit wg. Oooopppps unter Punkt 1
DriverManager.deregisterDriver(jdbcDriver);
} catch (Exception e) {
e.printStackTrace();
}
/*
Notes - Zeug erstmal auskommentiert!!!
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
} catch(Exception e) {
e.printStackTrace();
}
*/
}
}
Gruss Axel
es läuft mit IBM-JDBC treiber. ;D 8) ;D
import lotus.domino.*;
import java.util.*;
import java.io.*;
import java.sql.*;
public class JavaAgent extends AgentBase {
private final String jdbcUrl = "jdbc:db2:SAMPLE"; // Syntax siehe IBM DB2 JDBC Treiber Doku
private final String jdbcUser = "db2admin"; // Name von db2-superuser auf meinem Rechner
private final String jdbcPassword = "kennwort"; // 70% aller meiner kennwörter heissen so :-)
private Driver jdbcDriver = null;
public void NotesMain() {
try {
jdbcDriver = new COM.ibm.db2.jdbc.app.DB2Driver();
Properties props = new Properties ();
props.put(jdbcUser, jdbcPassword);
Connection con = jdbcDriver.connect(jdbcUrl, props);
Statement stmt = con.createStatement();
String query = "SELECT NAME FROM STAFF ORDER BY SALARY";
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
stmt.close();
con.close();
DriverManager.deregisterDriver(jdbcDriver);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Das Problem war, dass der Treiber nicht funktioniert, wenn man db2java.zip in den Agenten über "Edit Project" einbindet.
Man muss db2java.zip in die Notes.ini einbinden.
Und zwar eben von wo aus man den Agenten startet.
Wenn man den Agenten über das Aktionsmenu im Client startet, dann läuft er auf dem Client.
Die Zeile
JavaUserClasses=D:\SQLLIB\java\db2java.zip;
muss also in der Notes.ini des Clients stehen.
Für den Produktivbetrieb empfehle ich dringend mit serverseitigen Agenten zu arbeiten. Dann braucht db2java.zip nur in der ini des Servers eingebunden zu werden.
UND ZWAR GENAU AUS DEM SQLLIB\JAVA VERZEICHNIS. Nicht aus java11 oder java12 Verzeichnis von DB2 nehmen!!!
Das habe ich erst falsch gemacht.
So ist sichergestellt, das immer der JDBC-Treiber verwendet wird, der auch aktuell in DB2 unterstützt wird. Wenn man das falsche nimmt, stürzt der Notes-Client grundsätzlich ab.
DB2 läuft mit unterschiedlichen JDBC Treibern. Standardmässig läuft zumidest DB2 7 mit einem Java Version 1 Treiber. Normalerweise ändert jeder halbwegs ambitionierte Java-User das natürlich direkt auf Java2. Über das file usejdbc2.bat --> befindet sich in SQLLIB\java12).
Das kann man über usejdbc1 wieder zurückgeändert werden.
(bevor man das laufen lässt, alle DB2-Dienste und Notes-Client schliesen (letzteres warum auch immer)).
Die bats bewirken File-Copy-Operationen. Man kann sicher sein, dass im SQLLIB\Java Verzeichnis immer das "richtige" db2java.zip steht.
Notes5 läuft problemlos mit beiden, auch wenn das eine für java12 ist und Notes5 nur Java118 unterstützt.
Das entsprechende file kann dann natürlich auch von der Box des DB2-Server auf die Box des Notes-Servers kopiert werden. Zumindest wenn beides Windows ist. Bin mir nicht ganz sicher, aber kommen nicht auch DB2Clients mit dem SQLLIB\Java Unterverzeichnissen?
@quojote: Wenn du auf deinem Rechner einen DB2User-Client hast, mit dem du auf dem DB2-Server auf der AS400 zugreifen kannst, dann halte ich es für möglich, dass ein Notes-Agenten-Zugriff via JDBC auf die AS-400 möglich ist, ohne dass du dich mit AS400 Spezifika herumschlagen musst. Sicher bin ich mir nicht. Versuch es einfach mal aus, wenn du willst.
Der remote Zugriff auf die DB2 würde dann der optimierte DB2UserClient machen.
Ansonsten kannst du auch den net-Driver für remote Zugriffe auf das RDBMS verwenden. Remote muss aber grundsätzlich als anderes Wort für "Risiko schlechter Performance" verstanden werden.
Dafür müssten die folgenden Zeilen im code ausgetauscht werden:
private final String jdbcUrl = "jdbc:db2//<serverAS400>:<DB2_Java_Daemon_Listener_PORT>/DB_NAME";
statt
private final String jdbcUrl = "jdbc:db2:DB_NAME";
und
jdbcDriver = new COM.ibm.db2.jdbc.net.DB2Driver();
statt
jdbcDriver = new COM.ibm.db2.jdbc.app.DB2Driver();
Axel
Der entscheidende Hinweis( Danke Axel ) war das der Treiber über die UserClasses kommen muß und nicht beim Agent mitgegeben werden kann.
... wobei ich dich auch erst auf die Idee gebracht habe, ihn direkt in den Agenten einzubinden... ::) ;D
Ich versteh bloß nicht wieso beim Kompilieren findet der alles und beim ausführen nicht mehr. Notes ist da wohl ein bisserl vergesslich :-))
Meine Theorie, gestützt auf Dinge, die ich im Internet gelesen habe:
Die JDBC Treiber Klasse greift auf jeden Fall über Java Native Interface auf irgendwelche c-Klassen in SQLLIB bin zu. ...vielleicht werden die irgendwie relativ eingebunden und wenn db2java.zip im Projektverzeichnis des Agenten eingebunden wird, stimmen die Pfade nicht mehr und es wird eine Runtime-Exception geworfen.
Korrigiert mich, wenn jemand genaueres weiss.
Nach dem Urlaub bitte nicht vergessen:
Code Korrektur
Der jetzige Code kann bewirken, dass das connection Objekt nicht geschlossen wird.
Folgendes Scenario:
1 Connection con = jdbcDriver.connect(jdbcUrl, props);
2 Statement stmt = con.createStatement();
3 String query = "SELECT NAME FROM STAFF ORDER BY SALARY";
4 ResultSet rs = stmt.executeQuery(query);
5 while (rs.next()) {
6 System.out.println(rs.getString(1));
7 }
8 rs.close();
9 stmt.close();
10 con.close();
11 } catch (Exception e) {
12 e.printStackTrace();
13 }
In Zeile 1 wird die connection geöffnet. Der code läuft sequentiell ab. Irgendwo zwischen Zeile 2 und Zeile 9 zieht einer das Netzwerkkabel zum Datenbankserver. Der code kann nicht mehr auf die Datenbank zugreifen. Eine Exception wird geworfen und der Programmablauf geht in den catch-Block. Zeile 10 (Schliessen der Connection) wird dann nicht mehr aufgeführt. Nicht geschlossene Connections sind ein grosses Problem.
Deshalb:
import lotus.domino.*;
import java.util.*;
import java.io.*;
import java.sql.*;
public class JavaAgent extends AgentBase {
private final String jdbcUrl = "jdbc:db2:SAMPLE"; // Syntax siehe IBM DB2 JDBC Treiber Doku
private final String jdbcUser = "db2admin"; // Name von db2-superuser auf meinem Rechner
private final String jdbcPassword = "kennwort"; // 70% aller meiner kennwörter heissen so :-)
private Driver jdbcDriver = null;
public void NotesMain() {
try {
jdbcDriver = new COM.ibm.db2.jdbc.app.DB2Driver();
Properties props = new Properties ();
props.put(jdbcUser, jdbcPassword);
Connection con = jdbcDriver.connect(jdbcUrl, props);
Statement stmt = con.createStatement();
String query = "SELECT NAME FROM STAFF ORDER BY SALARY";
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
stmt.close();
DriverManager.deregisterDriver(jdbcDriver);
} catch (Exception e) {
e.printStackTrace();
} finally {
con.close();
try {
if (con != null) {
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
finally wird immer ausgeführt, egal ob der Programmablauf in den catch-Block verzweigt ist, oder ob er den try-Block bis zu Ende durchlaufen hat. Würde ich auf jedenfall immer so machen. Nicht geschlossene Statement und Resultset-Objekte sind nicht so schlimm, weil weil weil weil (ich glaube die irgendwie dann mitgeschlossen werden).
schönen Urlaub
Hallo Ralf,
weil ich nicht weiss, wann die automatische garbage-collection losläuft.
Es gilt als gute Programmierpraxis, sich nicht zu stark auf dem Garbage-collector zu verlassen. Dies gilt laut Literatur explizitest für Objekte, die andere Ressourcen binden wie Netzwerk-, Datenbank- oder File-Connections
Automatic garbage collection should not be perceived as a license for uninhibited creation of objects and forgetting about them. [...]
Certain objects, such as files and net connections, can tie up other resources and should be disposed of properly when they are no longer needed.
aus Mugal/Rasmussen: A programmers guide to java certification, S. 251f.
Dies nur als Beispiel.
Eine Datenbank-Connection bindet andere Ressourcen ausserhalb des Java Programms. Was wenn eine offene Connection existiert, der Anwender aber das Javaprogramm schliesst. Werden dann diese anderen Ressourcen freigegeben ??? Bin mir hier nicht sicher. Kann jemand was dazu sagen?
Wenn ich mich auf dem Garbage-Collector verlasse, warum dann überhaupt noch con.close()?
Wieso dies nicht einfach dem Garbage Collector überlassen? Wenn keine Referenz mehr auf das con-Objekt existiert (in unserem Beispiel am Ende der Methode NotesMain()) ist dieses Objekt eligible for garbage collection, also bräuchte ich con.close() gar nicht.
Gruss Axel
TomCat...
das 'aufrechthalten' der connections ist sowas von performancefressend...
wenn ich zeit finde, mach ich dir nen netten Screenshot...
no.no.no. (http://www.smilies.nl/nono.gif) ;D
So etwas habe ich noch nie gehabt.
Sprechen wir über das selbe?
aufrechterhalten der connection?
Ich habe verschiedenes für connection pooling eingesetzt (o.k. 2 verschiedene Sachen). Letztlich hatte es immer den selben Effekt. Aber performance-Probleme bei 5 connections ???
Ich habe connection pools mit "code von Frank Carver" und openSource jars benutzt. Heute ist es Bestandteil von Tomcat41 mit jndi, konfigurierbar mit der admin-Konsole und mit jdbc-2.0-j2ee-ext (also mit javax.sql.Datasource).
Würde ich jetzt immer damit machen. Hier ist Link:
Tomcat 4.1 doku (http://jakarta.apache.org/tomcat/tomcat-4.1-doc/jndi-datasource-examples-howto.html)
Bei dem code schliesst du natürlich die connection nach jeder Verwendung. Nur wird sie eben nicht geschlossen, sondern an den Pool zurückgegeben.
also tendentiell so:
// Das gemäss Service-Locator-J2EE-Pattern einbinden.
// Service Locator Schicht als Singleton realisiert und liefert verschiedene Servlets den Datasource-Pool.
// Aufruf dieses Konstrukts dann in init() der Servlets.
// Datasource jeweils als Instanzvariable der Servlets
// Da von doGet/doPost nur lesender Zugriff ist das ok.
Datasource ds =
(DataSource)ctx.lookup(
"java:comp/env/jdbc/TestDB");
//in doGet(), doPost() Methode.
if (this.ds != null) {
Connection conn = ds.getConnection();
[... do stuff ...]
conn.close();
Sprechen wir über das gleiche?
Werde das wenn ich zeit hab mal für das relativ neue JNDI-Datasource Feature von Tomcat durch-coden. Glaube nicht das das sonderlich den Rechner belastet. Kann mich irren.
Axel