sbt-assembly打包Scala项目缺失PostgreSQL JDBC驱动解决方案
我有一个Scala项目,通过JDBC连接PostgreSQL数据库取数,使用IntelliJ配合SBT构建,借助sbt-assembly插件打包成包含所有依赖的单JAR包。在IntelliJ内运行程序完全正常,但执行以下命令时抛出异常:
java -jar /opt/service/sample.jar
异常信息:
Exception in thread "main" java.sql.SQLException: java.sql.SQLException: No suitable driver found for jdbc:postgresql://
解压生成的JAR包后,未找到PostgreSQL驱动相关文件,推测是sbt-assembly未将该依赖打包进JAR。本地通过指定额外依赖的方式执行命令,程序能正常运行:
java -cp sampl.jar:/Users/x/.m2/repository/org/postgresql/postgresql/42.2.7/postgresql-42.2.7.jar com.abc.main
以下是项目中build.sbt的assembly相关配置,需要修改配置让单JAR包无需额外依赖即可运行:
ThisBuild / libraryDependencies ++= Seq( "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2", "org.postgresql" % "postgresql" % "42.2.14" ) import sbtassembly.MergeStrategy assemblyMergeStrategy in assembly := { case PathList("org", "apache", xs @ _*) => MergeStrategy.first case PathList("org", "slf4j", xs @ _*) => MergeStrategy.first case PathList("org", "aopalliance", xs @ _*) => MergeStrategy.first case PathList("jersey", "repackaged", xs @ _*) => MergeStrategy.first case PathList("javax", xs @ _*) => MergeStrategy.first case PathList("io", "netty", xs @ _*) => MergeStrategy.first case PathList("com", "amazonaws", xs @ _*) => MergeStrategy.first case PathList("META-INF", xs @ _*) => (xs map {_.toLowerCase}) match { case ("manifest.mf" :: Nil) | ("index.list" :: Nil) | ("dependencies" :: Nil) => MergeStrategy.discard case _ => MergeStrategy.first } case PathList("com", "sun", xs @ _*) => MergeStrategy.first case PathList("git.properties", xs @ _*) => MergeStrategy.first case PathList("mozilla", xs @ _*) => MergeStrategy.first case x => val oldStrategy = (assemblyMergeStrategy in assembly).value oldStrategy(x) }
修改后的build.sbt配置如下,主要调整两点:确保PostgreSQL驱动被纳入打包范围,以及正确处理驱动的服务注册文件避免丢失:
ThisBuild / libraryDependencies ++= Seq( "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2", // 显式声明compile范围,确保驱动被打包进最终JAR "org.postgresql" % "postgresql" % "42.2.14" % Compile ) import sbtassembly.MergeStrategy assemblyMergeStrategy in assembly := { // 新增PostgreSQL路径的合并策略,避免驱动文件被意外排除 case PathList("org", "postgresql", xs @ _*) => MergeStrategy.first case PathList("org", "apache", xs @ _*) => MergeStrategy.first case PathList("org", "slf4j", xs @ _*) => MergeStrategy.first case PathList("org", "aopalliance", xs @ _*) => MergeStrategy.first case PathList("jersey", "repackaged", xs @ _*) => MergeStrategy.first case PathList("javax", xs @ _*) => MergeStrategy.first case PathList("io", "netty", xs @ _*) => MergeStrategy.first case PathList("com", "amazonaws", xs @ _*) => MergeStrategy.first case PathList("META-INF", xs @ _*) => (xs map {_.toLowerCase}) match { case ("manifest.mf" :: Nil) | ("index.list" :: Nil) | ("dependencies" :: Nil) => MergeStrategy.discard // 合并JDBC驱动的服务注册文件,保证自动加载生效 case ("services" :: "java.sql.Driver" :: Nil) => MergeStrategy.concat case _ => MergeStrategy.first } case PathList("com", "sun", xs @ _*) => MergeStrategy.first case PathList("git.properties", xs @ _*) => MergeStrategy.first case PathList("mozilla", xs @ _*) => MergeStrategy.first case x => val oldStrategy = (assemblyMergeStrategy in assembly).value oldStrategy(x) }
额外注意:如果你的JDBC连接代码没有显式加载驱动类Class.forName("org.postgresql.Driver"),则必须确保META-INF/services/java.sql.Driver文件被正确打包(上面的配置已经通过MergeStrategy.concat处理了该文件的合并,避免被其他依赖的同名文件覆盖)。
修改完成后,重新执行sbt assembly生成新的JAR包,再运行java -jar /opt/service/sample.jar即可正常连接数据库。
内容的提问来源于stack exchange,提问作者Siddarth

