Java连接Access数据库实战:UCanAccess 4.0.4环境配置与避坑指南
简介:面向需要在 Java 项目中直连 Access 数据库(mdb 文件)的开发者,这份资源提供了一套开源免费的 UCanAccess 4.0.4 解决方案,可替代存在查询次数(50 次)与单次查询条数(1000 条)限制的 Access_JDBC30.jar,通过 JDBC 直连方式访问 mdb,无需依赖 JDBC-ODBC 桥接,且不受查询次数与条数约束,适合中小型系统迁移、报表读取与数据同步等场景。压缩包共 16 个文件,约 2.57MB,以 6 个 jar 依赖包为核心,辅以 8 个 txt 说明与许可文档、1 个 bat 和 1 个 sh 启动脚本,覆盖运行所需组件与使用指引,jar 之间依赖关系清晰,便于按需引入工程。目前已有 741 人学习下载,说明其在 Access 直连需求中具备一定参考价值。读者可据此快速搭建无限制的 JDBC 连接环境,理解各依赖包的分工与加载方式,并参照包内说明完成集成与排错。
1. 从一次凌晨两点的连接失败说起:Java 读 Access 到底该装什么
一个做内部报表的小项目,后端要读一个历史遗留的.accdb文件,本地用 IDE 跑得好好的,打成 jar 丢到测试机上就报No suitable driver found。折腾到凌晨两点才发现,问题不在代码,而在依赖:JDK 里根本没有 Access 的驱动,得靠 UCanAccess 这套纯 Java 的 jar 包顶上。Access 数据库在不少老系统里还活着,Java 想连它,绕不开 UCanAccess,而版本选错、jar 少一个,报错能让你怀疑人生。这篇记录的就是ucanaccess-4.0.4.jar这套环境怎么装、怎么配、装坏了怎么救,适合正在被 Access 连接问题卡住的 Java 开发者,新手能照着走通,熟手能看清版本和依赖的取舍。
2. 装前把环境列清:JDK、依赖 jar 与 Access 文件格式
2.1 版本对应关系先对齐
UCanAccess 不是单个 jar 就能跑起来的,它底层依赖 HSQLDB 做内存中转,还要 Jackcess 解析 Access 文件,另外需要 commons-lang、commons-logging 这类基础库。ucanaccess-4.0.4.jar这个版本对应的是 JDK 7 及以上,实际项目里我一般用 JDK 8 跑,兼容性最稳。JDK 11 之后模块化带来一些反射限制,如果非要用高版本 JDK,得留意--add-opens相关的告警,这是后话,先把 JDK 8 这条最顺的路走通。
版本选择上有个反直觉的点:不是越新越好。UCanAccess 5.x 之后对 JDK 版本要求提高,部分老项目还停留在 JDK 7,硬上 5.x 会直接编译不过。4.0.4属于 4.x 系列里比较成熟的一个点,配套的 Jackcess 是 2.1.x 系列,HSQLDB 是 2.3.x 系列,这几个版本号是绑定的,不能随手换成最新版,否则运行期会出现NoSuchMethodError这种让人抓狂的玄学问题。
下面这张表是我整理的环境对照,装之前先核一遍:
| 组件 | 版本 | 作用 | 是否必需 |
|---|---|---|---|
| JDK | 8(最低 7) | 运行环境 | 必需 |
| ucanaccess | 4.0.4 | Access 驱动主包 | 必需 |
| jackcess | 2.1.9 左右 | 解析 accdb/mdb | 必需 |
| hsqldb | 2.3.4 左右 | 内存数据库中转 | 必需 |
| commons-lang | 2.6 | 基础工具 | 必需 |
| commons-logging | 1.2 | 日志 | 必需 |
| commons-lang3 | 3.x | 部分版本需要 | 视情况 |
2.2 确认 Access 文件格式与驱动匹配
装之前还有一步容易被跳过:确认你手里的 Access 文件是.mdb还是.accdb。UCanAccess 对两者都支持,但.accdb是 Access 2007 之后的格式,.mdb是老格式,底层解析路径不同。如果文件是加密的或者带密码,连接串里要额外带密码参数,否则会报UCAExc:::4.0.4 given file does not exist或者权限类错误,看着像文件不存在,其实是认证没过。
我一般会先用一个最小 Java 程序验证文件能不能被识别,而不是直接塞进业务代码里调试。确认格式这一步花两分钟,能省掉后面半小时的瞎猜。
3. 下载与依赖装配:把 6 个 jar 凑齐的正确姿势
3.1 手动下载与目录组织
最直接的方式是把ucanaccess-4.0.4.jar和它的依赖 jar 全部下下来,放到项目的lib目录。Maven 项目更省事,直接在pom.xml里声明,让构建工具去拉传递依赖。但要注意,UCanAccess 的传递依赖有时候拉不全,尤其是老版本仓库里 jackcess 的版本可能被覆盖,所以我习惯手动核对一遍依赖树。
手动装配的目录结构大概是这样:
project/ ├── lib/ │ ├── ucanaccess-4.0.4.jar │ ├── jackcess-2.1.9.jar │ ├── hsqldb-2.3.4.jar │ ├── commons-lang-2.6.jar │ ├── commons-logging-1.2.jar │ └── commons-lang3-3.4.jar └── src/把 jar 放进lib之后,IDE 里要手动 Add as Library,命令行编译运行则要拼 classpath。这一步新手最容易漏,漏一个 jar 的报错往往指向别处,比如缺 hsqldb 时报的是驱动加载失败,看着像主包没装。
3.2 Maven 依赖写法与版本锁定
Maven 项目里我一般这样写,把版本号显式锁死,避免传递依赖被别的库顶掉:
<dependency> <groupId>net.sf.ucanaccess</groupId> <artifactId>ucanaccess</artifactId> <version>4.0.4</version> </dependency> <!-- 显式锁定底层依赖,防止版本漂移 --> <dependency> <groupId>com.healthmarketscience.jackcess</groupId> <artifactId>jackcess</artifactId> <version>2.1.9</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.3.4</version> </dependency>这里的关键是后两个依赖要显式声明。UCanAccess 的 pom 里虽然带了传递依赖,但项目里如果同时引了别的库,Maven 的最近优先原则可能把 jackcess 换成不兼容的版本,运行期就炸。显式写死版本,等于给自己留了后悔药。参数上version不要用LATEST或范围写法,生产环境锁死具体版本是基本纪律。
3.3 纯命令行场景的 classpath 拼法
不用 Maven 的老项目,编译和运行都要手动拼 classpath。Windows 下分隔符是分号,Linux 和 macOS 是冒号,这个搞混了会报Could not find or load main class,看着像主类写错,其实是路径没拼对。
# Linux/macOS 下编译,注意冒号分隔 javac -cp "lib/ucanaccess-4.0.4.jar:lib/jackcess-2.1.9.jar:lib/hsqldb-2.3.4.jar:lib/commons-lang-2.6.jar:lib/commons-logging-1.2.jar" -d out src/AccessDemo.java # 运行,classpath 要包含 out 目录和所有 jar java -cp "out:lib/*" AccessDemolib/*这种通配写法在 Java 6 之后支持,能省去逐个列 jar 的麻烦,但要注意通配符不会递归子目录,jar 必须平铺在lib下。Windows 下把冒号换成分号即可。这一步跑通,说明依赖装配没问题,可以进入连接配置。
4. 连接配置与代码落地:URL 参数与驱动注册
4.1 JDBC URL 的写法与参数含义
UCanAccess 的 JDBC URL 格式是固定的,写错一个字符就连不上:
jdbc:ucanaccess://<文件路径>;memory=<true|false>;showSchema=<true|false>memory=true表示把整个 Access 文件加载进内存再操作,读多写少的场景下性能更好,但文件大了会吃内存;memory=false是直接操作文件,适合大文件或者需要多进程访问的情况。showSchema打开后会在日志里打印表结构,调试阶段很有用,上线前关掉。
我一般读报表类场景用memory=true,因为文件通常不大,内存中转快;如果是几十万行的历史库,就老老实实memory=false,避免 OOM。这个参数没有绝对对错,看文件大小和访问模式。
4.2 一段能直接跑的最小连接代码
下面这段是我常用的验证模板,先跑通再往业务里搬:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class AccessDemo { public static void main(String[] args) throws Exception { // 显式加载驱动,老 JDK 或某些容器环境下必需 Class.forName("net.ucanaccess.jdbc.UcanaccessDriver"); // memory=true 表示整库载入内存,适合小文件读场景 String url = "jdbc:ucanaccess:///data/report.accdb;memory=true"; try (Connection conn = DriverManager.getConnection(url); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Sheet1")) { if (rs.next()) { System.out.println("行数: " + rs.getInt(1)); } } } }Class.forName这行在 JDBC 4.0 之后理论上可以省略,但 UCanAccess 在部分容器和 OSGi 环境下自动注册会失效,显式加载最保险。URL 里的路径用绝对路径,相对路径在不同工作目录下解析结果不一样,是常见的翻车点。try-with-resources保证连接和语句关闭,Access 文件被占用时如果不关连接,下次打开会报文件锁错误。
4.3 表名与字段名的坑
Access 的表名和字段名经常带空格或者中文,SQL 里必须用方括号包起来,比如SELECT [客户名称] FROM [订单 表]。不加方括号,解析器会把空格当成分隔符,报语法错误。这一点和 MySQL 的反引号类似,但 Access 只认方括号。字段名大小写不敏感,但表名在某些版本下敏感,统一按文件里的原始写法最稳。
5. 避坑与排查:五条血泪记录
5.1 报 No suitable driver found
现象是运行时报java.sql.SQLException: No suitable driver found for jdbc:ucanaccess://...。原因通常是驱动类没加载,或者 classpath 里缺 ucanaccess 主包。解决方法是先确认Class.forName("net.ucanaccess.jdbc.UcanaccessDriver")有没有执行,再检查 jar 是否真的在 classpath 里。用java -cp跑的时候,lib/*通配符在部分 shell 下不展开,得手动列全。
5.2 报 UCAExc 开头的版本冲突
现象是UCAExc:::4.0.4 java.lang.NoSuchMethodError或者NoClassDefFoundError。原因是 jackcess 或 hsqldb 版本被别的依赖顶掉了,和 4.0.4 不匹配。解决办法是用mvn dependency:tree看依赖树,把 jackcess 和 hsqldb 显式锁到 2.1.9 和 2.3.4,排除掉冲突来源。这个坑最隐蔽,因为编译期不报错,运行到具体方法才炸。
5.3 文件被占用或锁死
现象是第二次连接时报File is currently locked或者写入失败。原因是上一次的连接没关闭,Access 文件被进程持有。解决方法是确保每个Connection都在finally或 try-with-resources 里关闭,memory=true模式下尤其要注意,内存库不关会一直占着文件句柄。另外多线程同时写同一个文件也会锁,读场景尽量用内存模式规避。
5.4 中文乱码
现象是查出来的中文字段显示成问号或乱码。原因是 Access 文件的字符集和 JVM 默认编码不一致。解决办法是启动参数加-Dfile.encoding=UTF-8,同时确认 Access 文件本身存的是 Unicode。老.mdb文件如果是本地编码存的,可能需要额外转换,这种情况我一般先用 Access 另存为.accdb再读,省事。
5.5 高版本 JDK 的反射告警
现象是 JDK 11 及以上运行时刷一堆WARNING: An illegal reflective access operation has occurred。原因是 UCanAccess 4.0.4 内部用了反射访问 JDK 内部 API,模块化之后被限制。解决办法是启动参数加--add-opens java.base/java.lang=ALL-UNNAMED之类的开放指令,或者干脆降回 JDK 8。生产环境我倾向后者,少一堆告警,心里踏实。
6. 装完怎么验证:三步跑通再进业务
装完不能只看编译通过,得实际跑一次查询才算数。我的验证习惯分三步,每步都有明确的通过标准。
第一步,驱动加载验证。写一个只有Class.forName的 main 方法,跑起来不抛ClassNotFoundException,说明主包和依赖都在 classpath 里。这一步排除掉一半的装配问题。
第二步,连接与元数据验证。连上之后不查业务表,先查DatabaseMetaData拿表清单:
try (Connection conn = DriverManager.getConnection(url)) { var meta = conn.getMetaData(); try (ResultSet tables = meta.getTables(null, null, "%", new String[]{"TABLE"})) { while (tables.next()) { System.out.println("表: " + tables.getString("TABLE_NAME")); } } }能列出表名,说明文件解析、驱动、连接串全链路通了。这一步比直接查业务表更稳,因为不依赖具体表结构。
第三步,真实查询与类型验证。挑一张有中文、有日期、有数字的表,各查一个字段,确认类型映射正确。Access 的日期字段读出来是Timestamp,货币字段是BigDecimal,如果映射错位,业务层会出静默错误。我一般会打印rs.getObject(i).getClass()核对一遍。
三步都过,再往业务代码里搬。从那以后我每次接 Access 相关的活,都强制先走这三步验证,不再直接改业务代码试错,省下的调试时间比装环境本身还多。希望帮到你。
本文还有配套的精品资源,点击获取