BeanShell y JDBC
Pensaba que iba a ser muy sencillo conectarme a una base de datos mediante JDBC con BeanShell, simplemente tenía que cargar el driver con addClassPath() ... hasta que me encontré con java.sql.DriverManager.
Después de perder un buen rato intentando conectarme a una base de datos a través de JDBC en la consola de BeanShell pude comprobar que no era tan fácil como había pensado. El código que introducía era el siguiente (intentaba probar parte de un tutorial sobre javadb de java.sun.com):
addClassPath("./derby.jar");
browseClass("org.apache.derby.jdbc.EmbeddedDriver");
driver = getClass("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
import java.sql.*;
String userHomeDir = System.getProperty("user.home", ".");
String systemDir = userHomeDir + "/.addressbook";
System.setProperty("derby.system.home", systemDir);
DriverManager.getConnection("jdbc:derby:DefaultAddressBook;create=true");
// Error: // Uncaught Exception: Method Invocation DriverManager.getConnection : at Line: 1 : in file: : DriverManager .getConnection ( "jdbc:derby:DefaultAddressBook;create=true" )
Target exception: java.sql.SQLException: No suitable driver
Maldita excepción diciendo que no existía dicho driver. A primera vista sorprendente, porque había cargado el driver (el de la base de datos de apache Derby). Pero después de revisar la documentación del BeanShell y el código fuente de la clase java.sql.DriverManager encontré la respuesta.
La clave está en que BeanShell carga las librerías de sistema con el System ClassLoader, y el resto de las clases que se añaden con addClassPath() se cargan con otro ClassLoader propio. Eso explica por qué uso getClass()para registrar el Driver JDBC y no el típico Class.forName(). Voy a explicar esto último un poco mejor, recordemos que el Class.forName(), que usa el ClassLoader que cargó la clase Class (valga la redundancia) para instanciar los objetos que le indiquemos. Por lo tanto este método no va a ser capaz de instanciar objetos del Driver JDBC, ya que estos han sido cargados con el ClassLoader del BeanShell (recordemos que han sido añadidos con addClassPath). Por lo tanto la única solución es usar getClass() que usa el ClassLoader del BeanShell y por lo tanto tiene acceso a las clases JDBC que acabamos de cargar. Es un verdadero lío, lo sé, pero todo lo relacionado con los ClassLoaders en Java es siempre así de complicado.
Lo que no me explicaba es por qué una vez que había conseguido instanciar el driver JDBC seguía sin funcionar el condenado getConnection(). Recordemos que al instanciar un driver, la clase del driver automáticamente se encarga de registrarse en el DriverManager para así poder realizar las conexiones que sean del tipo de ese driver, en este caso de tipo jdbc:derby. Para lograr entenderlo no me quedó mas remedio que ver el código fuente del DriverManager, y como siempre ahí estaba la respuesta. Resulta que el método getConnection() para obtener el driver JDBC mantiene un array de Strings con los nombres de las clases que implementan los distintos tipos de drivers JDBC, y posteriormente intenta instanciarlos usando ... Class.forName(). Volvemos a tener el mismo problema con los ClassLoaders.
Así que no hay solución, no podemos usar el DriverManager para obtener las conexiones, ya que no hay forma de indicarle que use otro ClassLoader para cargar algunos drivers. No queda más remedio que abrir las conexiones a mano.
conn = driver.connect("jdbc:derby:DefaultAddressBook;create=true",System.getProperties());
stmt = conn.createStatement();
No sé si a alguien se le ocurre otra solución que no pase por arrancar el BeanShell con el jar de Derby incluido en el classpath.
Pensaba que iba a ser muy sencillo conectarme a una base de datos mediante JDBC con BeanShell, simplemente tenía que cargar el driver con addClassPath() ... hasta que me encontré con java.sql.DriverManager.
Después de perder un buen rato intentando conectarme a una base de datos a través de JDBC en la consola de BeanShell pude comprobar que no era tan fácil como había pensado. El código que introducía era el siguiente (intentaba probar parte de un tutorial sobre javadb de java.sun.com):
addClassPath("./derby.jar");
browseClass("org.apache.derby.jdbc.EmbeddedDriver");
driver = getClass("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
import java.sql.*;
String userHomeDir = System.getProperty("user.home", ".");
String systemDir = userHomeDir + "/.addressbook";
System.setProperty("derby.system.home", systemDir);
DriverManager.getConnection("jdbc:derby:DefaultAddressBook;create=true");
// Error: // Uncaught Exception: Method Invocation DriverManager.getConnection : at Line: 1 : in file:
Target exception: java.sql.SQLException: No suitable driver
Maldita excepción diciendo que no existía dicho driver. A primera vista sorprendente, porque había cargado el driver (el de la base de datos de apache Derby). Pero después de revisar la documentación del BeanShell y el código fuente de la clase java.sql.DriverManager encontré la respuesta.
La clave está en que BeanShell carga las librerías de sistema con el System ClassLoader, y el resto de las clases que se añaden con addClassPath() se cargan con otro ClassLoader propio. Eso explica por qué uso getClass()para registrar el Driver JDBC y no el típico Class.forName(). Voy a explicar esto último un poco mejor, recordemos que el Class.forName(), que usa el ClassLoader que cargó la clase Class (valga la redundancia) para instanciar los objetos que le indiquemos. Por lo tanto este método no va a ser capaz de instanciar objetos del Driver JDBC, ya que estos han sido cargados con el ClassLoader del BeanShell (recordemos que han sido añadidos con addClassPath). Por lo tanto la única solución es usar getClass() que usa el ClassLoader del BeanShell y por lo tanto tiene acceso a las clases JDBC que acabamos de cargar. Es un verdadero lío, lo sé, pero todo lo relacionado con los ClassLoaders en Java es siempre así de complicado.
Lo que no me explicaba es por qué una vez que había conseguido instanciar el driver JDBC seguía sin funcionar el condenado getConnection(). Recordemos que al instanciar un driver, la clase del driver automáticamente se encarga de registrarse en el DriverManager para así poder realizar las conexiones que sean del tipo de ese driver, en este caso de tipo jdbc:derby. Para lograr entenderlo no me quedó mas remedio que ver el código fuente del DriverManager, y como siempre ahí estaba la respuesta. Resulta que el método getConnection() para obtener el driver JDBC mantiene un array de Strings con los nombres de las clases que implementan los distintos tipos de drivers JDBC, y posteriormente intenta instanciarlos usando ... Class.forName(). Volvemos a tener el mismo problema con los ClassLoaders.
Así que no hay solución, no podemos usar el DriverManager para obtener las conexiones, ya que no hay forma de indicarle que use otro ClassLoader para cargar algunos drivers. No queda más remedio que abrir las conexiones a mano.
conn = driver.connect("jdbc:derby:DefaultAddressBook;create=true",System.getProperties());
stmt = conn.createStatement();
No sé si a alguien se le ocurre otra solución que no pase por arrancar el BeanShell con el jar de Derby incluido en el classpath.
2 Comments:
Hola:
Ya se que es tarde, pero una solución es crear un URLClassLoader y usar reflexion para ejecutar el método que estás proponiendo, dejando de lado el DriverManager.
Saludos,
Diego
conn = driver.connect("jdbc:derby:DefaultAddressBook;create=true",System.getProperties());
stmt = conn.createStatement();
alguien ayudeme no se q hacer, intente con la solucion a mano y aun no puedo.
Publicar un comentario
<< Home