简介:这份资源面向JSP初学者与Web开发入门者,聚焦小型项目或教学实践中JSP与ACCESS数据库的基础数据交互问题。包内共1个doc文档,约31KB,以图文与代码示例讲解如何创建test.mdb数据库及username表,将数据库文件放入Tomcat指定目录,并编写JDBCAccess.jsp完成连接与数据读取。内容涵盖JDBC-ODBC桥驱动加载、连接字符串拼接、Statement与ResultSet使用,以及资源关闭等关键环节,同时提示ACCESS在企业级应用中的局限,并对比MySQL、Oracle等数据库的连接差异。已有239人学习,适合希望理解JDBC原理、快速跑通JSP访问ACCESS流程的读者参考。
1. JSP 连接 ACCESS 数据库:为什么老项目还在用,新项目要不要碰
接手一个 2010 年前后的内部管理系统,服务器上跑着 Tomcat 6,数据库是一个.mdb文件,需求是加一个「设备借用登记」页面。你打开项目一看,数据源配置里写着jdbc:odbc:driver={Microsoft Access Driver (*.mdb)},连密码都没有。这就是 JSP 连接 ACCESS 数据库最典型的场景——不是因为它好,而是因为它已经在那儿了。
ACCESS 是文件型数据库,单文件、零服务、拷走就能跑,适合单机或极小并发(个位数同时在线)的内部工具。JSP 作为服务端页面技术,通过 JDBC-ODBC 桥或 UCanAccess 纯 Java 驱动读写.mdb/.accdb。这套组合能解决「不想装 MySQL、不想配服务、只想快速出增删改查页面」的问题,适合维护老系统、做课程设计、写小型内部登记工具的人。不适合高并发、多用户写入、需要事务隔离的生产系统——这一点必须先说清楚,否则后面全是坑。
2. 两条连接路线:JDBC-ODBC 桥与 UCanAccess 怎么选
2.1 先搞清楚 ACCESS 驱动在 JDK 里的命运
JDK 8 之前,sun.jdbc.odbc.JdbcOdbcDriver是内置的,写一行Class.forName("sun.jdbc.odbc.JdbcOdbcDriver")就能连。JDK 8 开始这个桥被标记废弃,JDK 11 之后彻底移除。所以如果你现在用 JDK 8 以上,JDBC-ODBC 桥这条路基本走不通,除非你手动把rt.jar里的相关类抽出来——不推荐,维护成本高。
常见做法是改用 UCanAccess,这是一个纯 Java 的 ACCESS 驱动,底层用 Jackcess 库直接读.mdb/.accdb文件,不依赖操作系统的 ODBC 数据源。它支持 JDK 8 到 JDK 17,Maven 坐标是net.sf.ucanaccess:ucanaccess。选它的理由很直接:跨平台、不装驱动、不配 DSN、代码里写 JDBC URL 就能跑。
如果你维护的是 JDK 6/7 的老项目,且服务器是 Windows,那 JDBC-ODBC 桥还能用,配置 ODBC 数据源反而更省事——因为老代码里可能已经写死了jdbc:odbc:xxx。两条路线的对比如下:
| 对比项 | JDBC-ODBC 桥 | UCanAccess |
|---|---|---|
| 依赖 | 系统 ODBC 数据源 + JDK 内置桥 | 纯 Java jar 包 |
| JDK 支持 | JDK 8 以下 | JDK 8 及以上 |
| 跨平台 | 仅 Windows | 全平台 |
| 部署复杂度 | 需在服务器配 DSN | 拷 jar 即可 |
| 并发能力 | 差 | 差(本质一样) |
| 推荐场景 | 维护 JDK 7 老项目 | 新写或升级项目 |
2.2 UCanAccess 的最小可运行配置
先加依赖。Maven 项目在pom.xml里写:
<dependency> <groupId>net.sf.ucanaccess</groupId> <artifactId>ucanaccess</artifactId> <version>5.0.1</version> </dependency>如果你不用 Maven,需要手动下载这几个 jar 放进WEB-INF/lib:ucanaccess、jackcess、commons-lang3、commons-logging、hsqldb。少一个都会在运行时报ClassNotFoundException,这是血泪经验。
然后写一个连接工具类:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class AccessConn { // ACCESS 文件放在 WEB-INF/db 下,避免被直接下载 private static final String DB_PATH = "D:/project/webapp/WEB-INF/db/device.mdb"; private static final String URL = "jdbc:ucanaccess://" + DB_PATH + ";memory=false"; public static Connection getConnection() throws SQLException { try { Class.forName("net.ucanaccess.jdbc.UcanaccessDriver"); } catch (ClassNotFoundException e) { throw new SQLException("UCanAccess 驱动未找到", e); } return DriverManager.getConnection(URL); } }逻辑说明:jdbc:ucanaccess://后面跟绝对路径,memory=false表示不把整个库加载进内存——对于几十 MB 的.mdb,设true会快一点,但内存占用高;设false每次读磁盘,适合文件不大但内存紧张的场景。参数DB_PATH必须用绝对路径,相对路径在 Tomcat 里会相对于bin目录,不是你的项目目录,这是新手最容易翻车的地方。
2.3 JDBC-ODBC 桥的配置步骤(仅限 JDK 7 及以下)
如果你确实在老环境里,步骤是:控制面板 → 管理工具 → ODBC 数据源(32 位)→ 系统 DSN → 添加 → 选Microsoft Access Driver (*.mdb)→ 填数据源名(比如devdb)→ 选文件。然后 Java 代码里:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection conn = DriverManager.getConnection("jdbc:odbc:devdb", "", "");注意 Tomcat 如果是 64 位,ODBC 数据源也必须配 64 位的,否则报[Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序。这个报错我见过太多次,原因就是 32 位 DSN 配了 64 位 Tomcat。
3. 在 JSP 里写增删改查:从建表到分页展示
3.1 建表与字段类型映射
ACCESS 的字段类型和 Java 类型对应关系要心里有数,否则getObject会给你惊喜:
| ACCESS 类型 | Java 读取方式 | 注意 |
|---|---|---|
| 文本(短文本) | rs.getString() | 长度默认 255 |
| 备注(长文本) | rs.getString() | 可能返回null |
| 数字(长整型) | rs.getLong() | 自增主键用这个 |
| 日期/时间 | rs.getTimestamp() | 格式敏感 |
| 是/否 | rs.getBoolean() | ACCESS 里存 -1/0 |
| OLE 对象 | rs.getBytes() | 存图片用,不推荐 |
建表可以在 ACCESS 界面里手动建,也可以用 SQL 在 JSP 里建。手动建更稳,因为 UCanAccess 对CREATE TABLE的语法支持有限,比如AUTOINCREMENT关键字有时不认。我一般建议在 ACCESS 里建好表,JSP 只做增删改查。
3.2 查询与分页的 JSP 片段
下面是一个设备列表分页查询的 JSP 核心代码:
<%@ page import="java.sql.*, com.example.AccessConn" %> <% int pageSize = 10; int pageNo = 1; String p = request.getParameter("page"); if (p != null) pageNo = Integer.parseInt(p); int offset = (pageNo - 1) * pageSize; String sql = "SELECT id, name, status FROM device ORDER BY id DESC"; Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = AccessConn.getConnection(); // ACCESS 不支持 LIMIT OFFSET,用绝对定位模拟 ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); rs = ps.executeQuery(); rs.absolute(offset + 1); // 定位到本页第一条 int count = 0; while (rs.next() && count < pageSize) { count++; %> <tr> <td><%= rs.getLong("id") %></td> <td><%= rs.getString("name") %></td> <td><%= rs.getInt("status") == 1 ? "在用" : "停用" %></td> </tr> <% } } catch (Exception e) { out.println("查询失败:" + e.getMessage()); } finally { if (rs != null) rs.close(); if (ps != null) ps.close(); if (conn != null) conn.close(); } %>逻辑说明:ACCESS 的 SQL 方言不支持LIMIT和OFFSET,所以分页要用ResultSet的滚动游标。TYPE_SCROLL_INSENSITIVE允许absolute()定位,CONCUR_READ_ONLY减少锁开销。rs.absolute(offset + 1)把游标移到本页第一条,然后循环取pageSize条。参数pageSize建议不超过 20,因为 ACCESS 文件锁在并发读时表现很差,一次取太多会拖慢响应。
3.3 插入、更新、删除的写法与事务
增删改都用PreparedStatement,不要拼字符串,否则单引号就能让你的页面崩掉:
public static int addDevice(String name, int status) throws SQLException { String sql = "INSERT INTO device (name, status) VALUES (?, ?)"; try (Connection conn = AccessConn.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, name); ps.setInt(2, status); return ps.executeUpdate(); } }更新和删除同理,把 SQL 换成UPDATE device SET name=? WHERE id=?或DELETE FROM device WHERE id=?。注意 ACCESS 的executeUpdate返回受影响行数,但有时因为文件锁会返回 0 而不报错,所以业务层要再查一次确认。
事务方面,ACCESS 支持conn.setAutoCommit(false)和commit(),但并发写入时容易死锁。我的做法是:单条增删改不显式开事务,靠自动提交;批量操作才开事务,且加conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED)。即便如此,两个用户同时写同一个.mdb仍可能报Could not update; currently locked,这是 ACCESS 的硬限制,不是代码问题。
4. 避坑与排查:ACCESS 连接最常见的 5 个翻车现场
4.1 报错[Microsoft][ODBC 驱动程序管理器] 未发现数据源名称
现象:JSP 页面抛出这个异常,堆栈指向DriverManager.getConnection。
原因:ODBC 数据源没配,或者配的位数和 Tomcat 不一致。32 位 DSN 在 64 位 JVM 里不可见,反之亦然。
解决:确认 Tomcat 的位数(java -version看 64-Bit 字样),然后去C:\Windows\SysWOW64\odbcad32.exe(32 位)或C:\Windows\System32\odbcad32.exe(64 位)配对应的系统 DSN。配完重启 Tomcat。
4.2 报错UCanAccessDriver not found
现象:Maven 依赖加了,但运行时报ClassNotFoundException: net.ucanaccess.jdbc.UcanaccessDriver。
原因:UCanAccess 依赖链没打全,缺jackcess或hsqldb。Maven 有时因为仓库问题只下了主包。
解决:在pom.xml里显式加jackcess和hsqldb依赖,或者用mvn dependency:copy-dependencies把所有传递依赖拷到WEB-INF/lib。检查WEB-INF/lib下至少有 5 个相关 jar。
4.3 报错Could not update; currently locked
现象:多人同时操作时,插入或更新失败,提示文件被锁。
原因:ACCESS 是文件锁,一个写操作会锁整个文件,其他写操作排队或直接失败。
解决:把memory=false改成memory=true让 UCanAccess 用内存副本,减少文件锁时间;或者用conn.setAutoCommit(true)尽快释放锁。根本方案是换数据库,如果并发超过 5 个人,ACCESS 就不该继续用。
4.4 中文乱码
现象:JSP 页面显示的中文变成???或乱码。
原因:ACCESS 文件本身编码、JDBC URL 没指定字符集、JSP 页面pageEncoding不一致。
解决:JSP 顶部写<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>;UCanAccess 的 URL 加;charSet=UTF-8;ACCESS 建表时字段用「短文本」而不是「备注」,备注字段在某些驱动版本下编码处理有问题。
4.5 日期格式写入失败
现象:ps.setTimestamp()写入 ACCESS 日期字段时报Data type mismatch。
原因:ACCESS 的日期字段对格式敏感,java.sql.Timestamp在某些驱动版本下不被识别。
解决:改用ps.setString()传yyyy-MM-dd HH:mm:ss格式的字符串,ACCESS 会自动转换。或者用ps.setObject()传java.util.Date,让驱动自己处理。我一般用字符串,最稳。
5. 进阶技巧:用连接池和只读模式把 ACCESS 压榨到极限
ACCESS 本身不支持连接池,但你可以用 DBCP 或 HikariCP 包一层,减少频繁打开文件的开销。配置 HikariCP 的maximumPoolSize设 3 到 5,connectionTimeout设 3000 毫秒,idleTimeout设 60000。超过 5 个连接反而会因为文件锁互相等待,性能下降。
另一个技巧是只读模式。如果页面只做展示,UCanAccess 的 URL 加;readonly=true,这样多个请求可以同时读,不会互相锁。写操作单独走一个连接,读写分离在 ACCESS 层面也能凑合实现。
验证方法:用 JMeter 开 10 个线程循环请求列表页,观察响应时间。如果平均超过 500 毫秒,说明文件锁在拖后腿,这时候要么加缓存(把查询结果放application作用域,定时刷新),要么换 SQLite——SQLite 的并发读比 ACCESS 好,迁移成本也不高。
我自己的习惯是:新项目一律不选 ACCESS,老项目维护时先确认 JDK 版本,JDK 8 以上直接上 UCanAccess,JDK 7 以下才考虑 ODBC 桥。每次改完连接代码,先在本机用main方法跑一遍增删改查,再部署到 Tomcat,能省掉一半的调试时间。希望帮到你。
本文还有配套的精品资源,点击获取