ARTICLE DETAIL

资讯详情

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

基于JSP的网上书店系统设计与实现全攻略

基于JSP的网上书店系统设计与实现全攻略 简介这是一份基于JSP技术的网上书店系统毕业设计文档面向计算机相关专业学生、毕业设计选题者及初学JavaWeb开发的人员。文档从系统需求分析、功能模块划分、数据库模式设计到前后端实现作了完整阐述涵盖图书展示、在线订购、后台图书管理、订单处理等典型电子商务功能并对比了JSP与ASP在安全性、性能及跨平台方面的差异。资源为1份doc文档整体包大小约1.09MB文字内容完整便于查阅和参考目录结构展开学习。目前已有352人学习下载。通过阅读这份设计读者可以了解基于JSPMySQL构建B2C书店系统的整体流程包括数据库表结构设计思路、关键功能模块划分、以及毕业设计论文的撰写框架适合用于开题参考、系统设计借鉴或论文格式参照。1. 基于JSP的网上书店系统值不值得在毕设里选它毕业季的Java Web选题里“基于JSP的网上书店系统的设计与实现”几乎是每年都能见到的面孔。需求听着简单做起来翻车最多的反而是一堆细节表结构怎么设计、购物车放Session还是数据库、下单要不要开事务。这套系统的价值在于能把JSP、Servlet、JDBC和MySQL在一个完整项目里串起来适合刚学完Java Web、想要一份能跑通也能答辩的毕业作品的读者。后面按“建表分层 → 会员模块 → 图书与订单 → 部署排错 → 安全收尾”的顺序展开能照着做也能拿来应付答辩老师对原理的追问。2. 表结构设计与JSP分层先行五张表撑起网上书店全部流程2.1 网上书店的ER图与五张核心表网上书店在毕设里算体量适中的题目需求容易讲清楚功能无外乎用户注册登录、图书浏览、加入购物车、下单、后台管理数据库五张表就能全覆盖。常见做法是先画ER图把实体和关系理清再落成建表SQL。表设计是整个系统的地基表设计翻车后面代码全要返工。用户、图书、购物车、订单、订单明细是五张核心表。购物车表可以先用Session实现而不建表但作为设计与实现的完整方案建议还是建上哪怕代码里暂时用Session结构上至少有据可查。订单和明细必须分开一个订单对应多条明细明细里保存下单时的书名和价格快照避免图书改价后历史订单跟着变。CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, nickname VARCHAR(50), phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE books ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), price DECIMAL(10, 2) NOT NULL, stock INT DEFAULT 0, cover_image VARCHAR(255), category VARCHAR(30), description TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, quantity INT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id), CONSTRAINT fk_cart_user FOREIGN KEY (user_id) REFERENCES users(id), CONSTRAINT fk_cart_book FOREIGN KEY (book_id) REFERENCES books(id) ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, total_price DECIMAL(10, 2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已发货 3已完成 4已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_order_user (user_id) ); CREATE TABLE order_items ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, book_id INT, book_name VARCHAR(100), price DECIMAL(10, 2), quantity INT, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders(id) );建表SQL里有几个参数值得注意。username加UNIQUE约束注册时数据库层就能拦住重复账号业务代码里只需捕获异常。price用DECIMAL(10,2)而不用FLOAT金额计算不会出现累积误差。order_no用VARCHAR(32)存业务订单号自增id只作为内部主键对外不暴露。订单明细冗余book_name和price这是电商系统常见的快照思路。status字段的TINYINT注释方便答辩时解释状态机。2.2 JSP项目不只有JSPModel 2分层与包结构很多初学者把JSP当成了全部写出的页面里又是连数据库又是写业务这就是Model 1模式。项目小时能跑页面一多就全乱。正规的做法是Model 2也就是MVCJSP只负责展示Servlet负责接收请求和控制跳转DAO负责数据库操作JavaBean负责封装数据。这套分层对于毕业设计非常有利论文里的“系统设计”部分可以直接画三层架构图答辩时老师问“为什么用MVC”也能答出职责分离、便于维护的点。我一般会建下面这个包结构src/main/java/com/bookstore/ ├── bean/ # User, Book, CartItem, Order ├── dao/ # UserDAO, BookDAO, OrderDAO ├── servlet/ # UserServlet, BookServlet, CartServlet, OrderServlet ├── util/ # DBUtil src/main/webapp/ ├── index.jsp ├── login.jsp ├── register.jsp ├── bookList.jsp ├── cart.jsp ├── orderConfirm.jsp ├── WEB-INF/web.xml └── css/各层依赖关系是单向的Servlet调用DAODAO返回BeanJSP通过EL表达式读取Bean。反过来不行JSP不能直接写JDBC代码这是这条目录结构的基本纪律。新人最容易犯的错就是图省事在JSP里写Java代码最后页面改一个按钮都要重新编译完全没有分层意义。Tomcat的web.xml也要在项目初期配好我默认放4.0版本的头部兼容Servlet 3.1以上注解扫描?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 display-namebookstore/display-name welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list session-config session-timeout30/session-timeout /session-config /web-appweb-app_version4.0决定了容器按Servlet 4.0规范解析WebServlet注解。如果头版本写成2.5或更低注解可能被忽略所有Servlet只能靠XML配置这是后面部署时的一个大坑。session-timeout设30分钟默认值就够了。2.3 用DBUtil统一管理数据库连接数据库连接不能散落在每个DAO里工具类集中管理是入门项目最稳妥的方案。用DriverManager直连是JSP毕设最常见做法代码简单、原理好讲后续可平滑换连接池。package com.bookstore.util; import java.sql.*; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/bookstore?useUnicodetruecharacterEncodingutf-8useSSLfalse; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(ResultSet rs, Statement stmt, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }URL参数里useUnicodetrue和characterEncodingutf-8必须成对出现少一个都可能让插入的中文变乱码。useSSLfalse避免MySQL 8.x连接时报警告。驱动类com.mysql.jdbc.Driver对应MySQL 5.x的驱动jar如果你用的是MySQL 8.x要改成com.mysql.cj.jdbc.Driver否则启动报ClassNotFoundException。这里我吃过亏升级数据库后忘了同步改驱动名排查了半个下午才发现。关闭连接的顺序是ResultSet、Statement、Connection理论上前者关闭后后者才能安全释放。用try-with-resources可以简化但如果要配合手动事务DBUtil里的close仍然有用因为事务提交后连接要回到连接池或真正关闭。3. 用IDEA新建JSP项目到会员模块落库注册、登录与个人信息展示页面3.1 从IDEA新建JSP项目开始IDEA里新建JSP项目有两条路。一条是新建时选Java Enterprise勾选Web Application但这会引入Jakarta EE的包名和Tomcat 8.5的javax包冲突运行时报NoClassDefFoundError。另一条更稳妥新建普通Java项目然后右键项目选择Add Framework Support勾选Web Application再手动配置Tomcat。推荐用第二种方式包名是javax.servlet对Tomcat 8.5兼容性最好。项目建好后检查Project Structure里的Artifacts确认生成了Web Application Exploded结构。运行配置里选Tomcat ServerDeployment选项卡把项目加到Server下Application context设为/bookstore访问路径就是http://localhost:8080/bookstore。这个步骤最容易出问题的就是Artifacts。IDEA里代码编译正常Run却提示404基本是Artifacts没配置好项目没被打成war或exploded目录。检查Artifacts栏里有没有项目名:war exploded没有就手动新建一个Web Application类型的Artifacts。3.2 UserDAO UserServlet登录的黑匣子拆开看登录功能的完整链路是login.jsp表单POST到UserServletUserServlet调用UserDAO查询数据库成功就把User对象放进Session跳转到首页失败则携带错误信息转发回登录页。这套流程看着简单但每一层都有细节。先写UserDAO这是数据访问的核心package com.bookstore.dao; import com.bookstore.bean.User; import com.bookstore.util.DBUtil; import java.sql.*; public class UserDAO { public User findUser(String username, String password) { String sql SELECT * FROM users WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setNickname(rs.getString(nickname)); user.setPhone(rs.getString(phone)); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; } }SQL用?占位符配合PreparedStatement而不是字符串拼接这是JSP项目里最基础也最重要的安全习惯。字符串拼接的SQL一旦用户名里输入 or 11登录黑匣子就被撬开了。PreparedStatement除了防注入还能预编译提升执行效率参数setString和setInt的序号从1开始。再写UserServlet用WebServlet注解注册路由package com.bookstore.servlet; import com.bookstore.bean.User; import com.bookstore.dao.UserDAO; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; WebServlet(/login) public class UserServlet extends HttpServlet { private UserDAO userDAO new UserDAO(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(utf-8); String username req.getParameter(username); String password req.getParameter(password); User user userDAO.findUser(username, password); if (user ! null) { HttpSession session req.getSession(); session.setAttribute(loginUser, user); resp.sendRedirect(index.jsp); } else { req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }注意两个细节。第一doPost里第一行必须调req.setCharacterEncoding(utf-8)不调的话中文用户名在POST参数里就是乱码。第二登录成功用sendRedirect失败用forward。成功后如果还用forward刷新页面会重复提交表单重定向让浏览器发起新请求地址栏变成index.jsp刷新也不会重复登录。3.3 注册去重与个人信息展示页面注册逻辑比登录多一步先查用户名是否已存在再插入新用户。数据库有UNIQUE约束但业务层先查一次能让错误信息更友好。public boolean register(User user) { String checkSql SELECT COUNT(*) FROM users WHERE username ?; String insertSql INSERT INTO users (username, password, nickname, phone) VALUES (?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement checkPs conn.prepareStatement(checkSql)) { checkPs.setString(1, user.getUsername()); try (ResultSet rs checkPs.executeQuery()) { if (rs.next() rs.getInt(1) 0) { return false; } } try (PreparedStatement insertPs conn.prepareStatement(insertSql)) { insertPs.setString(1, user.getUsername()); insertPs.setString(2, user.getPassword()); insertPs.setString(3, user.getNickname()); insertPs.setString(4, user.getPhone()); return insertPs.executeUpdate() 1; } } catch (SQLException e) { e.printStackTrace(); return false; } }注册接口的返回booleanServlet再根据结果决定跳转还是回显错误逻辑清晰。密码存的是明文这在毕设里常见但答辩容易被追问。第6章会给一个MD5加盐的改进版本那才是能拿得出手的写法。个人信息展示页面也就是热词里常见的“jsp个人信息展示页面”核心是把Session里的User读出来展示。JSP有两种做法传统的是useBean标签% page contentTypetext/html;charsetUTF-8 languagejava % jsp:useBean idloginUser scopesession classcom.bookstore.bean.User / html body h2个人中心/h2 欢迎您jsp:getProperty nameloginUser propertynickname / p手机号jsp:getProperty nameloginUser propertyphone //p /body /htmluseBean标签看着正式但有个隐患session里没有loginUser属性时会new一个空的User对象页面显示null。更推荐EL表达式${loginUser.nickname}属性为null时JSP页面显示空字符串同时配合c:if标签判断是否登录未登录跳回login.jsp。EL表达式在答辩时也是加分项说明你用了JSP 2.0之后的规范。4. 把图书列表、购物车和订单串起来分页、Session购物车与下单事务4.1 图书列表分页一次查一页别一次性查全表图书列表是网站的门面。最直观的写法是SELECT * FROM books把全部图书查到List里再在JSP页面循环输出。数据量小没问题一旦图书几百本页面明显变慢。分页是这里必须有的设计也是答辩老师最常问的功能点之一。分页的核心参数是currentPage和pageSizeSQL用LIMIT子句控制偏移量public ListBook findBooksByPage(int currentPage, int pageSize) { ListBook books new ArrayList(); String sql SELECT * FROM books ORDER BY id LIMIT ?, ?; int offset (currentPage - 1) * pageSize; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, offset); ps.setInt(2, pageSize); 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)); books.add(book); } } } catch (SQLException e) { e.printStackTrace(); } return books; }LIMIT的第一个参数是偏移量从0开始。第1页offset是0第2页是pageSize公式(currentPage - 1) * pageSize要记牢。如果直接传currentPage进去第2页会把第pageSize条到第2*pageSize条的数据错位展示这是分页最典型的翻车点。还要统计总数计算总页数。BookDAO里加一个countBooks()方法执行SELECT COUNT(*) FROM books然后Servlet里算totalPages (total pageSize - 1) / pageSize。这个公式用向上取整避免最后一页少算。BookServlet里把books、currentPage、totalPages都set进requestforward到bookList.jsp页面底部渲染上一页、下一页和页码列表。4.2 购物车放Session还是数据库先选型再写AddCartServlet购物车是网上书店最有讨论价值的设计点。常见方案有两种存Session和存数据库表。存Session简单购物车跟着用户会话走不需要额外的表操作但清浏览器或会话过期就丢。存数据库持久化好但每次加购都要读写cart表还要处理未登录用户。毕设项目我建议用Session理由有三需求简单不需要跨设备同步代码量少一半Session本身就是HttpSession答辩解释“购物车属于临时性数据”说得通。如果做的是带会员等级的完整商城再考虑落库。下面的AddCartServlet是Session方案的典型写法WebServlet(/addCart) public class AddCartServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int bookId Integer.parseInt(req.getParameter(bookId)); HttpSession session req.getSession(); LinkedHashMapInteger, CartItem cart (LinkedHashMapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new LinkedHashMap(); } CartItem item cart.get(bookId); if (item null) { item new CartItem(); item.setBookId(bookId); item.setQuantity(1); cart.put(bookId, item); } else { item.setQuantity(item.getQuantity() 1); } session.setAttribute(cart, cart); resp.sendRedirect(cart.jsp); } }用LinkedHashMap而不是List存购物车是一个值得说的细节。Map的key是bookId判断“这本书已经在购物车里”只需一次get时间复杂度从O(n)降到O(1)同时LinkedHashMap保持插入顺序页面展示按加入顺序排列。CartItem里只需要bookId和quantity书名价格在cart.jsp展示时再查库这样购物车只存最小必要信息不存一份可能过期的图书价格快照。Session购物车的坑在于用户未登录也能加购但下单时必须有登录用户。跳转下单页面时要判断session里有没有loginUser没有就重定向到login.jsp并在URL里带returnUrl参数登录成功再跳回来。这个逻辑属于在线书店购物流程的必修课能写进设计说明里。4.3 下单事务多张表一起写回滚才是后悔药下单是整个系统里唯一涉及多张表写入的操作生成订单主表记录、生成订单明细、扣减图书库存、清空购物车。四件事必须同时成功或同时失败这就是数据库事务。少了事务订单建好了库存没扣或者订单明细写一半订单主表没提交系统就处在脏数据状态。订单DAO的写法要注意Connection不能由每个方法自己获取又自己关闭必须由下单逻辑统一控制public boolean createOrder(int userId, double totalPrice, ListCartItem cart) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String insertOrder INSERT INTO orders (order_no, user_id, total_price, status) VALUES (?, ?, ?, 0); PreparedStatement psOrder conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, generateOrderNo()); psOrder.setInt(2, userId); psOrder.setDouble(3, totalPrice); psOrder.executeUpdate(); ResultSet keys psOrder.getGeneratedKeys(); int orderId 0; if (keys.next()) { orderId keys.getInt(1); } String insertItem INSERT INTO order_items (order_id, book_id, book_name, price, quantity) VALUES (?, ?, ?, ?, ?); for (CartItem item : cart) { PreparedStatement psItem conn.prepareStatement(insertItem); psItem.setInt(1, orderId); psItem.setInt(2, item.getBookId()); psItem.setString(3, item.getBookName()); psItem.setBigDecimal(4, item.getPrice()); psItem.setInt(5, item.getQuantity()); psItem.executeUpdate(); } String updateStock UPDATE books SET stock stock - ? WHERE id ? AND stock ?; for (CartItem item : cart) { PreparedStatement psStock conn.prepareStatement(updateStock); psStock.setInt(1, item.getQuantity()); psStock.setInt(2, item.getBookId()); psStock.setInt(3, item.getQuantity()); int rows psStock.executeUpdate(); if (rows 0) { throw new SQLException(库存不足事务回滚); } } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } e.printStackTrace(); return false; } finally { DBUtil.close(null, null, conn); } }这段代码有三个关键点。第一setAutoCommit(false)之后所有SQL都在同一个事务里直到commit才真正生效。第二扣减库存的SQL带了AND stock ?条件这条SQL的返回行数既是结果也是校验——库存不足时更新0行直接抛异常触发回滚从根上防止超卖。第三Statement.RETURN_GENERATED_KEYS配合getGeneratedKeys()拿到自增主键订单明细才能关联到正确的订单。事务的console日志在出错时能看到SQLException但看不到具体是哪一步失败。建议在catch块里把e.getMessage()打出来同时把事务里的每一步包成try-catch分别记录排查时会少走很多弯路。这也是我早期做项目时不重视的细节事务一旦回滚没有日志等于黑匣子只能靠猜。5. JSP网上书店部署避坑打war包到Tomcat常踩的6个坑5.1 页面中文全变问号编码不统一现象本地IDEA里运行一切正常部署到独立Tomcat后所有中文内容变成问号。原因开发环境IDEA默认UTF-8但Tomcat处理请求和响应的编码不一致。Tomcat 8.5后GET请求默认UTF-8POST请求默认ISO-8859-1JSP页面本身的pageEncoding没设UTF-8时Tomcat也按ISO-8859-1读。解决三处都改成UTF-8。JSP文件头部写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %写一个全局过滤器在doFilter里对request和response都调setCharacterEncoding(utf-8)必要时在server.xml的Connector上加URIEncodingUTF-8。只改其中一处问题还会在别的页面复现。5.2 IDEA里能跑部署到Tomcat却ClassNotFound现象本地运行正常把war包丢到Tomcat的webapps目录启动报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。原因IDEA运行项目时驱动jar在IDEA的Classpath里打war包时jar没有打进WEB-INF/lib目录。Artifacts的输出布局里默认只打包编译后的class和资源外部库需要手工加进去。解决Project Structure → Artifacts → Output Layout右键选择Put into Output Root或加lib目录把mysql-connector-java.jar放进去重新Build Artifacts。检查war包可以用压缩软件打开看WEB-INF/lib下有没有jar这个习惯能省大量部署时间。5.3 WebServlet没生效web.xml版本太老现象访问/login地址404Tomcat控制台没有报错。原因web.xml的头部声明是Servlet 2.5甚至2.3版本容器按老规范解析WebServlet注解被忽略。解决把web.xml头部换成4.0版本就是第2章写的那份。如果项目里没有web.xmlTomcat默认按Servlet 4.0处理注解反而能用。所以出现注解不生效时先别怀疑代码把web.xml版本号改了或者删掉重试。5.4 JSP改了不生效Artifacts没重新构建现象修改了JSP页面浏览器强制刷新还是旧内容。原因IDEA的Run tomcat时Tomcat看到的是Artifacts输出目录里的副本。修改了源码树下的JSP没有重新构建Artifacts副本还是旧文件。解决菜单Build → Build Artifacts → Rebuild然后重启Tomcat。更省事的方法是给Tomcat配置部署方式时选择war exploded并把Application context下的目录指向项目输出配合IDEA的Deployment选项。这个问题不算难但在毕设冲刺阶段改页面最频繁每次都要等rebuild提前养成顺手rebuild的习惯能省不少火气。5.5 orders表名与MySQL保留字冲突现象执行INSERT INTO orders一直报syntax error near orders检查多遍SQL看不出问题。原因MySQL里ORDER是保留字表名orders虽然没有被完整保留但某些版本和模式下和保留字相近的表名也会在语句解析时出问题。解决统一用反引号包住表名INSERT INTO \orders。或者干脆把表名改成t_order、book_order这种带前缀的名字一劳永逸。这个坑在MySQL 5.7和8.0都遇到过建议建表时就避开后面写SQL不折腾。5.6 nginx不能直接跑JSP别指望反代代劳现象试图用nginx直接部署JSP项目访问.jsp文件时要么返回源码要么下载文件。原因nginx本身不是Servlet容器它不解析JSP。JSP文件的编译和执行必须由Tomcat这类Servlet容器完成nginx只负责静态文件和反向代理转发。解决网上书店的常规部署是nginx监听80端口静态资源css、js、图片由nginx直接响应动态.jsp请求通过proxy_pass转发给Tomcat的8080端口。Tomcat处理完把响应返回nginxnginx再回给浏览器。毕设答辩演示时直接让Tomcat监听8080就行nginx不是必须项。但知道nginx只能代理不能解析JSP面试和答辩时能体现你对部署架构的理解。6. 给网上书店补一层答辩底气的安全与性能写法Session购物车虽然简单但有两件事决定这套系统能不能从“跑得通”升级到“讲得清”。第一是数据库连接池第二是密码加密。JDBC直连在每次请求时都经历“创建连接→执行SQL→销毁连接”压力一大就慢。常见做法是换成Druid连接池代码改动只在DBUtil内部private static DruidDataSource dataSource; static { dataSource new DruidDataSource(); dataSource.setUrl(URL); dataSource.setUsername(USER); dataSource.setPassword(PASSWORD); dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMaxWait(3000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }initialSize是连接池启动时预建的连接数maxActive是最大连接数maxWait是拿不到连接时的最大等待毫秒数。三个参数分别回答“池子里常备几个连接”“最多允许多少并发”“排队等多久就放弃”答辩时能讲清参数含义就是加分项。密码加密是另一个必改点。登录表里存明文密码放在本地项目没人看一旦部署到公网就是漏洞。最简单可靠的写法是MD5加盐public static String md5WithSalt(String password, String salt) { String text salt password; return DigestUtils.md5Hex(text); }salt可以用用户名注册时把salt 明文的MD5值入库。登录时先用同样的规则算出哈希再比对。这版完整流程是用户注册 → 计算md5(username password)→ 存入password列登录时 → 用输入的用户名和密码计算哈希 → 与库里的列比对。数据库泄露了攻击者拿到的也是哈希比明文安全一个量级。最后说个做毕设期间的教训我当时把全部精力放在把功能跑通上到了答辩演示老师一句“你密码怎么存、并发下单会不会超卖、购物车刷新之后还在不在”才发现靠裸DriverManager、明文密码、无事务的版本连及格线都勉强。后来补上连接池、事务和MD5加盐代码结构才真正立住。这套JSP网上书店做的不是发明新框架而是把每个环节用最标准的方式串起来这个过程本身比功能值钱。希望帮到你。本文还有配套的精品资源点击获取
返回列表