ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JSP+MVC+MySQL图书购物系统:从环境搭建到核心代码实战解析

JSP+MVC+MySQL图书购物系统:从环境搭建到核心代码实战解析 简介这是一份基于JSPMVC模式与MySQL的网上图书购物系统课程设计源码包面向具备Java Web基础的学习者或进阶开发者可用作毕业设计、课程设计、大作业及工程实训的参考项目。包内共有76个文件包含14个JSP页面、12个Java源文件及对应class文件另有SQL数据库脚本、CSS样式表、XML配置文件等整体压缩包约47.8MB目录结构清晰便于按模块阅读与检索。项目完整覆盖用户注册登录、图书分类浏览、关键词检索、购物车管理、订单生成等核心流程并附带课程设计项目展示视频与README文档可帮助理解JDBC数据库连接、MVC分层架构及数据表设计等关键知识点。目前已有136人学习下载对正在筹备图书商城类项目、希望参考完整调用链与排错思路的开发者很有价值。需要注意的是资料定位为参考代码适合有一定编程基础并能自行调试、扩展功能的学习者使用。1. 基于JSPMVCMySQL的图书购物系统先看清这个压缩包里到底是什么做JavaWeb课程设计的人多半经历过这种时刻网上找到一个图书购物系统题目的zip解压以后发现源码直接堆在根目录没有说明文件没有SQL脚本跑起来全是报错。这套基于JSPMVC模式MySQL的图书购物系统属于少见的完整类型——压缩包里是能直接导入Eclipse的工程带数据库文件mobiledatabase.sql还有课程设计展示视频和README。对于需要交课设、做大作业或者拿毕设练手的人来说它能解决一个很实际的问题让你在有限时间里看懂一个电商类Web项目的分层写法把图书展示、用户登录、购物车、下单这些模块的核心逻辑跑通并改造成自己的版本。前提是你至少有JavaWeb基础能读代码也愿意动手调试。下面我按自己拆这类资源的习惯从工程结构一路讲到跑通和改代码时最容易翻车的地方。2. 工程结构与MVC分层登录、查书、加购的请求链路走一遍2.1 压缩包里的文件Eclipse工程、SQL脚本、演示视频各是什么拆开javaweb-jsp--CourseDesign.zip第一眼看到的目录结构与常见Eclipse JavaWeb工程一致.settings、.classpath、.project是Eclipse用来识别项目的元数据src/main下面是源码目录build/classes是编译输出目录mobiledatabase.sql是数据库脚本剩下一个视频和README。这套结构说明一件事它不是用Maven建的而是传统Eclipse Dynamic Web Project。对于课程设计来说这反而是优势因为不用等Maven下载依赖JDK和Tomcat配好就能跑。文件清单可以用下表概括文件/目录作用使用时机.project/.classpath/.settingsEclipse工程定义与编译配置导入IDE时自动读取src/main/javaJava源码含Servlet、DAO、Bean、工具类阅读与修改src/main/webappJSP页面、静态资源、web.xml改页面mobiledatabase.sqlMySQL建库建表脚本项目启动前执行build/classes编译后的class文件一般不看Eclipse会自动覆盖课程设计-项目展示视频.mp4演示效果与答辩参考初次接触时快速过一遍我一般建议先花五分钟看README再看视频最后才碰代码。理由很简单视频能告诉你系统最终长什么样README会交代部署前提这两个信息到位了后面读代码会快很多。别一上来就翻Servlet那样容易被细节带偏。2.2 MVC三个角色落到代码位置Model、View、Controller这个项目的技术选型是JSPServletJavaBeanDAO属于典型的JSP版MVC。理解它的关键是把三个角色和你看到的包名对应起来Model层的职责是数据模型与业务规则在工程里对应com.bookstore.bean包下的实体类和com.bookstore.dao包下的数据访问类。Bean里的User、Book、Order这些类字段基本和数据库表一一对应DAO类负责写SQL比如UserDAO里放findByUsernameAndPassword这类方法BookDAO里放findAll、findById。View层就是webapp目录下的JSP页面。注意一个细节页面里不应该出现大段Java脚本数据展示靠EL表达式和JSTL标签完成比如${loginUser.username}这种写法。如果某个页面里写了一堆% for(...) %说明它的分层已经乱了你改造时可以顺手把逻辑挪到Controller里。Controller层是Servlet路径上通常叫com.bookstore.servlet承载所有请求入口。传统做法是在web.xml里为每个Servlet配置url-pattern这套工程也可能直接在Servlet上用WebServlet注解两者效果一样只是注解省了web.xml里的一段配置。选型理由也很现实课程设计评分看重的就是分层是否清晰、数据库设计是否合理、流程是否完整。用Servlet做控制器虽然笨但每一步请求处理都看得见答辩时讲链路比讲SpringMVC的DispatcherServlet容易得多。2.3 登录链路走查从表单到Session到重定向以登录为例把一条完整请求链路串起来你就知道这套MVC是怎么协作的了。用户在login.jsp输入用户名密码表单提交到/login路径Tomcat根据web.xml或注解找到LoginServlet它的doPost方法开始处理请求。// LoginServlet.java 登录控制器以MVC模式组织的核心入口 WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 处理POST中文参数不设置这个表单里的中文会变乱码 request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); UserDAO dao new UserDAO(); User user dao.findByUsernameAndPassword(username, password); if (user ! null) { // 登录成功把用户对象放Session后续页面判断登录状态都靠它 request.getSession().setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /bookList); } else { // 登录失败带回提示信息转发回登录页 request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这里有三个参数值得注意request.getParameter取的是表单控件的name属性所以login.jsp里的输入框必须分别叫username和passwordsendRedirect是重定向浏览器地址会变成/bookList刷新页面不会重复提交表单而失败时的forward是转发地址栏不变request域里的msg还能在login.jsp上用EL显示出来。这一段配合Session的工作方式可以补充一句登录成功后的loginUser存入session后续JSP页面里判断${empty loginUser}就能决定是显示欢迎xxx还是去登录。购物车加购、下单时判断用户是否登录走的也是同一个Session对象。链路读完你再看图书列表和加购两个请求会发现结构完全一致JSP提交请求Servlet收参数调DAO返回结果后跳转或转发。3. 环境搭建与数据库导入JDK、Tomcat、MySQL的版本选择和落地3.1 版本选型为什么是JDK 8 Tomcat 8.5 MySQL 5.7老项目最怕的不是代码本身而是环境不兼容。这套JSP项目来源是课程设计代码基于javax.servlet命名空间编写所以Tomcat版本是第一道门槛。Tomcat 10起Servlet API从javax.servlet迁移到了jakarta.servlet直接导致旧代码编译报错或运行期NoClassDefFoundError。最省事的选择是Tomcat 8.5或9.0这两个版本对javax兼容性最好也是课程设计场景下的标准配置。JDK方面1.8足够支撑JSPServlet开发而且与Tomcat 8.5搭配最稳。别为了赶新潮装JDK 17或21那些版本配合老Tomcat反而可能遇到编译级别不匹配的问题。MySQL的话5.7与项目SQL脚本的兼容性最好8.0也能用但需要在驱动名和连接参数上做适配这个坑我会在第5章详细讲。Eclipse推荐用Eclipse IDE for Enterprise Java版本内置了Web开发所需的插件省去单独装WTP的麻烦。我这里整理了一个版本参考表组件推荐版本说明JDK1.88u202稳定且兼容javax包Tomcat8.5 或 9.0严禁直接用10包名迁移不兼容MySQL5.7驱动名与连接参数最省心EclipseEnterprise Java版自带WTP、Maven支持如果你机器上只有MySQL 8.0也不影响大局但要把驱动类换成com.mysql.cj.jdbc.Driver连接URL加上serverTimezoneAsia/Shanghai否则启动时会有时区异常。我建议新手优先装5.7减少变量。3.2 导入Eclipse与执行SQL脚本跑起来的两个前置动作环境齐了之后第一步是把工程导入Eclipse。操作路径固定菜单File → Import → Existing Projects into Workspace → 选择zip解压后的根目录 → Finish。导入成功后如果项目名旁边有红色感叹号打开Problems视图看具体报错多数是JDK版本或Tomcat运行环境没匹配上。右键项目 → Properties → Java Build Path把JRE System Library改成你本地的JDK 1.8即可。第二步是建库。打开MySQL命令行或Navicat执行SQL脚本。命令行方式如下mysql -u root -p mobiledatabase.sql执行前先确认脚本里的建库语句。用文本编辑器打开mobiledatabase.sql看前几行通常是CREATE DATABASE或者USE语句记下数据库名后面改JDBC连接要用。如果脚本只有建表语句没有建库语句需要手动先建一个同名库mysql -u root -p CREATE DATABASE booksystem DEFAULT CHARACTER SET utf8; USE booksystem; SOURCE /path/to/mobiledatabase.sql;提示SQL脚本执行时最容易报错的点就是字符集不一致。建库时统一指定utf8能避免后面中文写入后查出来是问号。如果你用的是Navicat导入前同样要确认目标数据库字符集。执行完毕后用SHOW TABLES;确认表建出来了。正常的图书购物系统至少包含用户表、图书表、订单表、订单明细表这几张核心表。3.3 改JDBC连接参数URL、用户名、密码、字符集和时钟数据库就绪后代码里唯一的硬性改动点是JDBC连接参数。常规的JavaWeb课程设计会把连接信息放在一个工具类DBUtil.java里或者放在jdbc.properties配置文件中。这套工程大概率使用的是DBUtil方式位置在com.bookstore.util包下。改法如下// DBUtil.java 数据库连接工具类 public class DBUtil { // 注意数据库名、useSSL、characterEncoding这三个参数 private static final String URL jdbc:mysql://localhost:3306/booksystem?useSSLfalsecharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { // MySQL 5.7 用这个驱动名MySQL 8.0 需改成 com.mysql.cj.jdbc.Driver Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }说明三点第一URL里的booksystem必须和SQL脚本里的库名一致这是改错频率最高的地方第二useSSLfalse是为了关闭MySQL 8默认的SSL校验不然启动项目时控制台会刷一堆SSL连接警告第三characterEncodingutf8保证写入数据库的中文不乱码。PASSWORD改成你本地MySQL的root密码如果数据库单独建了账号USER也要同步改。参数改完把项目添加到Tomcat右键项目 → Run As → Run on Server。启动完成后浏览器访问http://localhost:8080/项目名/看到登录页或图书列表页说明环境这一关过了。如果404先确认访问路径里的项目名和实际部署名是否一致——这个细节放在后面避坑章节具体说。4. 核心功能代码走读图书检索、购物车、下单的写法4.1 图书检索PreparedStatement 和 LIKE 模糊查询图书购物系统的主功能就是浏览图书和搜索图书。列表页通常会有一个搜索框往BookServlet提交keyword参数Controller调用BookDAO的查询方法。我要重点说的是DAO层写SQL的方式——用PreparedStatement而非拼接字符串这是课程设计里容易被忽略但评分老师会看的地方。// BookDAO.java 按关键词模糊查询 public ListBook findByKeyword(String keyword) throws SQLException { String sql SELECT * FROM book WHERE book_name LIKE ? OR author LIKE ?; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { // 两个占位符都传同一个关键词%表示任意字符 ps.setString(1, % keyword %); ps.setString(2, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setBookName(rs.getString(book_name)); book.setAuthor(rs.getString(author)); book.setPrice(rs.getBigDecimal(price)); book.setStock(rs.getInt(stock)); list.add(book); } } } return list; }注意LIKE语句里的%需要手动拼到参数上而不是拼进SQL模板里。这样做的核心原因是防止SQL注入如果用WHERE book_name LIKE % keyword %用户输入% OR 11 --就会把查询条件拆掉导致查出全表数据。PreparedStatement的setString会把参数当作字符串处理特殊字符自动转义虽然在这个课设项目里数据敏感性不高但养成这个习惯很值钱。另外try-with-resources写法是Java 7以后的推荐方式Connection、PreparedStatement、ResultSet都会自动关闭省写finally块。如果你的代码里还在手动conn.close()可以顺手改成这种风格答辩时也算亮点。4.2 购物车把 Session 当临时容器用购物车是这套系统逻辑最集中的地方。常见做法是把购物车对象放到Session里因为购物车本质上是当前这个用户的临时数据用Session存最贴合HTTP无状态的特点。每个用户打开浏览器访问系统服务器分配一个独立的Session购物车各放各的互不干扰。加购请求的处理逻辑大致如下// AddToCartServlet.java 加购控制器 WebServlet(/addToCart) public class AddToCartServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int bookId Integer.parseInt(request.getParameter(bookId)); HttpSession session request.getSession(); // 从Session取购物车没有则创建 MapBook, Integer cart (MapBook, Integer) session.getAttribute(cart); if (cart null) { cart new LinkedHashMap(); session.setAttribute(cart, cart); } // 查书并加入购物车 BookDAO bookDAO new BookDAO(); Book book bookDAO.findById(bookId); // merge如果书已在购物车则数量1否则数量为1 cart.merge(book, 1, Integer::sum); // 重定向到购物车页面 response.sendRedirect(request.getContextPath() /cart); } }这里的关键点是MapBook, Integer的选择Key存Book对象Value存数量。用LinkedHashMap保证插入顺序购物车页面展示时保持用户加购的顺序。merge方法是Java 8的Map默认方法等价于取旧值加1再放回去比先containsKey再put短得多。如果书已经在购物车里这行代码会自动把数量加1不用额外写分支。购物车页面会遍历这个Map把图书名、单价、数量、小计显示成表格。修改数量和删除条目也在这个页面操作对应的Servlet路径一般是updateCart和removeFromCart逻辑无非是改Map里的value或直接remove这个key。注意删除时用bookId定位key而不是遍历整个Map去匹配对象否则对象equals没重写会删不干净。4.3 下单事务订单主表与明细表的一致性写法购物车结算时会走到下单逻辑这是整个系统里唯一涉及事务的地方。订单表一般拆成两张订单主表存订单编号、下单用户、总价、下单时间订单明细表存每本书的购买数量和单价。两步操作必须同时成功否则会出现主表有订单、明细表没数据的情况这就是事务要解决的问题。// OrderServlet.java 下单事务处理 Connection conn DBUtil.getConnection(); try { // 关闭自动提交事务开始 conn.setAutoCommit(false); // 第一步插入订单主表获取生成的订单ID PreparedStatement ps1 conn.prepareStatement( INSERT INTO orders (user_id, total_price, create_time) VALUES (?, ?, NOW()), Statement.RETURN_GENERATED_KEYS); ps1.setInt(1, userId); ps1.setBigDecimal(2, totalPrice); ps1.executeUpdate(); ResultSet keys ps1.getGeneratedKeys(); int orderId 0; if (keys.next()) { orderId keys.getInt(1); } // 第二步循环插入订单明细 for (CartItem item : cartItems) { PreparedStatement ps2 conn.prepareStatement( INSERT INTO order_item (order_id, book_id, book_name, price, quantity) VALUES (?, ?, ?, ?, ?)); ps2.setInt(1, orderId); ps2.setInt(2, item.getBook().getId()); ps2.setString(3, item.getBook().getBookName()); ps2.setBigDecimal(4, item.getBook().getPrice()); ps2.setInt(5, item.getQuantity()); ps2.executeUpdate(); } // 全部成功才提交 conn.commit(); } catch (SQLException e) { // 任何一步失败回滚保持数据一致 conn.rollback(); throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn); }setAutoCommit(false)是事务开始标志commit是确认所有SQL生效rollback在异常时撤销本次全部操作。这个模式在任何JavaWeb项目里都通用——Spring里用Transactional做的是同一件事只是封装好了。下单后别忘了清空购物车session.removeAttribute(cart)。很多课设代码在这里漏掉这一步导致用户下完单购物车还在再下第二单时会把前一单的书又带进去看起来像重复下单。5. 避坑排查乱码、驱动名、Tomcat版本、SSL连接四个翻车点5.1 中文乱码页面问号与数据库问号的排查顺序现象JSP页面显示中文正常但表单提交后写入数据库的值变成???或者JSP页面本身直接显示一堆编码乱码。原因乱码有三个来源经常叠加出现。第一JSP页面文件的保存编码和pageEncoding不一致。第二POST请求参数没设置字符集Tomcat默认按ISO-8859-1解码。第三JDBC连接URL没带characterEncodingutf8数据库连接层面的字符集不对。解决按顺序排查。先看JSP头部是否写了% page pageEncodingUTF-8 contentTypetext/html;charsetUTF-8%再在Servlet里加上request.setCharacterEncoding(UTF-8)且必须在读取任何参数之前调用最后确认JDBC URL里带了characterEncodingutf8。如果三处都改了还是乱码检查MySQL表结构SHOW CREATE TABLE user;字段的charset必须为utf8或utf8mb4。这三层都一致乱码问题基本绝迹。5.2 MySQL 驱动名找不到5.7 和 8.0 的兼容差异现象项目启动后第一次访问数据库就报ClassNotFoundException: com.mysql.jdbc.Driver。原因MySQL Connector/J驱动从8.0版本开始驱动类改名成了com.mysql.cj.jdbc.Driver。如果你本地装的是MySQL 8.0并使用了配套的8.x驱动包但DBUtil里写的还是老驱动名必然找不到类。解决两种方案任选。一是把DBUtil里的Class.forName(com.mysql.jdbc.Driver)改成com.mysql.cj.jdbc.Driver同时URL加serverTimezoneAsia/Shanghai二是保持代码不动把SQL脚本执行在MySQL 5.7上。我一般是新装环境直接选5.7因为老课设代码里的SQL语法、驱动、时区设置都是按5.7来的省掉一堆适配工作。5.3 Tomcat 10 的 javax 包冲突别用最新版现象代码在Eclipse里编译不报错部署到Tomcat后启动时抛NoClassDefFoundError: javax/servlet/...或者访问页面直接500。原因Tomcat 10是分水岭Servlet官方规范包名从javax.servlet迁移到了jakarta.servlet。这套JSP项目的源码是在旧规范下写的import的都是javax路径Tomcat 10里根本没有这些类。解决换回Tomcat 8.5或9.0。这个坑在课程设计场景里非常常见因为很多人图省事下载了最新版Tomcat。识别方式也简单Tomcat 10的lib目录下是jakarta.servlet-api.jar而Tomcat 9及以前是servlet-api.jar。另外注意Eclipse里Server Runtime也要重新指向Tomcat 8.5否则改了Tomcat版本但运行环境没更新照样报错。5.4 SSL 连接错误URL 上加两个参数解决现象控制台报Communications link failure错误信息里出现SSL connection error有时不是报错而是满屏黄色警告Establishing SSL connection without servers identity verification is not recommended。原因MySQL 5.7及8.0默认开启SSL要求驱动在建立连接时会主动尝试SSL握手而本地开发环境很少配置SSL证书握手失败就抛出连接异常。这属于MySQL客户端驱动安全策略与本地环境不匹配的典型冲突。解决在连接URL上追加两个参数useSSLfalse和allowPublicKeyRetrievaltrue。前者关闭SSL校验后者允许客户端从服务器获取公钥适用于MySQL 8.0的caching_sha2_password认证插件。这也是为什么我在3.3节改连接参数时特意提示这两个参数——大部分网络报错排查到最后问题都出在这一行URL上。顺带提一句网上教程常让人用Docker启动MySQL但Docker装MySQL失败的概率比本地安装高得多网络存储卷、端口映射、容器权限每一步都有坑课程设计场景老老实实本地装就好。6. 进阶改造给图书列表加一个分页顺手理解参数传递原版图书列表大概率是一次性查出所有book表数据。书少的时候无所谓数据超过几十条页面就会特别长而且每查一次列表都要全表扫描。给列表加分页是课程设计答辩时非常加分的改造点代码量不大但能体现你对SQL和HTTP参数传递的理解。先改DAO层加一个分页查询方法// BookDAO.java 分页查询 public ListBook findPage(int page, int pageSize) throws SQLException { // offset (页码-1) * 每页条数 int offset (page - 1) * pageSize; String sql SELECT * FROM book LIMIT ? OFFSET ?; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, pageSize); ps.setInt(2, offset); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapResultSetToBook(rs)); } } } return list; }LIMIT ? OFFSET ?的意思是从第offset行开始取pageSize条。页码从1开始第1页offset为0第2页offset为pageSize以此类推。同时需要一个count方法拿到总记录数用于计算总页数SELECT COUNT(*) FROM book总页数 (总数 pageSize - 1) / pageSize。BookServlet里接收page参数默认值为1pageSize固定为10或12都行。JSP页面的页码导航用EL和JSTL拼接链接bookList.jsp?page${currentPage - 1}、bookList.jsp?page${currentPage 1}。如果搜索功能和分页共存导航链接要把keyword参数一起带回去bookList.jsp?page2keyword${keyword}否则翻页后搜索条件丢失这是我第一次改分页时翻过车的细节——列表翻回第一页搜索词却没了。从那以后我每次给列表加分页都强制检查一遍当前页、总页数、上一页禁点、下一页禁点、搜索条件回显这五件事全部验证完才收工。希望这个习惯对你有用也帮你在答辩时少被问倒一次。本文还有配套的精品资源点击获取
返回列表