简介:这是一份面向Java Web初学者的JSP与Access数据库连接教程,适用于小型项目开发或课程实践。文档以创建test.mdb数据库并读取username表数据为例,详细讲解了从设计数据表(包含uid与pwd两个文本字段)、将数据库文件部署到Tomcat服务器指定目录,到编写JSP页面实现连接查询的完整流程。重点展示了使用JDBC-ODBC桥驱动建立连接、通过应用路径转换获取数据库物理路径、执行SQL语句、遍历结果集以及正确关闭资源的关键代码,并给出了异常处理的排错思路。资源为单个doc文档,共31KB,内容紧凑,附带可直接运行的JDBCAccess.jsp示例,注释清晰,便于读者对照练习。已有238人学习下载,适合正在学习JSP数据库编程或需要快速实现Access数据交互的开发者参考。
1. JSP连接ACCESS数据库:这个“老古董”组合为什么还值得会
最近带一个课设小组,题目是“JSP个人信息展示页面”,后台数据库被指定成Access。他们兴冲冲写了Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"),结果页面一开就报错,机房老师看了一眼说“JDK8以上已经没有这个驱动了”。这就是JSP连接ACCESS数据库的现状:技术很老,但课设、毕设、老单位内部小系统里还在用。它解决的问题很直接:让Java Web页面读写一个小型文件型数据库,完成增删改查。适合刚学JavaWeb的初学者、需要快速交付的课设项目,以及维护传统JSP项目打包war的老程序员。核心结论先行:别再碰ODBC桥,换用UCanAccess这类纯JDBC驱动,这条路能少踩八成坑。
2. 选对连接方案:JDBC-ODBC桥退役后,UCanAccess成了最省心的路
2.1 三种连接Access的方式与选型对比
在JSP里连接ACCESS数据库,按我的经验只有三条路:JDBC-ODBC桥、UCanAccess、微软官方Access数据库驱动。如果你在网上搜到2005年左右的教程,大多是第一种。原理是Java先通过ODBC读取Access文件,页面代码里常见的写法是:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); String url = "jdbc:odbc:test";这段代码在Oracle JDK 8里还能勉强跑,到JDK 11就彻底没了。更麻烦的是你在Windows上还要先建一个“ODBC数据源”,一旦系统是64位而机器里装了32位驱动,就会看到“64位引擎不支持DBC数据”之类的报错。所以ODBC桥这条老路,我把它定义为“只适合考古”。
UCanAccess则不一样。它是一个纯Java实现的JDBC驱动,底层用Jackcess库直接解析.mdb/.accdb文件,不依赖Windows ODBC,所以你在Windows、Linux、macOS上都能用。对JSP来说,只需要把几个jar包丢进项目的WEB-INF/lib,然后用标准JDBC接口操作即可。
| 方案 | 原理 | 依赖Windows | 维护状态 | 适合场景 |
|---|---|---|---|---|
| JDBC-ODBC桥 | 通过ODBC访问Access | 是 | JDK8后移除 | 考古、老教程 |
| UCanAccess | 纯Java解析Access | 否 | 活跃 | JSP课设、小站点、数据迁移 |
| 微软Access数据库驱动 | 通过ODBC访问Access | 是 | 维护但受限 | 仅限Windows下专用程序 |
选型结论很清楚:UCanAccess。Access本身有一些“玄学”特性,比如不支持存储过程、不支持复杂事务,但做课设和个人信息展示绰绰有余。如果你已经在用Access数据库64位系统驱动程序,也不要紧张,它和JDBC桥是两回事,后续统一用UCanAccess替换即可。
2.2 在IDEA新建JSP项目并引入UCanAccess依赖
假设你用的是IDEA。新建项目时选“Java Enterprise”,项目模板选“Web Application”,服务器勾上Tomcat。如果选的是Maven,就在pom.xml里加依赖。我一般固定用UCanAccess 5.0.1,这是一个成熟版本,网上的报错案例也最多,搜起来方便。
<dependency> <groupId>net.sf.ucanaccess</groupId> <artifactId>ucanaccess</artifactId> <version>5.0.1</version> </dependency>逻辑说明:Maven会把UCanAccess及其依赖全部下载进来,包括jackcess、commons-lang3、hsqldb。如果你们公司不许用Maven,需要手动下载jar包并一个个放进WEB-INF/lib。“传统jsp项目打包war”的场景里经常有人只放一个ucanaccess.jar,运行时就报ClassNotFoundException: com.healthmarketscience.jackcess.Database,这就是缺依赖了。
还需要提醒一点:Access数据库文件是文件型数据库,不像MySQL、Oracle那样有个服务端。你要保证运行环境里有这个文件,并且连接串里的路径能读到它。常见陋习是先把Access文件放到桌面或者网盘里,再写死绝对路径,结果一换机器就翻车。我在本地习惯建一个db目录,把数据库文件放进去,后面部署时再外置路径。
2.3 用UCanAccess写第一个连接:代码模板与参数说明
依赖进好后,先写一个工具类。这几年维护这类老项目,我的习惯是第一件事就是把连接逻辑从JSP页面里抽出来,否则每个页面都写一遍Class.forName,乱得没法看。
package com.example.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { static { try { // 加载UCanAccess纯Java驱动 Class.forName("net.ucanaccess.jdbc.UcanaccessDriver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { // Windows下也建议用正斜杠,避免反斜杠转义问题 String dbPath = "D:/data/users.accdb"; String url = "jdbc:ucanaccess://" + dbPath + ";showSchema=true;charSet=UTF-8"; return DriverManager.getConnection(url, "admin", ""); } }逻辑说明:静态块负责加载驱动。URL格式是jdbc:ucanaccess://后跟文件绝对路径,注意路径里的分隔符用/而不是\\,JDBC URL里的反斜杠是转义字符,写错会连不上。showSchema=true表示把Access内部系统表也暴露出来,平时查询可以不加。charSet=UTF-8在连接串里指定字符集,配合页面UTF-8可以避免中文乱码。Access文件如果没设密码,用户填admin、密码留空即可;如果设了密码,需要额外拼接;pwd=密码参数。
这里再补一个格式差异:Access 2003之前的.mdb是旧格式,2007之后的.accdb是新格式,UCanAccess两者都支持。如果数据库是老师给的.mdb,直接用;如果是自己新建,建议另存为.accdb,新格式的锁文件处理更稳。别混着用“Access数据库64位系统驱动程序”来理解UCanAccess,UCanAccess不需要你在系统层面装任何东西。
测试连接时,可以在main方法里写两行:
Connection conn = DBUtil.getConnection(); System.out.println(conn == null ? "fail" : "ok");看到“ok”说明JSP连接ACCESS数据库的通道已经建立。接下来可以进入真正的页面开发。
3. 在JSP页面里完成增删改查:一个可抄作业的用户信息管理模块
3.1 数据库准备:建表与插入测试数据
先建库。打开Access,新建空数据库,保存为users.accdb。Access不会像MySQL那样给你命令行,你需要用“创建→查询设计→SQL视图”执行SQL。这里有个反直觉的点:Access的SQL语句不完全兼容MySQL、Oracle。我们建一张用户表:
CREATE TABLE users ( id COUNTER PRIMARY KEY, name TEXT(20) NOT NULL, age INTEGER, remark TEXT(100) );逻辑说明:COUNTER是Access里的自增字段,等价于MySQL的AUTO_INCREMENT。表名用users而不是user,因为user在Access里也是保留字,容易让SQL语句报错。TEXT在Access里默认有长度限制,写20够用。执行后再插入三条测试数据:
INSERT INTO users (name, age, remark) VALUES ('张三', 22, '测试数据'); INSERT INTO users (name, age, remark) VALUES ('李四', 25, '来自Access'); INSERT INTO users (name, age, remark) VALUES ('王五', 30, 'JSP连接');注意:如果你打算把Access文件放到项目目录下,保存后的users.accdb要复制到web/db/目录下,并在连接串里改成对应路径。相对路径方案我会在部署那一章展开,现在先继续写代码。
3.2 查询列表页:连接、遍历ResultSet与关闭资源
JSP页面直接用Java代码块连数据库,看起来丑,但这是课设和传统JSP项目最常见的形态。不要用Statement拼SQL,直接用PreparedStatement,一是防access注入,二是参数绑定更省心。创建一个user_list.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ page import="java.sql.*, com.example.db.DBUtil" %> <html> <head><title>用户列表</title></head> <body> <table border="1"> <tr><th>ID</th><th>姓名</th><th>年龄</th><th>备注</th></tr> <% Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); ps = conn.prepareStatement("SELECT id, name, age, remark FROM users"); rs = ps.executeQuery(); while (rs.next()) { %> <tr> <td><%= rs.getInt("id") %></td> <td><%= rs.getString("name") %></td> <td><%= rs.getInt("age") %></td> <td><%= rs.getString("remark") %></td> </tr> <% } } finally { if (rs != null) rs.close(); if (ps != null) ps.close(); if (conn != null) conn.close(); } %> </table> </body> </html>逻辑说明:<%@ page import %>把DBUtil引入页面。DBUtil.getConnection()每次请求新建连接,这是合理的,因为Access是文件型数据库,并发不高,临时连接比长期占用更安全。finally里的资源关闭是必须的,尤其是UCanAccess,连接不关闭会导致Access锁文件一直存在,后续写操作报错。
页面编码三件套别漏:contentType="text/html; charset=UTF-8"、pageEncoding="UTF-8"、数据库连接串里的charSet=UTF-8。漏任何一个,中文就变成乱码。这是我在课设里翻车最多的点。如果不需要对表结构做反射,可以不设置showSchema=true,ResultSet里就不会出现系统表,查询更干净。
3.3 新增和删除:PreparedStatement的写法与防注入
列表页解决后做新增。新增通常从一个表单页提交到自己或专门的Servlet。这里写一个混合JSP的例子,重点展示PreparedStatement的写法。创建user_add.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ page import="java.sql.*, com.example.db.DBUtil" %> <% request.setCharacterEncoding("UTF-8"); String name = request.getParameter("name"); String ageStr = request.getParameter("age"); String remark = request.getParameter("remark"); if (name != null && !name.trim().isEmpty()) { Connection conn = null; PreparedStatement ps = null; try { conn = DBUtil.getConnection(); String sql = "INSERT INTO users(name, age, remark) VALUES(?, ?, ?)"; ps = conn.prepareStatement(sql); ps.setString(1, name); ps.setInt(2, Integer.parseInt(ageStr)); ps.setString(3, remark); ps.executeUpdate(); } finally { if (ps != null) ps.close(); if (conn != null) conn.close(); } response.sendRedirect("user_list.jsp"); } %> <html> <head><title>新增用户</title></head> <body> <form method="post"> 姓名:<input type="text" name="name"><br> 年龄:<input type="text" name="age"><br> 备注:<input type="text" name="remark"><br> <button type="submit">保存</button> </form> </body> </html>逻辑说明:request.setCharacterEncoding("UTF-8")必须放在任何读取request参数之前,否则POST中文必乱。PreparedStatement的三个占位符用?占位,然后通过setString和setInt绑定。这样即使参数里有单引号或SQL关键字,也不会拼进SQL语句,Access注入的老套路就失效了。
这个写法同样适用于删除和修改:删除就是DELETE FROM users WHERE id = ?,修改就是UPDATE users SET name = ?, age = ? WHERE id = ?,参数绑定方式一致。Integer.parseInt(ageStr)在年龄为空或不是数字时会抛NumberFormatException,课设可以先不管,生产环境建议加上校验,否则页面直接500。我建议至少用try包一层,并给用户返回一个可读的提示。
3.4 把连接逻辑收进DBUtil工具类
页面代码多了之后会发现两个问题:一是每个JSP都写Connection conn = null这些重复代码;二是连接串改个路径得翻遍所有页面。解决办法是扩展DBUtil,把关闭连接的方法也放进去。我在正式动手前习惯把这一层做好,后面写增删改查会快很多。补上close方法:
public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt != null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} } }逻辑说明:Statement是PreparedStatement的父类,所以close方法参数声明成Statement,新增、删除、查询都能用。忽略关闭异常,是为了避免资源关闭失败盖过业务异常。关闭顺序必须是ResultSet、Statement、Connection,虽然现代JDBC驱动大多会自动处理,但按标准顺序关最稳。
有了DBUtil之后,JSP里的finally块可以简化成一行:DBUtil.close(conn, ps, rs);。这样至少不会因为漏关连接导致Access文件被锁。到这里,一个支持查询、新增、删除的JSP+ACCESS小模块已经能跑了。下一章集中讲那些让人头皮发麻的坑。
4. JSP连接ACCESS的避坑指南:从驱动缺失到中文乱码的5个高频问题
4.1 64位系统报“未找到Microsoft Access驱动程序”或“无法加载ODBC”
现象:新手照着老教程写Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"),Tomcat直接抛ClassNotFoundException;或者用ODBC数据源时提示“指定的DSN包含驱动程序和应用程序之间的体系结构不匹配”。
原因:JDK 8之后JDBC-ODBC桥被移除,而且你大概率是64位系统,却装了32位Access驱动,于是系统一直提示“64位引擎不支持DBC数据”。机房电脑最常见这个组合。
解决:放弃ODBC方案,换成UCanAccess。驱动类名写成net.ucanaccess.jdbc.UcanaccessDriver,连接串写成jdbc:ucanaccess://...,一步到位。不要混用“Access数据库64位系统驱动程序”和JDBC桥,那只是ODBC引擎的一部分,和纯Java的UCanAccess不是一回事。
4.2 Access文件被Lock文件占用,写操作报错
现象:第一次插入成功,第二次执行executeUpdate()报错,大概意思是“Could not lock file”或者“Unable to open database”。
原因:UCanAccess以文件方式打开Access,如果上次连接没有正常关闭,会在数据库目录下生成一个users.laccdb锁文件;或者你调试时用Access程序手动打开了同一个文件。
解决:先关掉Access程序,删除users.laccdb,然后检查代码里finally是否关闭了Connection。我自己的经验是,数据库文件放到网络共享目录会频繁锁,放在本地磁盘最稳定。如果你只是做只读展示,可以把连接参数改成只读模式,但课设一般用不上,只要每次用DBUtil.close及时释放锁就够了。
4.3 数据库路径是中文或带空格导致连接失败
现象:本地开发一切正常,把项目拷到用户名是中文的机器上,启动后报Database file not found。
原因:UCanAccess对URL编码的处理在某些JDK版本上遇到中文路径容易出问题;Windows路径里的反斜杠也被当成转义吃掉。
解决:数据库文件放到纯英文、无空格的路径,比如D:/data/users.accdb。如果非要放中文目录,在Java代码里用URLEncoder.encode处理路径,或者通过new File(...).toURI().toASCIIString()拼URL。我一般不会为了一台机器给自己加戏,直接改路径最简单。这个坑也解释了为什么生产部署时一定要把路径外置,而不是写死在页面里。
4.4 UCanAccess与JDK版本不匹配报UnsupportedClassVersionError
现象:Tomcat启动正常,一旦访问JSP页面就抛UnsupportedClassVersionError: net/ucanaccess/jdbc/UcanaccessDriver has been compiled by a newer version...。
原因:UCanAccess的编译版本高于当前JDK运行版本。比如JDK 8环境用了为JDK 11编译的UCanAccess包。
解决:先确认Tomcat用的JDK版本,再选匹配的UCanAccess版本。JDK 8环境装UCanAccess 5.0.1,JDK 11以上用更新的版本,反之亦然。注意IDEA里虽然设置了某个JDK,但Tomcat运行时用的可能是另一个,去Tomcat的bin目录执行java -version确认。这个报错是JDK自带的安全机制,不要试图绕过,换jar包最靠谱。
4.5 JSP页面中文乱码:页面编码与数据库编码步调一致
现象:页面上的中文变成???,或者从数据库读出的中文全是乱码。
原因:三种编码不一致——JSP文件本身的保存编码、HTTP响应的字符集、数据库连接串里的字符集。Access内部按Unicode存储,但连接时没有指定字符集,UCanAccess可能会按系统默认编码读。
解决:JSP头写成<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>,表单接收前执行request.setCharacterEncoding("UTF-8"),连接串里的charSet=UTF-8不能省。这三个地方对齐后,中文就是透明的。如果你的项目里有Tomcat配置了请求编码过滤器,那就不用每个页面都写,但老项目很少配,还是手动设置比较稳。
5. 从课设到部署:把JSP+ACCESS项目打包WAR并发布到Tomcat
5.1 数据库路径外置:用配置文件与相对路径定位Access文件
很多人在IDEA里把Access文件放在web/db/下面,然后用相对路径拿不到,因为JSP的工作目录不一定在项目根目录。部署后更麻烦,路径一变就找不到文件。常见的做法是把Access路径外置到一个配置里。我给你一个我经常用的方案:用环境变量。代码放在DBUtil里:
public static String getDbPath() { String dbPath = System.getenv("ACCESS_DB_PATH"); if (dbPath == null || dbPath.trim().isEmpty()) { throw new RuntimeException("请设置环境变量 ACCESS_DB_PATH"); } return dbPath; }逻辑说明:System.getenv读取的是JVM所在进程的环境变量。在IDEA里运行,给Tomcat的运行配置加一个ACCESS_DB_PATH环境变量;在服务器上,写进Tomcat的bin/setenv.sh或者Windows的系统环境变量。这样代码和数据库文件地址彻底解耦,换机器不用改代码。如果你不喜欢环境变量,也可以用配置文件:把jdbc.properties放在WEB-INF/classes下,用ClassLoader.getResourceAsStream("jdbc.properties")读取。注意部署后war包里的WEB-INF/classes可能是只读的,后期改配置不方便,所以环境变量方案更省事。
5.2 用IDEA打包WAR并部署到Tomcat
传统JSP项目打包war,IDEA里的操作路径是:File→Project Structure→Artifacts→ 点+→Web Application: Archive→ 命名后确认。然后Build→Build Artifacts→ 选择war。这个动作会生成一个*.war文件,通常在项目的out/artifacts目录下。War其实是标准ZIP格式,你可以用压缩工具打开看看,里面应该有WEB-INF/lib、WEB-INF/classes和JSP页面。
部署步骤一句话:把war文件复制到Tomcat的webapps目录,启动Tomcat,它会自动解压并部署。注意Tomcat版本要和JDK匹配,Tomcat 9配JDK 8或11。如果是用Maven,可以在pom.xml里配置maven-war-plugin,但课设直接用IDEA打包更快。部署后访问地址是http://localhost:8080/项目名/user_list.jsp。如果Tomcat的webapps下已经有了同名目录,先删除旧目录再放新war,避免Tomcat解压时把旧文件混进来。
5.3 部署后路径与权限排查
部署后最常见的报错是:“Database file not found”或者“Unable to open database file”。首先检查ACCESS_DB_PATH是否真的指向数据库文件,路径里不要带前后空格。其次检查权限:Windows下要给数据库文件所在目录加“Users”可读写权限;Linux下用chmod 666 users.accdb和chmod 777 /var/data。
为什么要给目录权限?因为Access文件型数据库写操作不仅写数据文件,还会在同目录创建锁文件,目录不可写,锁文件创建失败,事务就挂了。
另一个容易忽略的点:如果Access文件被放进war包里,也就是WEB-INF内部,运行时可能读不到或写入失败。Tomcat解压war后放在临时目录,重启后路径可能变,而且应用内部资源不应该承受外部写入。所以生产环境务必把Access文件放到应用外部,比如/data或者D:/data。这一步是很多人把课设改成“可部署项目”时踩得最狠的坑。我一般会在DBUtil里加一个启动自检,让日志打印真实路径:
System.out.println("[DBUtil] dbPath=" + dbPath);看到日志里的路径和你预期一致,再继续往下查。
6. 连接性能与数据迁移:Access做后台时的三个务实技巧
6.1 不要照搬MySQL连接池
UCanAccess是文件型数据库,连接池并不总是帮手。我踩过坑:把Tomcat DBCP连接池配给UCanAccess,平时查询没问题,一旦多个连接同时写,Access产生文件级锁,页面卡死。课设和小型系统并发很低,每次请求新建连接再关闭,完全够用。如果确实想复用连接,可以用单例Connection,但写操作时要做好同步,或者干脆保持“请求-关闭”的模式。
6.2 迁移到MySQL的快捷路径
Access不适合做正式生产数据库,数据量大了会明显拖慢。如果想迁移,用Java逐行读Access然后写入MySQL,代码逻辑很简单:从Access查出ResultSet,再往MySQL的PreparedStatement里绑定。也可以用Access自带的“外部数据→ODBC数据库”导出功能,但需要先在目标库建好表。迁移前记得备份.accdb文件,复制一份到安全目录即可完成备份。
6.3 用main方法自检连通性
最后推荐一个务实技巧:在项目里保留一个main方法,专门测试连接状态。部署后先执行它,能确认驱动、路径、编码、权限都正常,再打开页面。代码很短:
public class CheckAccess { public static void main(String[] args) throws Exception { Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT COUNT(*) AS total FROM users"); ResultSet rs = ps.executeQuery(); if (rs.next()) { System.out.println("ok, users count=" + rs.getInt("total")); } rs.close(); ps.close(); conn.close(); } }逻辑说明:直接运行这个类,如果打印出ok, users count=3,说明连接没问题;如果报错,根据异常类型就能定位是路径、驱动还是权限问题。比打开浏览器看200错误页高效得多。我接手老项目的第一天就是先干这件事。这个方法也适合做完课设验收前自检,省得答辩时当着老师的面翻车。希望帮到你。
本文还有配套的精品资源,点击获取