简介:这份资源面向JSP入门学习者与小型Web项目开发者,聚焦JSP通过JDBC-ODBC桥连接ACCESS数据库并读取数据的基础实践,帮助读者理解Java Web与数据库交互的完整链路。包内为1个doc文档,压缩包约31KB,以图文与代码示例形式呈现,便于边看边在本地环境复现。文档围绕建立test.mdb数据库、设计username表及uid、pwd文本字段、将数据库文件放入Tomcat指定目录、编写JDBCAccess.jsp等环节展开,并给出加载sun.jdbc.odbc.JdbcOdbcDriver、建立连接、执行查询、遍历结果集及关闭资源的完整代码与注释说明,同时提示连接字符串与路径需按实际环境调整。已有241人学习,适合作为JDBC入门的对照范例与排错参考。
1. 一个 JSP 页面连 Access:老项目里最容易被低估的“最后一公里”
接手一个十年前的教学管理系统时,最让人头疼的往往不是业务逻辑,而是首页那个“数据库连接失败”的红字提示。项目用的是 JSP 连接 Access 数据库,test.mdb就躺在 Tomcat 的webapps/ROOT/data目录下,代码看起来只有几十行,但换一台机器就报java.sql.SQLException。这类 JSP 连 Access 的场景在小型项目、课程设计、实验室内部工具里非常常见:数据量不大、部署简单、不需要独立数据库服务,一个.mdb文件加一个 Tomcat 就能跑。适合谁?适合手里有老 JSP 工程要维护、或者想理解 JDBC-ODBC 桥接到底怎么把 Web 容器和桌面数据库串起来的开发者。下面从目录结构、连接代码、驱动加载到踩坑排查,把这条链路完整拆一遍。
2. 项目结构与运行环境:文件放哪、容器怎么起
2.1 目录布局与文件职责
这个项目的物理结构非常扁平,核心只有三样东西:Access 数据库文件、JSP 页面、Tomcat 容器。把test.mdb放在webapps/ROOT/data下不是随便选的,因为 JSP 里用的是application.getRealPath("data/test.mdb"),这个 API 返回的是当前 Web 应用根目录下的物理路径。ROOT 是 Tomcat 默认的根应用,所以getRealPath拼出来的就是.../webapps/ROOT/data/test.mdb。如果换成自定义应用名,比如myapp,那文件就要放到webapps/myapp/data下,否则路径对不上。
典型的部署目录长这样:
Tomcat 6.0/ ├── webapps/ │ └── ROOT/ │ ├── data/ │ │ └── test.mdb # Access 数据库文件 │ ├── JDBCAccess.jsp # 数据访问测试页 │ └── WEB-INF/ │ └── web.xml ├── lib/ │ └── (JDK 自带的 rt.jar 内含 JDBC-ODBC 桥) └── conf/ └── server.xml这里有个容易被忽略的点:test.mdb不需要打进 WAR 包,也不需要放在WEB-INF下。放在ROOT/data的好处是路径直观、getRealPath能直接命中;坏处是文件暴露在 Web 根目录下,理论上可以被直接下载。生产环境常见做法是把 mdb 放到容器外的固定目录,JSP 里写绝对路径或通过配置读取,避免数据库文件被 HTTP 直接访问。
2.2 运行环境与依赖版本
这套方案对版本比较敏感,不是随便一个 Tomcat 加 JDK 就能跑。JDBC-ODBC 桥驱动sun.jdbc.odbc.JdbcOdbcDriver是 JDK 自带的,从 JDK 1.1 一直带到 JDK 7,JDK 8 开始被标记为废弃但还能用,JDK 11 之后彻底移除。所以如果拿 JDK 17 去跑这段代码,Class.forName会直接抛ClassNotFoundException,这不是代码写错了,是驱动没了。
| 组件 | 可用版本范围 | 说明 |
|---|---|---|
| JDK | 1.6 ~ 1.8 | JDK 8 仍含桥驱动,JDK 11+ 移除 |
| Tomcat | 6.x / 7.x / 8.x | 示例路径按 Tomcat 6 写,7/8 结构一致 |
| Access 驱动 | Microsoft Access Driver (*.mdb) | 32 位 Office 自带,64 位系统需注意位数匹配 |
| 数据库文件 | .mdb(Access 2003 及以前) | .accdb 需要换驱动名 |
位数匹配是血泪经验里排第一的坑:Tomcat 跑在 64 位 JVM 上,但系统里装的 ODBC 驱动是 32 位的,连接时就会报“未找到驱动程序”。因为 JDBC-ODBC 桥最终调用的是操作系统的 ODBC 管理器,JVM 位数和 ODBC 驱动位数必须一致。常见做法是确认控制面板 → 管理工具 → 数据源(ODBC)里能看到Microsoft Access Driver (*.mdb),64 位系统要专门看 64 位 ODBC 管理器那一项。
2.3 启动与验证步骤
把文件放好后,启动 Tomcat,访问http://localhost:8080/JDBCAccess.jsp。如果页面输出“用户名: xxx 密码: xxx”和“恭喜你!数据库连接成功!”,说明整条链路通了。如果只看到异常信息,先别改代码,按顺序确认三件事:mdb 文件是否真的在getRealPath指向的位置、JVM 位数和 ODBC 驱动位数是否一致、username表名和字段名是否和 SQL 里写的一致。这三步能解决八成以上的连接失败。
3. 核心连接代码拆解:从 getRealPath 到 ResultSet 的完整链路
3.1 路径转换:为什么不能直接写相对路径
JSP 里第一行关键代码是application.getRealPath(spath)。很多人会问,既然 mdb 就在data/test.mdb,为什么不能直接把"data/test.mdb"塞进连接字符串?因为 JDBC-ODBC 桥最终要把路径交给操作系统的 ODBC 驱动,驱动需要的是文件系统绝对路径,而不是 Web 应用的相对路径。getRealPath的作用就是把 Servlet 上下文里的相对路径翻译成服务器磁盘上的真实路径。
<% String spath = "data/test.mdb"; // Web 应用内的相对路径 String dbpath = application.getRealPath(spath); // 转成磁盘绝对路径 // 典型输出:C:\...\webapps\ROOT\data\test.mdb %>参数说明:spath必须以/或相对应用根目录的形式书写,不能带盘符;application是 JSP 内置的ServletContext对象,getRealPath在应用未解压(比如直接跑 WAR)时可能返回 null,这是另一个隐蔽的坑。如果返回 null,说明容器没有把应用展开到磁盘,需要配置 Tomcat 解压 WAR 或者改用绝对路径。
3.2 连接字符串与驱动加载
连接字符串的格式是jdbc:odbc:Driver={驱动名};DBQ=数据库绝对路径。这里的Driver={Microsoft Access Driver (*.mdb)}必须和 ODBC 管理器里显示的驱动名一字不差,包括括号和空格。驱动名写错,报的是“找不到驱动程序”,而不是“文件不存在”,排查时容易误判。
<% String url = "jdbc:odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ=" + dbpath; Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // 加载 JDBC-ODBC 桥 Connection conn = DriverManager.getConnection(url); %>Class.forName这一行的作用是触发驱动类的静态初始化块,把驱动注册到DriverManager。JDBC 4.0 之后支持 SPI 自动发现,理论上可以省略,但 JDBC-ODBC 桥属于老式驱动,显式加载更稳妥。DriverManager.getConnection(url)只传了一个参数,因为 Access 数据库通常没有用户名密码;如果 mdb 设置了数据库密码,需要写成getConnection(url, user, password),但注意这是数据库级密码,不是文件打开密码,两者不是一回事。
3.3 Statement 与 ResultSet 的参数含义
创建 Statement 时用了两个参数:ResultSet.TYPE_SCROLL_SENSITIVE和ResultSet.CONCUR_UPDATABLE。前者表示结果集可滚动且对底层数据变化敏感,后者表示结果集可更新。对于只是select展示的场景,这两个参数其实不是必需的,用无参的createStatement()也能跑。但原代码这么写,说明作者可能预留了翻页或就地更新的意图。
<% Statement stmt = conn.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, // 结果集可前后滚动 ResultSet.CONCUR_UPDATABLE // 结果集可更新 ); String sql = "select * from username"; ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { out.print("用户名:" + rs.getString("uid")); out.print(" 密码:" + rs.getString("pwd") + "<br>"); } %>需要提醒的是,JDBC-ODBC 桥对CONCUR_UPDATABLE的支持并不完整,某些驱动组合下会降级为只读,甚至抛异常。如果只是读取展示,我一般会改成createStatement(),少一层兼容性风险。rs.getString("uid")按列名取值,列名大小写不敏感,但表里字段名如果是UID而 SQL 写uid,在 Access 里通常没问题,换到其他数据库就要注意。
3.4 资源关闭与异常处理
原代码在try块末尾依次rs.close()、stmt.close()、conn.close(),顺序是对的:先关结果集,再关语句,最后关连接。但问题在于,如果executeQuery抛异常,这三个close都不会执行,连接就泄漏了。Tomcat 连接池没配的情况下,泄漏几次之后应用就会卡死。
<% Connection conn = null; Statement stmt = null; ResultSet rs = null; try { // ... 连接与查询 } catch (Exception e) { out.print("数据库连接错误:" + e.getMessage()); } finally { // 常见做法:在 finally 里按反序关闭,每个 close 单独 try-catch try { if (rs != null) rs.close(); } catch (Exception ignored) {} try { if (stmt != null) stmt.close(); } catch (Exception ignored) {} try { if (conn != null) conn.close(); } catch (Exception ignored) {} } %>这是这个项目最值得改造的地方之一。原代码把关闭放在 try 末尾,属于“顺利路径才关闭”,一旦出错就漏。改成finally块之后,无论查询成功还是失败,资源都能释放。每个close单独包 try-catch 是因为关闭本身也可能抛异常,不能因为关结果集失败就不关连接。
4. 避坑与排查:连接失败时先看这五条
4.1 报“找不到驱动程序”但驱动明明装了
现象:页面输出java.sql.SQLException: [Microsoft][ODBC 驱动程序管理器] 未找到数据源名称并且未指定默认驱动程序。原因通常不是驱动没装,而是 JVM 位数和 ODBC 驱动位数不匹配。64 位 Tomcat 调的是 64 位 ODBC 管理器,而很多 Office 安装的是 32 位驱动。解决:在C:\Windows\SysWOW64\odbcad32.exe(32 位)和C:\Windows\System32\odbcad32.exe(64 位)里分别确认驱动是否存在,让 Tomcat 的位数和驱动位数对齐,或者换用 UCanAccess 这类纯 Java 驱动。
4.2 getRealPath 返回 null
现象:连接字符串里的DBQ=后面是空的,报“文件名无效”。原因:应用以 WAR 包形式部署且容器未解压,getRealPath拿不到磁盘路径。解决:在 Tomcat 的server.xml里确认unpackWARs="true",或者把 mdb 放到容器外固定目录,JSP 里直接写绝对路径,绕开getRealPath。
4.3 表名或字段名对不上
现象:连接成功但查询报“找不到表”或“找不到列”。原因:Access 里建的表叫username,但字段可能被建成了u_id、password,或者表名带了空格。解决:打开 mdb 确认表结构和字段拼写,SQL 里的列名和rs.getString里的列名必须与表定义一致。Access 对中文表名和字段名也支持,但编码不一致时容易出乱码,建议统一用英文。
4.4 中文乱码
现象:页面显示的用户名是问号或乱码。原因:JSP 页面声明了charset=gb2312,但 Access 数据库里存的是另一种编码,或者 Tomcat 的 URI 编码配置不一致。解决:先确认 mdb 里数据本身是否正常,再检查 JSP 的pageEncoding和contentType是否统一。老项目里 gb2312 和 UTF-8 混用是常态,常见做法是页面统一改 UTF-8,数据库连接不做额外转码,让 JDBC-ODBC 桥按系统默认编码处理。
4.5 JDK 升级后代码直接崩
现象:换到 JDK 11 或更高版本,Class.forName("sun.jdbc.odbc.JdbcOdbcDriver")抛ClassNotFoundException。原因:JDBC-ODBC 桥在 JDK 11 中被移除。解决:要么把 JDK 降回 8,要么换驱动。换驱动的话,UCanAccess 是纯 Java 方案,不需要 ODBC,依赖几个 jar 包,连接串改成jdbc:ucanaccess://路径,代码结构基本不用大改。这是老项目迁移时最实际的一条路。
5. 二次开发改造:把演示代码变成能维护的数据访问层
原代码把连接、查询、输出全塞在一个 JSP 里,作为教学演示没问题,但稍微加两个页面就会重复三遍连接逻辑。二次开发的第一步不是加功能,而是把数据库访问抽出来。我一般会建一个简单的工具类,把连接和关闭封装掉,JSP 只负责取数据和渲染。
// DBUtil.java —— 放在 WEB-INF/classes 下或打成 jar import java.sql.*; public class DBUtil { // 从 ServletContext 传入的绝对路径构造连接 public static Connection getConnection(String dbAbsolutePath) throws Exception { String url = "jdbc:odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ=" + dbAbsolutePath; Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); return DriverManager.getConnection(url); } public static void close(ResultSet rs, Statement stmt, Connection conn) { try { if (rs != null) rs.close(); } catch (Exception ignored) {} try { if (stmt != null) stmt.close(); } catch (Exception ignored) {} try { if (conn != null) conn.close(); } catch (Exception ignored) {} } }JSP 里就变成先拿路径、再调工具类、最后在finally里统一关闭。这样改的好处是,以后要换 UCanAccess 或者换数据库,只改DBUtil一个地方,页面不用动。参数上唯一需要注意的是dbAbsolutePath必须由调用方通过application.getRealPath算好传进来,工具类不依赖 Servlet API,方便单独测试。
第二个改造点是 SQL 参数化。原代码的select * from username没有用户输入,暂时安全,但一旦加上按用户名查询,拼接字符串就会引入注入风险。Access 的 JDBC-ODBC 桥对PreparedStatement支持有限,常见做法是用PreparedStatement加?占位符,如果驱动报不支持,再退回手工转义单引号。改造时先用一条带参数的查询验证驱动行为,别等上线才发现占位符不生效。
第三个改造点是配置外置。把 mdb 路径、驱动名写进web.xml的<context-param>或者一个 properties 文件,JSP 启动时读取。这样换机器部署时不用改代码,只改配置。验证方法很简单:把 mdb 挪到另一个目录,只改配置不改 JSP,页面还能正常输出,说明抽离到位了。
最后说一个我踩过的坑:改完工具类后忘了把编译好的.class放到WEB-INF/classes对应包路径下,Tomcat 启动不报错,访问页面才抛ClassNotFoundException。从那以后我每次改完 Java 类,都强制走一遍“编译 → 确认 class 文件落在 WEB-INF/classes → 重启 Tomcat → 访问页面看输出”的流程,不再靠热部署的玄学。希望帮到你。
本文还有配套的精品资源,点击获取