返回

Spring Boot 打包: 告别 Failed to determine driver class 错误

java

搞定 Spring Boot 打包 Jar 后的 DataSourceBeanCreationException: Failed to determine a suitable driver class

好不容易用 IntelliJ 把 Spring Boot 项目写完,想打个 Jar 包部署,结果一运行 java -jar your-app.jar,啪!甩过来一堆错误,最终定位到这个刺眼的 DataSourceProperties$DataSourceBeanCreationException: Failed to determine a suitable driver class。更奇怪的是,项目在 IntelliJ 里跑得好好的,数据库连接啥都正常,怎么一打包就不认识驱动了呢?

别急,这个问题还挺常见的,尤其是在尝试用非标准方式(比如 IntelliJ 自带的 Artifact 功能或某些通用 Maven 插件)打包 Spring Boot 应用时。咱们一步步拆解下。

遇到啥问题了?

核心错误信息是:

org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine a suitable driver class

这个错误表明,Spring Boot 在尝试配置数据源 (DataSource) 时,找不到合适的 JDBC 驱动类。它通常嵌套在一长串的异常堆栈里,比如 UnsatisfiedDependencyExceptionBeanCreationException,最终指向数据源配置这块儿。

回顾一下提问者遇到的坑:

  1. 最初用 IntelliJ 的 Artifact 功能 (JAR -> from modules with dependencies) 打包,结果报 no main manifest attribute
  2. 调整 Manifest 文件路径后,又报 No auto configuration classes found in META-INF/spring.factories
  3. 手动添加 spring.factories 文件后,终于遇到了我们正在讨论的主角 Failed to determine a suitable driver class
  4. 尝试换用 maven-assembly-plugin 打包,结果是 Error: Could not find or load main class

这一连串的问题,其实都指向一个根源:打包方式不对

为啥会这样?

Spring Boot 应用不是一个普通的 Java 程序。它有自己的一套启动和加载机制,尤其是当打成可执行 Jar 包时。

关键点:Spring Boot 的可执行 Jar 结构

Spring Boot 推荐的打包方式(通过 spring-boot-maven-plugin 或对应的 Gradle 插件)生成的 Jar 文件结构很特别:

  • 它不是一个简单的 "fat jar" (把所有依赖解压后重新打包)。
  • 它包含一个 BOOT-INF/classes 目录,存放你自己的项目代码。
  • 它包含一个 BOOT-INF/lib 目录,里面是所有依赖的 Jar 文件(原封不动地嵌套 )。
  • 它包含一个特殊的 org/springframework/boot/loader 包,负责加载 BOOT-INF 里的类和 Jar。
  • META-INF/MANIFEST.MF 文件会指定 Spring Boot 的 JarLauncher 作为 Main-Class,它再负责找到并运行你真正的 main 方法。

问题根源分析:

  1. no main manifest attributeCould not find or load main class : 这说明 Jar 包里的 META-INF/MANIFEST.MF 文件要么没生成,要么内容不对,没有正确指定启动类。特别是使用 maven-assembly-plugin 或 IntelliJ 的 Artifact 功能时,如果没有特别配置,它们可能不会生成符合 Spring Boot 要求的 Manifest。
  2. No auto configuration classes found in META-INF/spring.factories : 这个错误强烈暗示打包方式破坏了 Spring Boot 的自动配置机制。Spring Boot 通过扫描所有依赖 Jar 包中的 META-INF/spring.factories 文件来发现自动配置类。如果用错误的方式打包(比如简单粗暴地合并所有依赖),这些 spring.factories 文件可能丢失或内容冲突,导致自动配置失效。
  3. Failed to determine a suitable driver class : 这是前一个问题的延伸。当自动配置失效或不完整时,负责配置数据源 (DataSourceAutoConfiguration) 的那部分可能就没正常工作。或者,更常见的是,打包过程没能把数据库驱动程序 Jar 包正确地包含在内,或者虽然包含了,但 Spring Boot 的类加载器(由于错误的打包结构)找不到它。

简单来说,你试图用组装普通 Jar 包的方式去组装一个需要特殊结构的 Spring Boot 可执行 Jar 包 ,导致 Spring Boot 的“魔法”失灵了。

咋解决呢?

核心思想:用 Spring Boot 推荐的方式来打包!

方案一:拥抱 spring-boot-maven-plugin (推荐)

这是最正宗、最省心的方法。Spring Boot 项目默认就带了这个插件。

原理:

spring-boot-maven-pluginrepackage 目标 (goal) 会在 Maven 默认的 package 阶段之后执行。它会接管 Maven 生成的原始 Jar 包,并按照上面提到的 Spring Boot 可执行 Jar 结构重新打包。它会自动处理好 Manifest 文件、嵌套依赖、spring.factories 合并等所有细节。

步骤:

  1. 检查 pom.xml
    确保你的 pom.xml 文件里包含了 spring-boot-maven-plugin。通常在用 Spring Initializr 创建项目时就已经加好了。

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <!-- 可选:如果父 POM 不是 spring-boot-starter-parent,
                     可能需要显式添加 <version> -->
                <!-- 可选:确保 repackage 目标被执行 (通常是默认的)
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                 -->
            </plugin>
            <!-- 可能还有其他插件,比如 maven-compiler-plugin -->
        </plugins>
    </build>
    
  2. 执行 Maven 打包命令
    打开终端(或 IntelliJ 的 Maven 工具窗口),在项目根目录下执行:

    # 清理旧的构建产物并重新打包
    mvn clean package
    

    或者,如果你想跳过测试:

    mvn clean package -DskipTests
    
  3. 找到并运行 Jar 包
    命令成功执行后,在项目的 target/ 目录下会生成两个 Jar 文件:

    • your-app-name-0.0.1-SNAPSHOT.jar:这是可执行 的 Spring Boot Jar 包。
    • your-app-name-0.0.1-SNAPSHOT.jar.original:这是 Maven 原始生成的 Jar 包(通常不需要它)。

    运行你的应用:

    java -jar target/your-app-name-0.0.1-SNAPSHOT.jar
    # (将 your-app-name-0.0.1-SNAPSHOT.jar 替换成实际的文件名)
    

    现在再看看,那个 Failed to determine a suitable driver class 错误是不是消失了?

优势:

  • 官方推荐,最符合 Spring Boot 设计。
  • 简单可靠,无需手动配置 Manifest 或处理依赖。
  • 能正确处理 spring.factories 和其他 Spring Boot 特定资源。

方案二:排查 DataSource 配置和依赖

如果在使用了 spring-boot-maven-plugin 正确打包后,仍然 出现 Failed to determine a suitable driver class 错误(虽然概率小了很多),那问题可能出在你的配置或依赖本身。

检查点 1:数据库驱动依赖是否存在?

确保你的 pom.xml 文件中添加了正确的数据库驱动依赖,并且其作用域 (scope) 通常是 runtime

  • MySQL 示例:

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
        <!-- 建议使用较新且稳定的版本 -->
        <!-- <version>8.0.28</version> -->
    </dependency>
    
  • PostgreSQL 示例:

    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
        <!-- <version>42.3.3</version> -->
    </dependency>
    
  • 其他数据库 :请查找对应数据库的 Maven 依赖。

重要: 确保依赖没有被意外排除,并且版本和你的数据库兼容。

检查点 2:application.propertiesapplication.yml 配置是否正确?

Spring Boot 通常能根据 spring.datasource.url 自动推断出 driver class。但检查一下总是好的,特别是 URL 格式。

  • application.properties 示例:

    # 数据源 URL
    spring.datasource.url=jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    # 用户名
    spring.datasource.username=your_username
    # 密码
    spring.datasource.password=your_password
    
    # 通常不需要显式指定驱动类,Spring Boot 会根据 URL 推断
    # 但如果推断失败,可以尝试加上(确保类名完全正确)
    # spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
  • application.yml 示例:

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
        username: your_username
        password: your_password
        # 通常不需要显式指定驱动类
        # driver-class-name: com.mysql.cj.jdbc.Driver
    

关键点检查:

  • URL 中的主机名 (localhost)、端口 (3306)、数据库名 (your_database) 是否正确?
  • 用户名、密码是否正确?
  • 对于 MySQL 8+,URL 参数如 serverTimezone=UTCallowPublicKeyRetrieval=true 可能需要。useSSL=false 也很常用(除非你真的配置了 SSL)。
  • 如果显式指定了 driver-class-name,确保类名没有拼写错误。对于 MySQL 8+,驱动类是 com.mysql.cj.jdbc.Driver,对于旧版本可能是 com.mysql.jdbc.Driver

安全建议:

别把数据库密码直接写在代码库的 application.properties/yml 文件里。考虑使用环境变量、Spring Cloud Config 或其他配置管理工具来管理敏感信息。

例如,你可以这样引用环境变量:

spring.datasource.password=${DB_PASSWORD}

然后在运行 Jar 包时设置环境变量 DB_PASSWORD

方案三:反思 IntelliJ 手动构建 / maven-assembly-plugin (不推荐用于 Spring Boot)

为啥之前用 IntelliJ 的 Artifact("JAR from modules with dependencies")或 maven-assembly-plugin 会失败?

原理回顾:

这些方法通常会采取以下两种策略之一:

  1. 解压合并 (Exploded/Fat Jar): 把所有依赖 Jar 的内容解压出来,和你自己项目的 *.class 文件混在一起,再打成一个巨大的 Jar。这种方式会破坏依赖 Jar 包内部的 META-INF 结构(比如 spring.factories),也与 Spring Boot 的 NestedJarFileClassLoader 不兼容。这就是导致 No auto configuration classes found 和后续 driver class 找不到的根本原因。
  2. 简单包含 (JAR with Manifest Class-Path): 生成一个小的 Jar,包含你的代码,并在 Manifest 文件里用 Class-Path 属性指向所有外部依赖 Jar。这种 Jar 不能独立运行,你需要把所有依赖 Jar 放在指定的相对路径下。它不是 Spring Boot 设计的可执行 Jar 格式。

结论:

对于 Spring Boot 应用,想要打成一个独立的、可以直接 java -jar 运行的包,请坚持使用 spring-boot-maven-plugin (或对应的 Gradle 插件 org.springframework.boot)。避免使用 IntelliJ 的 Artifact "JAR from modules with dependencies" 功能或通用的 maven-assembly-plugin (除非你对其进行极其复杂的配置以模拟 Spring Boot 的打包结构,但完全没必要)。

进阶技巧

使用 Spring Profiles 管理不同环境配置

你的开发环境数据库和生产环境数据库通常不一样。可以使用 Spring Profiles 来管理。

  1. 创建不同环境的配置文件,例如:

    • application-dev.properties (开发环境配置)
    • application-prod.properties (生产环境配置)
  2. 在各自文件里配置对应环境的 spring.datasource.url, username, password

  3. 运行时通过 spring.profiles.active 属性指定要激活哪个配置:

    # 运行开发环境配置
    java -jar target/your-app.jar --spring.profiles.active=dev
    
    # 运行生产环境配置(更常见的是通过环境变量或启动脚本设置)
    java -jar target/your-app.jar --spring.profiles.active=prod
    

理解 Classpath 和 Spring Boot Jar

需要注意,对于 Spring Boot 打包的可执行 Jar,你不能像普通 Jar 那样使用 -cp-classpath 参数来添加额外的依赖或覆盖 Jar 内部的类。Spring Boot 的自定义类加载器会接管加载过程,它只认 BOOT-INF/classesBOOT-INF/lib 里的内容。

调试运行中的 Jar 包

如果应用在 Jar 包运行时表现异常,可以开启远程调试。

# 监听 5005 端口进行调试
java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005 -jar target/your-app.jar

然后在你的 IDE (如 IntelliJ) 中配置一个 Remote Debug,连接到对应的端口(这里是 5005)。suspend=n 表示应用启动时不会暂停等待调试器连接。


回到最初的问题,通过使用标准的 spring-boot-maven-plugin 来打包,大概率能一次性解决从 Manifest 找不到、自动配置失败到数据库驱动找不到这一系列连锁问题。如果配置和依赖也确认无误,你的 Spring Boot 应用应该就能愉快地以 Jar 包形式跑起来了。