TODESKING
技術ブログ

JDBCドライバを動的にロードする

古典的な話題だけど意外と日本語リソースなかった。

クラスパス外にあるJDBCドライバを使いたいというケース。

クラスパスにある場合は、以下のようなコードでドライバを使用できる。

1
2
3
4
5
6
// クラス名を指定して明示的にドライバクラスを初期化する。
// JDBC4対応のドライバは、ServiceLoaderの機構を使用して自動でロードされるため不要。
Class.forName("com.example.MySuperDBDriver")

// 登録されたドライバの中から自動で適合するものが使用される
DriverManager.getConnection("jdbc:mysuperdb:....")

クラスパス外のドライバを使いたい場合、追加で

  • 外部のドライバクラスをロードするためのClassLoaderを作成
  • DriverManagerはシステムクラスローダを使ったDriverしか使ってくれないので、ラップする

という処理を行う必要がある。

Scalaで書くとこうなった

主要部分はこんなかんじ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 外部jarを読むためのクラスローダ作成
val systemClassLoader = getClass.getClassLoader
val driverClassLoader = java.net.URLClassLoader.newInstance(config.driverJars.map(_.toURI.toURL).toArray, systemClassLoader)

// 必要に応じてドライバをラップしてDriverManagerに登録する処理
def register(driver:Driver) = DriverManager.registerDriver(DriverProxy.wrapIfNeeded(driver, systemClassLoader))


// JDBC4非対応のドライバは、クラス名を元に手動でロードする
val unmanagedDrivers = config.uninitializedDriverClasses.map { klass =>
  Class.forName(klass, true, driverClassLoader).newInstance.asInstanceOf[Driver]
}

// DriverManagerに登録済みのドライバをクリア
// 必須ではないが、ドライバ一覧を表示したいときに重複が発生するのを避けるため。
deregisterAllDrivers()

unmanagedDrivers.foreach { driver => register(driver) }

// JDBC4に対応したドライバの場合、ServiceLoaderを使用してDriverのインスタンスを取得できる
val serviceLoader = java.util.ServiceLoader.load(classOf[java.sql.Driver], driverClassLoader)
serviceLoader.iterator.asScala.foreach { driver => register(driver) }

ドライバがシステムクラスローダ以外を使っている場合は、DriverManagerを騙すためにラップするためのクラス。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DriverProxy(val original:java.sql.Driver) extends java.sql.Driver {
  def acceptsURL(x$1: String): Boolean = original.acceptsURL(x$1)
  def connect(x$1: String,x$2: java.util.Properties): java.sql.Connection = original.connect(x$1, x$2)
  def getMajorVersion(): Int = original.getMinorVersion()
  def getMinorVersion(): Int = original.getMinorVersion()
  def getParentLogger(): java.util.logging.Logger = original.getParentLogger()
  def getPropertyInfo(x$1: String,x$2: java.util.Properties): Array[java.sql.DriverPropertyInfo] = original.getPropertyInfo(x$1, x$2)
  def jdbcCompliant(): Boolean = original.jdbcCompliant()
}

object DriverProxy {
  import java.sql.Driver

  def wrapIfNeeded(driver:Driver, classloader:ClassLoader):Driver = driver match {
    case d if d.getClass != (try { Class.forName(d.getClass.getName, true, classloader) } catch { case e:ClassNotFoundException => null }) =>
      new DriverProxy(d)
    case d => d
  }
  def unwrap(driver:Driver):Driver = driver match {
    case d:DriverProxy => d.original
    case d => d
  }
}

参照

Comments