Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a JDBC driver from an arbitrary location

Tags:

java

jdbc

driver

I need to test a JDBC connection to a database. The java code to do that should be as simple as:

DriverManager.getConnection("jdbc connection URL", "username", "password");

The driver manager will lookup the appropriate the driver for the given connection URL. However I need to be able to load the JDBC driver (jar) at runtime. I.e I don't have the JDBC driver on the classpath of the java application that runs the snippet of code above.

So I can load the driver using this code, for example:

URLClassLoader classLoader = new URLClassLoader(new URL[]{"jar URL"}, this.getClass().getClassLoader());
Driver driver = (Driver) Class.forName("jdbc driver class name", true, classLoader).newInstance();

But then the driver manager still won't pick it up as I can't tell it which classloader to use. I tried setting the current thread's context classloader and it still doesn't work.

Anyone has any idea on the best way to achieve that?

like image 687
SaM Avatar asked Nov 14 '08 00:11

SaM


2 Answers

From the article Pick your JDBC driver at runtime; I am just going to post the code here for reference.

The idea is to trick the driver manager into thinking that the driver was loaded from the system classloader. To do this we use this class:

public class DelegatingDriver implements Driver
{
    private final Driver driver;

    public DelegatingDriver(Driver driver)
    {
        if (driver == null)
        {
            throw new IllegalArgumentException("Driver must not be null.");
        }
        this.driver = driver;
    }

    public Connection connect(String url, Properties info) throws SQLException
    {
       return driver.connect(url, info);
    }

    public boolean acceptsURL(String url) throws SQLException
    {
       return driver.acceptsURL(url);
    }

    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException
    {
        return driver.getPropertyInfo(url, info);
    }

    public int getMajorVersion()
    {
        return driver.getMajorVersion();
    }

    public int getMinorVersion()
    {
        return driver.getMinorVersion();
    }

    public boolean jdbcCompliant()
    { 
        return driver.jdbcCompliant();
    }
}

This way the driver you register is of type DelegatingDriver which is loaded with the system classloader. You now just have to load the driver you really want to use using whatever classloader you want. For example:

URLClassLoader classLoader = new URLClassLoader(new URL[]{"path to my jdbc driver jar"}, this.getClass().getClassLoader());
Driver driver = (Driver) Class.forName("org.postgresql.Driver", true, classLoader).newInstance();
DriverManager.registerDriver(new DelegatingDriver(driver)); // register using the Delegating Driver

DriverManager.getDriver("jdbc:postgresql://host/db"); // checks that the driver is found
like image 120
SaM Avatar answered Oct 14 '22 04:10

SaM


The problem is DriverManager performs "tasks using the immediate caller's class loader instance". See Guideline 6-3 of Secure Coding Guidelines for the Java Programming Language, version 2.0. The system class loader is in no way special in this case.

Just for kicks, I wrote a blog entry (edit: now defunct) on this subject a while back. My solution, though more complicated then Nick Sayer's solution, is more complete and even works from untrusted code (edit: the concept of untrusted code is deprecated for removal in Java 17). Also note URLClassLoader.newInstance is preferred over new URLClassLoader.

like image 43
Tom Hawtin - tackline Avatar answered Oct 14 '22 05:10

Tom Hawtin - tackline