Loading JDBC Driver at Runtime - java

I'm using the following code to load a driver class:
public class DriverLoader extends URLClassLoader {
private DriverLoader(URL[] urls) {
super(urls);
File driverFolder = new File("driver");
File[] files = driverFolder.listFiles();
for (File file : files) {
try {
addURL(file.toURI().toURL());
} catch (MalformedURLException e) {
}
}
}
private static DriverLoader driverLoader;
public static void load(String driverClassName) throws ClassNotFoundException {
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException ex) {
if (driverLoader == null) {
URL urls[] = {};
driverLoader = new DriverLoader(urls);
}
driverLoader.loadClass(driverClassName);
}
}
}
Although the class loads fine I can't establish a Database connection (No suitable driver found for ...) no matter which driver I try.
I assume this is because I'm not loading the driver class using Class.forName (which wouldn't work since I'm using my own ClassLoader). How can I fix this?

You need to create an instance of the driver class before you can connect:
Class drvClass = driverLoader.loadClass(driverClassName);
Driver driver = drvClass.newInstance();
Once you have the instance you can either use that instance to connect:
Properties props = new Properties();
props.put("user", "your_db_username");
props.put("password", "your_db_password");
Connection con = driver.connect("jdbc:postgresql:...", props);
As an alternative, if you want to keep using DriverManager you must register the driver with the DriverManager manually:
DriverManager.registerDriver(driver);
Then you should be able to use the DriverManager to establis a connection.
If I recall it correctly there was a problem with the DriverManager refusing to connect if the driver itself was not loaded by the same classloader as the DriverManager. If that (still) is the case, you need to use Driver.connect() directly.

You should establish connection in a class loaded by your DriverLoader. So, load the connection establishment code using DriverLoader and then call JDBC from it.

You need to add a Classpath reference in the manifest. Follow these simple steps:
add a folder "lib" to your application
place "mysql-connector-java-5.1.18-bin" in lib
now open your "MANIFEST.MF" and go to tab "RUNTIME"
on bottom right, you would see "classpath" ; click "Add"
now add the folder lib [created in step 1] along with the jar file
in this way, whenever a runtime EclipseApplication /OSGi Application is started this jar file is exported along too. So the connectivity would then be available there too.

Related

java.sql.SQLException: No suitable driver found for jdbc://localhost:3306/twitch

I was trying to connect to MySQL "twitch" database using java with this code below:
import java.sql.*;
public class App {
public static void main(String[] args) throws Exception {
try {
Class.forName("com.mysql.jdbc.Driver");
//Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc://localhost:3306/twitch";
String username = "root";
String pass = "nfreal-yt10";
Connection con=DriverManager.getConnection(url,username,pass);
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select distinct creator_id from twitch.information where creator_id > 40;");
while (rs.next()) {
System.out.println(rs.getString(1));
}
con.close();
}
catch(Exception e) {
System.out.println(e);
}
}
}
when I executed the code my console throws (Full error):
Loading class `com.mysql.jdbc.Driver'.
This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver
class is generally unnecessary.
java.sql.SQLException: No suitable driver found for jdbc://localhost:3306/twitch
I have added MySQL connector on my directory folder and all stuff which required to be added, yet the error still occurred, why?
When you communicate with your database (located at /localhost:3306/twitch), you must precise the protocol used (eg. your browser use http or https protocol followed with the adress).
JDBC is a driver that can interface with MySQL, but can't directly access to your database. Hence your URL should be:
String url = "jdbc:mysql://localhost:3306/twitch";
EDIT : Class.forName("com.mysql.cj.jdbc.Driver"); is no more needed in general. Details here.

Load SPI class with URLClassLoader rise ClassNotFoundException

I did some research, But due to complexity of this situation, Not working for me.
Child first class loader and Service Provider Interface (SPI)
Like flink or tomcat, My application run as framework with platform and system classloader.
Framework load plugin as module and plugin may depend some lib, so make this define:
plugin/plugin-demo.jar
depend/plugin-demo/depend-1.jar
depend/plugin-demo/depend-2.jar
framework will create two classloader like this:
URLClassLoader dependClassloader = new URLClassLoader({URI-TO-depend-jars}, currentThreadClassLoader);
URLClassLoader pluginClassloader = new URLClassLoader({URI-TO-plugin-jar},dependClassloader);
With an HelloWorld demo this is working file ( and at first I NOT set systemClassloader as parent).
But with JDBC driver com.mysql.cj.jdbc.Driver which using SPI goes into trouble:
Even I manual register driver:
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver", true, pluginClassloader);
com.mysql.cj.jdbc.Driver driver = (com.mysql.cj.jdbc.Driver) clazz.getConstructor().newInstance();
DriverManager.registerDriver(driver);
This working fine, But after that:
DriverManager.getConnection(this.hostName, this.userName, this.password)
will rise
Caused by: java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:440)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 7 more
Or:
Caused by: java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/furryblack
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:706)
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:229)
I try to print all driver:
Enumeration<java.sql.Driver> driverEnumeration = DriverManager.getDrivers();
while (driverEnumeration.hasMoreElements()) {
java.sql.Driver driver = driverEnumeration.nextElement();
System.out.println(driver);
}
And there is no driver registered.
So, Question is: why NoClassDefFoundError ?
I have some guess: DriverManager run in systemclassloader but driver load in my classloader parent won't search in children, So I set currentThreadClassLoader as parent but still rise exception.
Update 1:
URI-TO-depend-jars is Array of File.toURI().toURL().
This design working fine with demo, So I think it should be correct.
And with debug, The ClassLoader parent chain is
ModuleLoader -> DependLoader
And with systemclassloader is
ModuleLoader -> DependLoader -> BuiltinAppClassLoader -> PlatformClassLoader -> JDKInternalLoader
This is the full code:
Interface in jar 1:
public interface AbstractComponent {
void handle();
}
Plugin in jar2 (depend jar3 in pom.xml):
public class Component implements AbstractComponent {
#Override
public void handle() {
System.out.println("This is component handle");
SpecialDepend.tool();
}
}
Depend in jar3:
public class SpecialDepend {
public static void tool() {
System.out.println("This is tool");
}
}
Main in jar1:
#Test
public void test() {
String path = "D:\\Server\\Classloader";
File libFile = Paths.get(path, "lib", "lib.jar").toFile();
File modFile = Paths.get(path, "mod", "mod.jar").toFile();
URLClassLoader libLoader;
try {
URL url;
url = libFile.toURI().toURL();
URL[] urls = {url};
libLoader = new URLClassLoader(urls);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
URLClassLoader modLoader;
try {
URL url;
url = modFile.toURI().toURL();
URL[] urls = {url};
modLoader = new URLClassLoader(urls, libLoader);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
try {
Class<?> clazz = Class.forName("demo.Component", true, modLoader);
if (AbstractComponent.class.isAssignableFrom(clazz)) {
AbstractComponent instance = (AbstractComponent) clazz.getConstructor().newInstance();
instance.handle();
}
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException exception) {
throw new RuntimeException(exception);
}
}
Output is
This is component handle
This is tool
This is working perfect.
Update 2:
I try to print more debug and some unnecessary code, Then I found, The Driver class can be found and instancelized, But the DriverManager.registerDriver didn't register it.
So the question become: Why DriverManager can't register driver load from sub classloader?
Update3
contextClassLoader is get from Thread.currentThread().getContextClassLoader() But inject by framework with currentThread.setContextClassLoader(exclusiveClassLoader);
As double check I print the hashcode, Its same.
And I debug into DriverManager, Its was registered the driver into internal List but after that, getDrivers will got nothing.
ClassLoader looks for classes in its parent first, and the parent delegates to its parent and so on. With that said, ClassLoaders that are siblings cannot see eachothers classes.
Also the method DriverManager#getDrivers() internally validates if the caller ClassLoader can load the class with DriverManager#isDriverAllowed(Driver, ClassLoader).
this means that even if your Driver is added to the registration list, it is only added as an instance of DriverInfo, this means that it would only be loaded on demand (Lazy), and still might not register when loading is attempted, that's why you get an empty list.

Why does runtime loaded MySQL driver output "No suitable driver found"?

I tried to load MySQL driver when my application is running. So I made jar file loading method as "loadJobJars" to that be creating URLClassLoader object with given jar file list. And I was call Class.forName method and then DriverManager.getConnection method. But it was output "No suitable driver found" on terminal.
Is there anyone know why this be happened?
My code is below.
public DBConnectionManager(Path jarPath) throws MalformedURLException {
this.urlClassLoader = loadJobJars(listJarFiles(jarPath));
}
public Connection createConnection(String dbName, String host, int port, String db, String user,
String password) throws NoSuchFieldException, SecurityException, IllegalArgumentException,
IllegalAccessException, ClassNotFoundException, SQLException, InstantiationException {
dbName = dbName.toLowerCase();
String url = "jdbc:" + dbName + "://" + host + ":" + port + "/" + db;
String driverName = DBDriver.getDriverName(dbName.toUpperCase());
Logger.getInstance().info("connection url: "+url+" driver: "+driverName);
Class.forName(driverName, true, this.urlClassLoader);
Connection conn = DriverManager.getConnection(url, user, password); //Here is it was happened code line.
return conn;
}
public URLClassLoader loadJobJars(File[] jarFiles) throws MalformedURLException {
URL[] urls = Arrays.asList(jarFiles).stream().map(f -> {
try {
System.out.println(f.getAbsolutePath());
return f.toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}).filter(u -> u != null).toArray(URL[]::new);
return URLClassLoader.newInstance(urls, ClassLoader.getPlatformClassLoader());
}
public File[] listJarFiles(Path jarPath) {
return jarPath.toFile().listFiles(new FilenameFilter() {
#Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
});
}
Console output is below.
[INFO][DBConnectionManager:77][20210119152004] connection url: jdbc:mysql://localhost:3306/mysql driver: com.mysql.jdbc.Driver
Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:mysql://192.168.1.157:3306/mysql
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:702)
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:251)
at com.innotree.innoquartz.iqjobgen.DBConnectionManager.createConnection(DBConnectionManager.java:35)
at com.innotree.innoquartz.iqjobgen.DBConnectionManager.main(DBConnectionManager.java:77)
The way DriverManager creates connections is sensitive to the class loader of the calling code. The Driver used to create the connection must be visible to the class loader of the calling class (or when the calling code has no class loader, the context class loader). The driver you loaded is not visible to the class loader of the calling code, so DriverManager will not use the driver to create a connection.
You must create the connection using code that uses the same class loader (or at least where the class loader with the driver is visible). Alternatively, you need to use the java.sql.Driver directly, skipping DriverManager altogether.
Thank for your kind answer.
I saw your answer and finally figure out the problem by direct loading MySQL driver. Your help was very useful to solve that. Solution code is down below.
String url = "jdbc:mysql://localhost:3306/mysql";
URLClassLoader loader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
Class cls = loader.loadClass("com.mysql.jdbc.Driver");
Driver driver = (Driver)cls.newInstance();
Properties info = new Properties();
info.put("user", "admin");
info.put("password", "1234");
Connection conn = driver.connect(url, info);

How to load a JDBC driver dynamically during runtime since Java 9?

I'm currently migrating my Java 8 code to Java 11 and stumbled across a problem. I'm looking for jar files in a directory and add them to the classpath in order to use them as JDBC drivers.
After doing so I can easily use DriverManager.getConnection(jdbcString); to get a connection to any database I loaded a driver beforehand.
I used to load drivers using this bit of code which no longer works since the SystemClassLoader is no longer a URLClassLoader.
Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true);
method.invoke(ClassLoader.getSystemClassLoader(), new Object[] { jdbcDriver.toURI().toURL() });
So after looking around for alternatives I found this answer on SO:
https://stackoverflow.com/a/14479658/10511969
Unfortunately for this approach I'd need the drivers class name, i.e. "org.postgresql.Driver" which I don't know.
Is there just no way to do this anymore, or am I missing something?
Using a Shim is a good way to load the JDBC driver when the driver is, for some reason, not accessibile via the system class loader context. I have ran into this a few times with multi-threaded scripts that have their own separated classpath context.
http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
Not knowing the driver's class seems like an odd constraint.
I would go for a custom class loader that after ever class initialisation (I think you can do that), calls DriverManager.getDrivers and registers any new drivers it finds. (I have no time at the moment to write the code.)
The hacky alternative would be to load all your code (except a bootstrap) in a URLClassLoader and addURL to that.
Edit: So I wrote some code.
It creates a class loader for the drivers that also contains a "scout" class that forwards DriverManager.drivers (which is a naughty caller sensitive method (a newish one!)). A fake driver within the application class loader forwards connect attempts onto any dynamically loaded drivers at the time of request.
I don't have any JDBC 4.0 or later drivers conveniently around to test this on. You'll probably want to change the URL - you'll need the Scout class and the driver jar.
import java.lang.reflect.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.util.logging.*;
import java.util.stream.*;
class FakeJDBCDriver {
public static void main(String[] args) throws Exception {
URLClassLoader loader = URLClassLoader.newInstance(
new URL[] { new java.io.File("dynamic").toURI().toURL() },
FakeJDBCDriver.class.getClassLoader()
);
Class<?> scout = loader.loadClass("Scout");
Method driversMethod = scout.getMethod("drivers");
DriverManager.registerDriver(new Driver() {
public int getMajorVersion() {
return 0;
}
public int getMinorVersion() {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
return new DriverPropertyInfo[] { };
}
public boolean jdbcCompliant() {
return false;
}
public boolean acceptsURL(String url) throws SQLException {
if (url == null) {
throw new SQLException();
}
for (Iterator<Driver> iter=drivers(); iter.hasNext(); ) {
Driver driver = iter.next();
if (
driver.getClass().getClassLoader() == loader &&
driver.acceptsURL(url)
) {
return true;
}
}
return false;
}
public Connection connect(String url, Properties info) throws SQLException {
if (url == null) {
throw new SQLException();
}
for (Iterator<Driver> iter=drivers(); iter.hasNext(); ) {
Driver driver = iter.next();
if (
driver.getClass().getClassLoader() == loader &&
driver.acceptsURL(url)
) {
Connection connection = driver.connect(url, info);
if (connection != null) {
return connection;
}
}
}
return null;
}
private Iterator<Driver> drivers() {
try {
return ((Stream<Driver>)driversMethod.invoke(null)).iterator();
} catch (IllegalAccessException exc) {
throw new Error(exc);
} catch (InvocationTargetException exc) {
Throwable cause = exc.getTargetException();
if (cause instanceof Error) {
throw (Error)cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
} else {
throw new Error(exc);
}
}
}
});
// This the driver I'm trying to access, but isn't even in a jar.
Class.forName("MyDriver", true, loader);
// Just some nonsense to smoke test.
System.err.println(DriverManager.drivers().collect(Collectors.toList()));
System.err.println(DriverManager.getConnection("jdbc:mydriver"));
}
}
Within a directory dynamic (relative to current working directory):
import java.sql.*;
public interface Scout {
public static java.util.stream.Stream<Driver> drivers() {
return DriverManager.drivers();
}
}
I would always suggest avoiding setting the thread context class loader to anything other than a loader that denies everything, or perhaps null.
Modules may well allow you to load drivers cleanly, but I've not looked.
if you don`t know the driver name, you cannot use reflect to use urlLoader to load jar, which you exactly want.
I have same problem with dynamically load driver, because of jars are conflict.
Even though, I have to know the driver name to jar, which i want to load use my url class loader.
DriverManager use class loader to load jar, so it could find jdbc driver by name. As usual we use: class.forName。
We use self defined class loader to load our driver, so that it can solve the conflict of jars.

JDBC JAVA No suitable driver found for jdbc:mysql://localhost:3306/voting

Hello im trying to connect to a mysql database using JDBC my code is below.I get an error as such No suitable driver found.Searching around I found that the usual error is syntax or missing the jar file from the class path.I tried both of these solutions and dont know what to do next it wont connect.Also to manage the databases I have WAMP and mySQL workbench installed not sure if its related.
package test.jdbc;
import java.sql.*;
public class jdbctester {
public static void main(String[] args)
{
try
{
Connection myconn=DriverManager.getConnection("jdbc:mysql://localhost:3306/voting","root","Vanquish123");
Statement myStmt=myconn.createStatement();
ResultSet myRs=myStmt.executeQuery("select * from electoral");
/*
while(myRs.next())
{
System.out.println(myRs.getString("state")+","+myRs.getString("perDem"));
}
*/
}
catch(Exception exc)
{
exc.printStackTrace();
}
}
}
Try this:
Class.forName("fully qualified driver class name");
Java Class.forName, JDBC connection loading driver
this post states that you should not need it but it will not hurt you to try.
you have to add "com.mysql.jdbc_5.1.5.jar" in to your project build path... go to project property>build Path> library> add external jar and add jar file.
Connection conn = null;
try {
// Register JDBC driver
Class.forName(DRIVER).newInstance();
// Open a connection
conn = DriverManager.getConnection(Local_URL + , USERNAME, PASSWORD);
System.out.println("Connected Database Successfully...\n\n");
} catch (Exception se) {
throw new AppException("Failed to create Local Database connection", se);
}
return conn;

Categories

Resources