DriverManager 在 gradle 自定义插件的任务中看不到依赖关系

DriverManager doesn't see dependency in gradle custom plugin's task

我正在编写一些 gradle 插件,现在我遇到了一个问题,即 DriverManager 没有看到 JDBC 在构建脚本依赖项中定义的驱动程序:

我有下一个 build.gradle 文件:

buildscript {
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("com.h2database:h2:1.4.196")
   }
}
plugins {
   id "someplugin"
}
apply plugin: 'groovy'

当我调用任务中定义的扩展 DefaultTask 的命令时

DriverManager.getConnection("jdbc:h2:mem:", "sa", "")

我收到异常

No suitable driver found for jdbc:h2:mem:

当我对这些 类 进行单元测试时没有问题 - 只有当我调用插件的任务时才会发生 DriverManager.getConnection.

我在这里错过了什么?谢谢!

好吧,关于为什么 DriverManager 不像您使用它那样工作以及如何使它工作有一个答案,并且有一个关于如何使用 SQL 的答案来自 Groovy(这是 Gradle 脚本的基础)。我将从 Groovy:

中使用 SQL 的正确方法开始

正确使用SQL from Gradle / Groovy:

将驱动添加到buildscript class路径中是不够的,不幸的是要使用Groovy Sql class,您需要将驱动添加到正确的class改为loader,否则将无法正常工作。

除了将驱动程序添加到 JVM 的 ext 目录之外,您还可以像这样动态地执行此操作:

configurations { jdbc }
jdbc 'com.h2database:h2:1.4.196'
def sqlClassLoader = Sql.classLoader
configurations.jdbc.each { sqlClassLoader.addURL it.toURI().toURL() }
Sql.withInstance('jdbc:h2:mem:', 'sa', '', 'org.h2.Driver') {
    it.execute 'your sql here'
}

正确使用DriverManager:

由于 Groovy 的动态性,您不能像以前那样正确使用 DriverManager。在您使用的方法中,DriverManager 尝试从调用堆栈中动态找到调用者 class,然后使用 class 的 class 加载程序来查找数据库驱动程序。对于 Groovy,这是找到的一些动态代理 class,因此在其 class 加载程序中找不到数据库驱动程序。

如果您改为像 DriverManager.getConnection("jdbc:h2:mem:", [user: "sa", password: ""] as Properties, getClass()) 一样明确地给 DriverManager 正确的调用者 class,它会正常工作。或者它也可以不给任何调用者 class 像 DriverManager.getConnection("jdbc:h2:mem:", [user: "sa", password: ""] as Properties, null) 在这种情况下使用当前线程上下文 class 加载器,这也很好。

Gradle 中的自动驱动程序查找问题:

加载class DriverManager 时,它会扫描系统属性 jdbc.drivers 和提供java.sql.Driver 服务的所有服务。它遍历那些找到的 classes 并实例化它们。驱动程序本身通常会对在 DriverManager 上注册自己做出响应,他们可以在此时及时进行注册,以便稍后像我上面建议的那样进行自动查找。

现在的问题是,如果您正在使用 Gradle 守护进程(这是现在的默认设置),并且 运行 该守护进程中加载​​ DriverManager(例如,在您之前的尝试中),那么 class 已经加载。如果您稍后将 buildscript 依赖项添加到 H2(或 运行 一个它存在的构建在它不存在但 DriverManager 已加载的构建之后),那么 class 已经加载并且不查找现在位于 class 路径中的驱动程序。

这里有一些没有明确命名驱动程序的可能解决方法 class,从可能最差到可能最好的解决方案:

  • 为你的构建禁用守护进程,并确保没有人使用你的构建启用守护进程。这很难控制和执行,并且会降低构建性能。
  • 在调用 getConnection 之前使用私有方法 DriverManager.loadInitialDrivers() 以确保再次完成查找并自动添加 H2 驱动程序。更好,但使用私有方法。
  • 自己使用 ServiceLoader 加载 class 路径中的所有 Driver class 使它们像 ServiceLoader.load(Driver.class).collect()getConnection 调用之前。可能是最优雅的解决方案。

这里有一些可能的解决方法,明确命名驱动程序 class:

  • 只需在使用getConnection()之前加载class,使其自动注册到org.h2.Driver.toString()
  • 在使用getConnection()之前简单地加载class,使其自动注册到Class.forName 'org.h2.Driver'