4
votes

I'm trying to build a project in clojure that takes data from a Microsoft SQL Server database, and pushes it to a MySQL database.

The project runs fine using lein run, but when I package it into an uberjar using leiningen, and run it using java -jar, it fails with:

Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:sqlserver....

This error only happens when I attempt to use both the MySQL database and the SQL Server database. If I use any one of them on its own, the jar file runs fine using java -jar.

My project.clj is as follows:

(defproject sqlserver-clojure "1.0.0-SNAPSHOT"
  :description "A minimal example of the MySQL/SQLServer conflict"
  :dependencies [[org.clojure/clojure "1.4.0"]
             [com.microsoft/sqljdbc4 "3.0"]
             [clojureql "1.0.4"]
             [mysql/mysql-connector-java "5.1.6"]]
  :main sqlserverclojure.core)
2
Uber-jars are evil; you probably don't copy certain resources or entries in META-INF, or worse overwriting them (eg META-INF/services/java.sql.Driver exists in all JDBC 4 compliant drivers with different content)Mark Rotteveel
You could always fall back to the "classic" method of loading JDBC drivers, using Class.forName("driver.class.Name") somewhere in your application before using JDBC. Alternately you could use DataSource objects instead of DriverManager.getConnection()millimoose
@millimoose Mentioning of META-INF/services/java.sql.Driver was just an example, the loading of the driver could still fail (even when using the classic method or a DataSource), for example because other required resources weren't copiedMark Rotteveel
@MarkRotteveel Interesting...there is indeed a META-INF/services/java.sql.Driver in the jar package and it only contains an entry for the MySQL driver. Is there an alternative to using an uberjar here? What would you recommend?Daniel Neal
@MarkRotteveel It might fail, and it might not, I don't really believe it's all that common in practice to put anything under META-INF/ that could have filename conflicts besides the relatively few features of the JDK that make use of that information. And beyond that "something else might fail" comes across as FUD-spreading.millimoose

2 Answers

4
votes

The problem is most likely that when making the uber-jar, you are not copying some files (eg .properties or XML config) that are required by the driver, or you are overwriting files that exist in both JDBC drivers.

A good example of overwriting is the META-INF/services/java.sql.Driver file which all JDBC 4 drivers have. This file contains a list of all classes in the jar implementing java.sql.Driver, so that the java.sqlDriverManager class can automatically load all Driver implementations using ServiceLoader.

If you create an uber-jar with multiple JDBC drivers, you either need to make sure this file contains the union of all these files, or your application needs to explicitly load the drivers you need with Class.forName("<name of the java.sql.Driver implementation>") for each required driver and not depend on the JDBC 4 driver autoloading.

Also verify that the process you use to create the uber-jar copies all resources and not just .class files!

Better yet (in my opinion), don't use an uber-jar, but keep the dependent .jar files external and reference them in the Class-Path entry of the META-INF/MANIFEST.MF file of your application, this saves you a lot of hassle to get your uber-jar working correctly (or verify that it is actually working correctly).

1
votes

Adding this for my own reference since I've not touched Java before and struggle with Java interop.

Add

(. Class (forName "com.microsoft.sqlserver.jdbc.SQLServerDriver"))

In the namespace with your jdbc clojure function calls.