# ODBC-Treiber



## RealHAZZARD (22. März 2006)

*ODBC/JDBC-Treiber*

Servus.

_Edit: Ich meine natürlich die JDBC-Treiber_

Ich möchte ein kleines Programm schreiben, welches auf 
unterschiedliche SQL-Server (von unterschiedlichen Herstellern)
 connecten kann(und natürlich auch darauf arbeiten).

Die Treiber kann man sich ja beim Hersteller herunterladen...
Meine Idee ist jetzt, dass der Benutzer mein Programm zur Laufzeit 
mit neuen Treibern füttern kann. Und mein Programm soll dann erkennen,
welchen Treiber es da bekommen hat, von welchem Hersteller, und für
welche Versionen(SQL-Server) das kompatibel ist.
Natürlich soll mein Programm dann auch den Server fragen, auf welcher Version, und
von welchem Hersteller dieser rennt.

Hat da einer einen Lösungsansatz?


----------



## RealHAZZARD (22. März 2006)

```
DriverManager.getDriver("jdbc:mysql://localhost/")
```
...eignet sich schonmal nicht für dynamisches "Herausfinden des Treibers".
Denn wie in dem Beispiel gezeigt, muss ich ja schonmal wissen, 
von welchem Hersteller der DB-Server ist, um das subprotokoll angeben zu können.
Denn ohne geht es nicht. Und überhaupt. Was mach ich denn mit dem getDriver.
OK, es liefert einen Driver zurück, aber den muss ich doch nicht haben, wenn der
 DriverManager den schon hat, oder versteh ich hier was falsch?
Gibt es vielleicht eine Möglichkeit, herauszufinden, von welchem Hersteller die Server kommt?


----------



## RealHAZZARD (23. März 2006)

Hallo?

Naja...Ich hab da mal noch etwas gefunden, aber ich komm nicht wirklich damit klar.
Es geht um den Connector von Sun.
Der ist zum einen für die Treiber-Entwickler gedacht, zum anderen für Entwickler die einfach weitere JDBC - Treiber hinzufügen wollen (also doch eigentlich auch für mich).
Aber ich versteh nicht ganz was ich da wie verwenden soll.

Hat schonmal jemannd mit dem Connector von Sun gearbeitet?


----------



## RealHAZZARD (24. März 2006)

Hallo?
Hat denn keiner eine Idee?


----------



## Thomas Darimont (24. März 2006)

Hallo!

... das ist ganz einfach... verwende einfach nicht mehr die ur-alte DriverManager Vorgehensweise, sondern verwende die javax.sql.DataSource implementierungen desentsprechenden JDBC Drivers... den JDBC Treiber (bzw. das entsprechende Jar) kannst du dynamisch zur Laufzeit laden.

Siehe auch hier:

```
/**
 * 
 */
package de.tutorials;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

/**
 * @author daritho
 * 
 */
public class DynamicClassPathExtensionExample {

    private static final String ORACLE_JDBC_POOL_ORACLE_DATA_SOURCE = "oracle.jdbc.pool.OracleDataSource";

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        addUrlToClasspath((URLClassLoader) Thread.currentThread()
                .getContextClassLoader(), new File(
                "D:/oracle/jdbc/lib/ojdbc14.jar").toURL());

        DataSource dataSource = createDataSourceForOracle(
                "user",
                "password",
                "localhost",
                1521,
                "ORCL",
                "thin");

        Connection con = dataSource.getConnection();
        con.close();
    }

    private static void addUrlToClasspath(URLClassLoader classLoader, URL url)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, MalformedURLException {
        Method method = URLClassLoader.class.getDeclaredMethod(
                "addURL",
                new Class[] { URL.class });
        method.setAccessible(true);
        method.invoke(classLoader, new Object[] { url });
    }

    private static DataSource createDataSourceForOracle(
            String user,
            String password,
            String serverName,
            int portNumber,
            String databaseName,
            String driverType) throws Exception {
        DataSource oracleDataSource = (DataSource) Thread.currentThread()
                .getContextClassLoader().loadClass(
                        ORACLE_JDBC_POOL_ORACLE_DATA_SOURCE).newInstance();
        Map propertyMap = new HashMap();
        // Instance property mapping koennte aus einer XML Datei kommen
        // Oder es wird vom Benutzer spezifiziert.
        propertyMap.put("user", user);
        propertyMap.put("password", password);
        propertyMap.put("serverName", serverName);
        propertyMap.put("portNumber", Integer.valueOf(portNumber));
        propertyMap.put("databaseName", databaseName);
        propertyMap.put("driverType", driverType);
        setInstanceProperties(oracleDataSource, propertyMap);
        return oracleDataSource;
    }

    private static void setInstanceProperties(Object instance, Map propertyMap)
            throws Exception {
        BeanInfo beanInfo = Introspector.getBeanInfo(instance.getClass());
        PropertyDescriptor[] propertyDescriptors = beanInfo
                .getPropertyDescriptors();
        for (int i = 0; i < propertyDescriptors.length; i++) {
            PropertyDescriptor descriptor = propertyDescriptors[i];
            String propertyName = descriptor.getName();
            if (propertyMap.containsKey(propertyName)) {
                descriptor.getWriteMethod().invoke(
                        instance,
                        new Object[] { propertyMap.get(propertyName) });
            }
        }
    }
}
```

Gruss Tom


----------



## RealHAZZARD (24. März 2006)

Vielen Dank.
Ich werd das bald einmal ausprobieren.


----------



## elmato (24. März 2006)

Sieht echt toll aus nur Versteh ich die hälfte nicht :/ von wegen ganz einfach.....


----------



## RealHAZZARD (24. März 2006)

Naja einfach ist relativ, und nicht immer der beste Lernweg.
Du wirst anhand weiterer Postings (von mir) sehen wie einfach ich
es finden werde.
Aber ich bin schon zufrieden, dass es einen gibt, der mal eine Antwort auf meine Frage hat.


----------



## RealHAZZARD (28. März 2006)

Soooo....
Ich hab mich jetzt mal mit dem Beispiel von dir befasst. @ Thomas
Ich komm da im allgeimeinen noch nicht so wirklich klar.
Ich verstehe nicht ganz was du da machst.
Kannst du mir das etwas näher erklären, oder hast du vielleicht ein Tutorial dazu?
Kann ich denn mit der Herangehensweise das ganze auch dynamisch halten?...
Soll meinen:
Der Benutzer richtet eine neue Verbindung in meinem Programm ein. Und mein Prog schaut gleich mal nach, ob es 1. den Server gibt(das soll mal nicht das Prob werden) und 2. welchen Treiber das Prog bräuchte, um eine voll funktionsfähige Verbindung auf zu bauen.
Geht das?


----------



## Schlafor (30. Juli 2008)

Uuuudelellie!

Auch wenn sich die Sache längst erledigt haben sollte, möchte ich doch ein paar Leidensgenossen, die sich ebenfalls jetzt erst mit diesem Thema beschäftigen meinen Lösungsweg nicht vorenthalten. Man stößt bei Google doch immer wieder auf diesen Thread.

Thomas Lösung ist zwar sehr geschickt aber ich weiß nicht, ob es so gut ist eine Methode, die den Access Modifier protected hat, namentlich "addURL" mittels der reflect API accessible zu machen. Bin kein Experte und lasse mich gerne eines Besseren belehren.

Man könnte doch eher die Methode loadClass("package.classname") des URLClassLoaders verwenden:


```
import java.sql.DriverManager;
import java.net.URLClassLoader;
import javax.sql.DataSource;
import java.lang.reflect.*;

public class ClassLoaderTest{
	public static void main(String[] args) throws InstantiationException, ClassNotFoundException, java.net.MalformedURLException, IllegalAccessException {		

		java.net.URL p = new java.net.URL("file:\\C:\\Programme\\MySQL\\MySQL Tools for 5.0\\java\\lib\\mysql-connector-java-5.0.4-bin.jar");

		URLClassLoader loader = new URLClassLoader(new java.net.URL[]{p}, Thread.currentThread().getContextClassLoader() );		

		DataSource ds = (DataSource) loader.loadClass("com.mysql.jdbc.jdbc2.optional.MysqlDataSource").newInstance();

		try{
			Method meth = ds.getClass().getDeclaredMethod("setUser", new Class[]{ String.class });

			meth.invoke(ds, new Object[] { "Halloroot" });

			meth = ds.getClass().getDeclaredMethod("getUser", new Class[]{  });

			System.out.println( meth.invoke(ds, new Object[0]) );
		}catch( NoSuchMethodException ex ){
			System.out.println(ex.getMessage());
		}catch( InvocationTargetException ex ){
			System.out.println(ex.getMessage());
		}
	}
}
```

Erläuterungen:

WICHTIG: Ich habe es bisher nur für einen Connector (Mysql) getestet und auch noch keine Connection hergestellt. Werde mich nochmal melden, wenn ich mehrere ausprobiert habe.

Man muss die Methoden setUser, setPort etc... mittels der java.lang.reflect.Method aufrufen, da die Methoden im Interface javax.sql.DataSource nicht vorgeschrieben wurden, man aber nur eine gültige Connection von der DataSource bekommen kann, wenn diese Attribute alle gesetzt sind.

Die Nutzung von beans, wie bei Thomas Lösung zu sehen, ist wohl die von Java bevorzugte um die Methoden einer DataSource aufzurufen. (Scheint nebenbei bemerkt auch die elegantere Variante zu sein) 

Ich verstehe nur nicht ganz warum man die Methoden nicht einfach im Interface vorgschrieben hat, damit die Methoden nach Instanzierung zur Verfügung stehen. Aber auch hier lasse ich mich gerne eines besseren belehren 

Gruß,

Schlafor


----------



## Schlafor (30. Juli 2008)

Uuudelellieee!

Warum die Methoden nicht im Interface vorgeschrieben wurden weiß ich jetzt. Es gibt so etwas wie eine Naming and Directory (JNDI) API. Damit kann man wohl einen Service / Dienst bereitstellen, der die zur Verbindungsherstellung notwendigen Daten bereitstellt. 

Wenn ein Client sich nun bei der Datenbank anmelden möchte, muss man nur noch diese Daten beim Service erfragen und kann daraus direkt eine DataSource erstellen. 


```
InitialContext ctx = new InitialContext();
DataSource s = (DataSource) ctx.lookup("jdbc/ServiceName");
Connection con = s.getConnection( user, pass );
```

Das hat den Vorteil, dass man bei Änderung der Datenbank nicht jeden Client anfassen, sondern die Änderungen nur in den Daten vornehmen muss, die durch den Service angeboten werden.

Egal, mir war das mit der protected Methode wichtig.

Hier nochmal für MySQL mit gültiger Connection, nur damit die ganze Sache abgerundet wird:


```
public static void main(String[] args) throws InstantiationException, ClassNotFoundException, java.net.MalformedURLException, IllegalAccessException, java.sql.SQLException {		

		java.net.URL p = new java.net.URL("file:\\C:\\Programme\\MySQL\\MySQL Tools for 5.0\\java\\lib\\mysql-connector-java-5.0.4-bin.jar");

		URLClassLoader loader = new URLClassLoader(new java.net.URL[]{p}, Thread.currentThread().getContextClassLoader() );		

		DataSource ds = (DataSource) loader.loadClass("com.mysql.jdbc.jdbc2.optional.MysqlDataSource").newInstance();

		try{
			Method meth = ds.getClass().getDeclaredMethod("setUser", new Class[]{ String.class });

			meth.invoke(ds, new Object[] { "AAAA" });

			meth = ds.getClass().getDeclaredMethod("setPort", new Class[]{ int.class });

			meth.invoke(ds, new Object[] { 3306 });	

			meth = ds.getClass().getDeclaredMethod("setPassword", new Class[]{ String.class });

			meth.invoke(ds, new Object[] { "AAAAAA" });

			meth = ds.getClass().getDeclaredMethod("setServerName", new Class[]{ String.class });

			meth.invoke(ds, new Object[] { "localhost" });

			java.sql.Connection con = ds.getConnection();

			System.out.println( con.isValid(10) );

		}catch( NoSuchMethodException ex ){
			System.out.println("Methode nicht gefunden " + ex.getMessage());
		}catch( InvocationTargetException ex ){
			System.out.println(ex.getMessage());
		}
	}
```

Aber das mit dem Service will ich jetzt auch nochmal ausprobieren .


----------



## Schlafor (31. Juli 2008)

Uuuudelelliiiieee!

Ich habe mit JNDI mal etwas rumprobiert und bin zum Entschluss gekommen, das es sich für mich nicht lohnt einen solchen Aufwand zu betreiben. (Auch wenns vom ursprünglichen Thema abweicht).

Hier findet sich ein ausführliches Tuorial: http://java.sun.com/products/jndi/tutorial/getStarted/overview/index.html

Ich mache es auf jeden Fall wie oben beschrieben.

Einfach wie oben über ein URL-Objekt der Treiber von einem Webserver herunterladen. Die benötigten Pfade (Datasource Path innerhalb des Treibers / Jars) ebenfalls vom Webserver laden und man kann die DataSource selbst erstellen.


----------



## Schlafor (31. Juli 2008)

JNDI-Tutorial - Nachtrag, ums zu komplettieren

http://java.sun.com/products/jndi/tutorial/

eignet sich für Systemlandschaften, in denen die DataSource über einen Applikationsserver und ein JNDI-konformes Protokoll wie LDPA bereitgestellt wird. 

Dann hat man gegenüber Thomas bzw. meiner Lösung den Vorteil, dass nicht der gesamte Treiber über das Netzwerk übertragen werden muss. Auch wenns nur einmal für den Client bei Startup der Applikation vorgenommen werden muss. 

Mit JNDI kann man die DataSource-Klasse einzeln übergeben und sie muss beim Client auch nicht mehr mittels ClassLoadern geladen werden. Ein DataSource-Objekt wird einfach auf dem Applikationsserver mittels JNDI an eine URL gebunden, die dann vom Client abgerufen werden kann.

Das kommt auch dem pooling von Verbindungen zugute, dass dann ebenfalls auf dem Server realisiert werden kann.

Hier eine kleine Sammlung interessanter Links:
Beispiele 5 und 6 im Skript
http://www.jeckle.de/vorlesung/DB-Anwendungen/script.html#jdo

JavaClassLoader - sehr theoretisch
http://it-republik.de/jaxenter/artikel/Java-Classloader-1261.html

dynamisches Laden von DB-Treiber mit ClassLoader und DriverManager, also ohne DataSource
http://www.kfu.com/~nsayer/Java/dyn-jdbc.html

Nochmal ClassLoader in Verbindung mit *.jar-Archiven
http://www.javaworld.com/javaworld/javatips/jw-javatip70.html


Wenn ich darf, würde ich das ganze dann als erledigt markieren.


----------



## Thomas Darimont (31. Juli 2008)

Hallo,

danke für die Ausführungen. Das hättest du auch ruhig in einen eigenen Thread packen können ;-.)



> Dann hat man gegenüber Thomas bzw. meiner Lösung den Vorteil, dass nicht der gesamte Treiber über das Netzwerk übertragen werden muss. Auch wenns nur einmal für den Client bei Startup der Applikation vorgenommen werden muss.


Also bei keiner der aufgezeigten Beispiele wird irgend einer Treiber übers Netzwerk geladen... 



> Mit JNDI kann man die DataSource-Klasse einzeln übergeben und sie muss beim Client auch nicht mehr mittels ClassLoadern geladen werden. Ein DataSource-Objekt wird einfach auf dem Applikationsserver mittels JNDI an eine URL gebunden, die dann vom Client abgerufen werden kann.


Das Stimmt so nicht ganz. Die datasources die über das JNDI verwaltet werden, spricht man in der Praxis nur innerhalb des selben Containers an. Entfernte Clients ziehen sich nicht die DataSource von einem anderen Rechner...  das ginge auch nicht ohne weiteres denn die DataSource ist nicht serialisierbar. Weiterhin geht man eher den Weg den Datenbankzugriff von den Clients zu abstrahieren, so dass diese so "low-level" Persistenz Details wie eine DataSource gar nicht zu Gesicht bekommen.

Gruß Tom


----------



## Schlafor (31. Juli 2008)

Uuuudelellieeee!

stimmt. Die Clients holen sich ja nur die Daten beim Applikationsserver ab. Vielen Dank für den Hinweis . Es würde ja das Konzept ad absurdum führen die dataSource zu liefern. Ich bin nur so in meiner Lösung drin, dass ich da was verwechselt habe :-( (siehe unten).

Wie gesagt, bin kein Experte und habe grad erst begonnen mich in Java und Datenbanken einzuarbeiten. 

Tom: "Also bei keiner der aufgezeigten Beispiele wird irgend einer Treiber übers Netzwerk geladen..." 

Sry, ich hatte mir kurz bevor ich die Nachricht verfasst habe eine Lösung mit einem Apachen gebastelt und vergessen zu posten. 

Aber das lässt sich ja nachholen:


```
public static void main(String[] args) throws InstantiationException, ClassNotFoundException, java.net.MalformedURLException, IllegalAccessException, java.sql.SQLException {		

        // Habe diese URL mittels ALias in den HTML-Baum des Apachen eingebunden ->
        // physischer Pfad auf den DB-Treiber: C:\Programme\Apache Software
        // Foundation\Apache2.2\htdocs\connector\jdbc\mysql-connector-java-5.1.6-bin.jar        
        /*
         * Dort liegt dann also der DB-Treiber als jar
         */
        java.net.URL p = new java.net.URL("http://localhost/connector/jdbc");

        // Der ClassLoader lädt den Treiber nach, weil das *.jar ja nicht auf dem Client zur Verfügung steht und
        // der Treibr somit auch nicht in der CLASSPATH-Umgebungsvariable des Clients nicht bekannt ist
        // Es scheint wichtig zu sein, welchen ClassLoader man dem URLClassLoader im Konstrukt übergibt.
        // Ganz durchgestiegen bin ich da aber nicht. Egal einfach den ClassLoader des aktuellen Threads nehmen und gut. Danke an Tom.
        URLClassLoader loader = new URLClassLoader(new java.net.URL[]{p}, Thread.currentThread().getContextClassLoader() );

        // DataSource ist das Interface und in der loadClass() wird der Klassenpfad zur MysqlDataSource angegeben
        // dann instanzieren e volia
        DataSource ds = (DataSource) loader.loadClass("com.mysql.jdbc.jdbc2.optional.MysqlDataSource").newInstance();

        // So hier fehlen mir noch ein paar Grundlagen in Java:
        // Warum ist das Objekt von der Klasse MysqlDataSource, wenn ich vorher Cast nach DataSource ausgeführt habe
        // Ich meine man kann ja auch hinterher nach MysqlDataSource zurückcasten, die Infornmationen gehen bei cast ja nicht verloren
        // Trotzdem dürften die weiter unten mittels java.lang.reflect.Method aufgerufenen Methoden nicht zur Verfügung stehen.
        System.out.println(ds.getClass());
                 
        try{                     
                Method meth = ds.getClass().getDeclaredMethod("setUser", new Class[]{ String.class });
                                
                meth.invoke(ds, new Object[] { "root" });

                meth = ds.getClass().getDeclaredMethod("setPort", new Class[]{ int.class });

                meth.invoke(ds, new Object[] { 3306 });

                meth = ds.getClass().getDeclaredMethod("setPassword", new Class[]{ String.class });

                meth.invoke(ds, new Object[] { "5gaZ49koq2" });

                meth = ds.getClass().getDeclaredMethod("setServerName", new Class[]{ String.class });

                meth.invoke(ds, new Object[] { "localhost" });

                // Ganz normal eine Verbindung holen
                java.sql.Connection con = ds.getConnection();

                // Prüfen ob die Verbindung gültig ist
                System.out.println( con.isValid(10) );

        }catch( NoSuchMethodException ex ){
                System.out.println("Methode nicht gefunden " + ex.getMessage());
        }catch( InvocationTargetException ex ){
                System.out.println(ex.getMessage());
        }

        java.sql.Connection con = ds.getConnection();

        System.out.println( con.isValid(10) );
                
     }
```

Nur damit der ganze Schmu, den ich hier fabriziere besser nachvollziehbar ist:

Ich bastle mir gerade einen Java-DB-Client (nicht nur, kommt auch noch Logik dazu, die Berechnungen vornimmt) der stand-alone, also ohne Appl auskommen soll. Hatte ehrlich gesagt gar nicht dran gedacht als ich begonnen habe. Soll halt alleine ohne großen Aufwand für den Nutzer lauffähig sein. 

Aber jetzt komm ich immer mehr zu dem Schluss ein Applet zu schreiben. Nur dann brauch ich ja auch wieder nen Server!?


----------



## Schlafor (5. August 2008)

Uuuudelelliieeeee!

Jetzt hätte ich noch ein Frage:

Wo ist festgelegt wie die Attribute der DataSource-Implementierung benannt werden?

Wenn ich mir mittels PropertyDescriptor die Attributnamen aus der zur DataSource zugehörigen BeanInfo-Implementierung hole, erhalte ich beispielsweise für den Namen des Servers die Attributbezeichnung:

"serverName" (als Wert beispielsweise "localhost")

Kann man sich darauf verlassen, dass die Attributbezeichnungen bei allen DB-Herstellern gleich sind oder ist der Servername beim einen so und beim anderen anders. Bsp.:

"server" = "localhost"

Oder ist die DataSource generell nur über einen Context zu laden, bei dem ja dann eine fertig konfigurierte DataSource registriert und "zurückgeliefert" wird. (Ich weiß, sie wird nicht wirklich zurückgeliefert )

Wäre über Hilfe sehr dankbar .

Gruß

Schlafor


----------



## Schlafor (5. August 2008)

Uuudelelliiiieeee!

Antwort ist ja, es gibt einen Standard.

Hier ein kleiner Vorgeschmack, aber das ist ja nur für Oracel. 

http://stanford.edu/dept/itss/docs/oracle/10g/java.101/b10979/urls.htm

Table 3-1 Standard Datasource Properties enthält die Standard-Properties. Und Oracel ignoriert laut dieser Quelle schonmal unverblühmt ein Stadard-Property .Sehr gut !



> "Be aware, however, that Oracle does not implement the standard roleName property."



Und die vollständige Spezifikation zur JDBC2-API findet sich unter:

http://java.sun.com/products/jdbc/download.html

Seite 12 unten werden dann auch die Standardattribute der DataSource beschrieben.

Gruß

Schlafor


----------



## airliner (25. September 2008)

Schlafor hat gesagt.:


> Uuudelelliiiieeee!



Ähm, schlafor?

Was ist das eigentlich für eine "Begrüßung"?

Das hab ich bis jetzt weder gehört, noch gelesen...


----------



## Schlafor (25. September 2008)

Tja Unwissender .

Das ist aus Walt Disneys Robin Hood. Auch wenns kindisch ist, man sollte beim Programmieren immer was zum Lachen haben.

Ich stehe dazu, der Film ist einfach nur geil. Ist zwar ein Kinderfilm aber ich habe ihn mir nochmal mit meinen Kollegen angesehen und mich weggeschrien.

Hatte ihn jedoch bereits als Kind geshen == Nostaligie.

Uuuuuhhudellleiiieeeeeeeee


----------



## airliner (25. September 2008)

Kenn mich mit Robin Hood nich so aus, war halt nich mein Genre ;-)


----------

