当你在MyBatis项目中看到"java.lang.ClassNotFoundException: Cannot find class: ${jdbc.driver}"这样的报错时,意味着JVM在运行时无法加载你配置的JDBC驱动类。这个错误看似简单,但背后可能隐藏着多种配置问题。作为Java开发者,我遇到过不下十次这类问题,每次的解决方式都不尽相同。
这个错误通常发生在MyBatis初始化阶段,具体表现为:
问题的核心在于:MyBatis配置中指定的JDBC驱动类没有被正确加载到JVM的classpath中。可能的原因包括但不限于:驱动jar包缺失、类名拼写错误、依赖作用域配置不当、环境变量未正确替换等。
JDBC驱动采用的是SPI(Service Provider Interface)机制,但传统的Class.forName()显式加载方式仍然有效。当出现ClassNotFoundException时,说明以下环节至少有一个出了问题:
重要提示:从JDBC 4.0开始,理论上不需要显式调用Class.forName(),但实际项目中显式加载仍是推荐做法,可以提前发现问题。
MyBatis在初始化数据源时会经历以下关键步骤:
当第三步失败时,就会抛出我们看到的ClassNotFoundException。值得注意的是,如果使用属性占位符(如${jdbc.driver}),问题可能出在属性替换阶段。
按照以下顺序逐步排查:
xml复制<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
<!-- 不要使用provided或test作用域 -->
<scope>runtime</scope>
</dependency>
验证驱动类名:
检查配置文件:
xml复制<!-- mybatis-config.xml -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
...
</dataSource>
properties复制# application.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
如果基础检查后问题仍然存在,试试这些方法:
方法1:检查最终生成的配置
java复制// 在初始化代码后添加调试语句
System.out.println("Actual driver class: " + configuration.getEnvironment().getDataSource().getDriver());
方法2:手动加载测试
java复制try {
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("Driver loaded successfully");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
方法3:依赖树分析
code复制mvn dependency:tree | grep connector
场景1:Spring Boot项目中
properties复制# 确保使用正确属性名
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
场景2:多模块项目
场景3:动态数据源
java复制// 手动注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
现象:错误信息中显示字面量$
解决方案:
xml复制<properties resource="application.properties"/>
现象:报错信息显示部分类能找到但部分不能
解决方案:
xml复制<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
<exclusions>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
现象:本地运行正常但Docker/K8s中报错
解决方案:
dockerfile复制COPY target/lib/mysql-connector-java-8.0.28.jar /app/lib/
依赖管理规范:
配置检查清单:
启动时验证:
java复制@PostConstruct
public void validateDataSource() {
try {
dataSource.getConnection().close();
} catch (SQLException e) {
throw new RuntimeException("DB connection failed", e);
}
}
xml复制<logger name="org.mybatis" level="DEBUG"/>
<logger name="java.sql" level="TRACE"/>
当出现ClassNotFoundException时,JVM经历了以下加载过程:
驱动类加载失败通常发生在第3步,说明:
关键阶段在第4步,MyBatis会调用:
java复制Class.forName(configuration.getDriver());
从JDBC 4.0开始支持自动加载:
查看类路径:
bash复制# Java 8
java -verbose:class -version 2>&1 | grep "Loaded"
# Java 9+
java --list-modules | grep jdbc
检查jar包内容:
bash复制jar tf mysql-connector-java-8.0.28.jar | grep Driver.class
IntelliJ IDEA技巧:
Eclipse技巧:
配置以下日志器获取详细信息:
properties复制logging.level.org.springframework.jdbc=DEBUG
logging.level.com.zaxxer.hikari=DEBUG
驱动版本选择:
连接参数优化:
properties复制jdbc.url=jdbc:mysql://host:3306/db?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
xml复制<dataSource type="POOLED">
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
</dataSource>
xml复制<profiles>
<profile>
<id>dev</id>
<properties>
<jdbc.driver>com.mysql.cj.jdbc.Driver</jdbc.driver>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<jdbc.driver>com.mysql.jdbc.Driver</jdbc.driver>
</properties>
</profile>
</profiles>
yaml复制spring:
profiles: dev
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
---
spring:
profiles: prod
datasource:
driver-class-name: com.mysql.jdbc.Driver
当驱动类加载成功后,连接池的实现也会影响最终表现:
| 连接池类型 | 初始化时机 | 驱动加载要求 |
|---|---|---|
| HikariCP | 延迟加载 | 必须提前可用 |
| Tomcat JDBC | 立即初始化 | 必须提前可用 |
| Druid | 立即初始化 | 必须提前可用 |
| MyBatis内置 | 立即初始化 | 必须提前可用 |
在实际项目中,我推荐使用HikariCP配合显式驱动加载,这种组合最稳定可靠。