尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JSP+Servlet商城系统全解析:从数据库设计到部署避坑指南

JSP+Servlet商城系统全解析:从数据库设计到部署避坑指南 简介面向毕业设计场景的Java Web家用电器购物商城系统基于JSP、Servlet、JDBC搭建配套MySQL数据库适合需要快速掌握传统Java Web开发全流程的本科生或开发者。系统围绕管理员和用户双角色设计管理员可维护商品信息、上传图片、查询订单并统计销售数据用户可完成注册登录、浏览商品详情、管理购物车、维护收货地址、提交订单并模拟付款功能覆盖典型电商交易闭环。资源共255个文件核心代码包含42个Java类、19个JSP页面、34个JS脚本并辅以SQL库表脚本、CSS样式、图标字体及依赖JAR包压缩包整体约8.03MB目录结构清晰便于本地部署和学习。目前已有29人学习使用。从这套资源中可以提取一套可直接运行的完整源码、数据库建表脚本、Layui前端布局示例和分层写法参考作为毕业设计蓝本或课程设计进阶练习均较合适。1. 从毕设选题到能跑的jspservlet商城系统这项目到底解决什么每年到了课程设计季总有一批人被“基于jspservlet的家用电器购物商城”这类题目围住。打开学校给的题目清单满屏都是“XX管理系统”点进去一看要求前端页面、后台管理、购物车、订单、登录注册再配一个MySQL数据库。说白了这题考的不是你会不会用Spring Boot而是你能不能在没有脚手架的情况下把JavaWeb最底层的请求响应流程走通。jspservlet这套东西现在生产环境用得少了但它恰恰是理解JavaWeb的骨架——请求怎么进来、Servlet怎么接、JSP怎么渲染、数据库怎么连全在这条链路里。这套家用电器购物商城系统技术上是标准的Servlet做控制器、JSP做视图、MySQL存数据的三层结构外加一个购物车模块和订单流程。适合三类人正在做JavaWeb课设的学生、想用最小成本搭一个传统JavaWeb项目练手的开发者、以及需要一套带数据库脚本的源码做二次开发的人。接下来我把这套系统的表结构、运行步骤、核心代码和常见坑一次性拆完。2. 为什么是jspservlet而不是脚手架技术栈拆解与数据库表设计2.1 JSP负责页面、Servlet负责逻辑它们的职责边界很多新手拿到这个项目第一反应是“JSP里是不是能写Java代码那我全写JSP里不就行了”——这是最大的误区。JSP本质上是Servlet的模板Tomcat会把JSP编译成Java类再运行但你要是把业务逻辑全塞进JSP的% %标签里页面会变成一锅粥。这项目的正确姿势是Servlet接收请求、调用DAO层拿数据、把结果塞进request域或session域然后forward到JSP去渲染。比如用户点“加入购物车”浏览器发一个/cart/add请求CartServlet的doPost方法里做三件事从session里拿购物车对象、把商品和数量塞进去、重定向回商品列表页。JSP只负责一件事从request里把数据拽出来用${}表达式或者c:forEach循环渲染成HTML。职责一旦分清楚后面排查问题能省一半时间。2.2 MySQL数据库表设计从商品表到订单表家用电器商城的管理系统表不会太复杂但该有的关联关系得有。最少需要五张表用户表、商品分类表、商品表、订单表、订单明细表。用户和订单是一对多订单和订单明细是一对多商品和订单明细是多对一。下面是我常用的建表SQL直接跑在MySQL 5.7以上版本没问题CREATE DATABASE IF NOT EXISTS appliance_mall DEFAULT CHARSET utf8mb4; USE appliance_mall; -- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, phone VARCHAR(20), address VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 商品分类表 CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, sort INT DEFAULT 0 ); -- 商品表 CREATE TABLE t_product ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, image VARCHAR(255), description TEXT, status TINYINT DEFAULT 1, FOREIGN KEY (category_id) REFERENCES t_category(id) ); -- 订单表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES t_user(id) ); -- 订单明细表 CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, FOREIGN KEY (order_id) REFERENCES t_order(id), FOREIGN KEY (product_id) REFERENCES t_product(id) );这里的DECIMAL(10,2)用于价格字段是有讲究的家电价格涉及小数不能用FLOAT否则算总价会出现精度问题。订单状态status用数字字典管理0表示待付款1表示已付款待发货2表示已发货3表示已完成4表示已取消。FOREIGN KEY建议保留虽然项目代码里可能大部分操作都是单表查询但外键能在你手滑删掉分类时拦住你。2.3 JDBC与连接池数据库操作的标准三步这项目里数据库访问不用MyBatis就是裸的JDBC。常规写法是三步加载驱动、获取连接、执行SQL。但每次请求都新建一个连接性能上是灾难。所以源码里一般会提供一个DBUtil类内部用c3p0或Druid连接池管理连接。连接池是热搜里“mysql的数据库连接池”的实践场景课设阶段不用纠结选哪个Druid就行监控页面还能截图写进报告里。核心的DBUtil写法public class DBUtil { private static DruidDataSource dataSource; static { dataSource new DruidDataSource(); dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/appliance_mall?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); dataSource.setMaxActive(20); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) try { rs.close(); } catch (SQLException e) {} if (ps ! null) try { ps.close(); } catch (SQLException e) {} if (conn ! null) try { conn.close(); } catch (SQLException e) {} } }关键点有两个。第一serverTimezoneAsia/Shanghai必须加MySQL 8.x的连接驱动对时区敏感不写这个会直接报Server time zone异常。第二setInitialSize(5)和setMaxActive(20)是最常用的初始化和最大连接池参数课设并发量小5个连接绰绰有余不用调大。3. 在IDEA里把jspservlet项目跑起来骨架搭建与部署3.1 新建JavaWeb项目从Web Application到Tomcat配置拿到源码后第一件事不是看代码是先让它跑起来。这项目的标准形态是一个war包结构IDEA里新建项目时选“Java Enterprise”或直接在已有项目里添加“Web Application”支持。很多教程会让你从零build但你有源码在手正确的路线是IDEA菜单File - New - Project from Existing Sources选源码根目录Maven项目会识别pom.xml非Maven项目直接选普通Java项目再手动加Web配置。如果你拿到的是纯JSPServlet项目没有Maven依赖管理那么WEB-INF/lib目录下需要手动放这几个jar包servlet-api.jar或者jakarta.servlet-api取决于Tomcat版本、mysql-connector-java.jar、druid.jar、jstl.jar、standard.jar。一个常见翻车点是把高版本的jakarta.servlet-api放到Tomcat 8上Tomcat 8用的是javax.servlet包名二者完全不兼容。3.2 项目结构与传统jsp项目打包war的对应关系JSPServlet项目的目录结构有严格约定Tomcat只认这个格式。典型的源码结构如下src/ ├── com/mall/dao/ # 数据访问层 │ ├── UserDao.java │ ├── ProductDao.java │ └── OrderDao.java ├── com/mall/entity/ # 实体类 │ ├── User.java │ ├── Product.java │ └── Order.java ├── com/mall/servlet/ # Servlet控制器 │ ├── LoginServlet.java │ ├── RegisterServlet.java │ ├── CartServlet.java │ └── OrderServlet.java ├── com/mall/util/ # 工具类DBUtil等 │ └── DBUtil.java ├── com/mall/filter/ # 过滤器 │ └── LoginFilter.java └── web/ ├── WEB-INF/ │ ├── web.xml # 部署描述符注册Servlet和Filter │ └── lib/ # 依赖jar包 ├── login.jsp ├── register.jsp ├── index.jsp ├── product_list.jsp └── cart.jsp网上搜“传统jsp项目打包war”能搜到一堆教程核心思路在IDEA里打开Project Structure - Artifacts - 点“” - Web Application Exploded - 选项目然后Build - Build Artifacts最后target目录下会生成war包。这个结构对应关系搞清楚你排查404和类加载问题就有方向了——ClassNotFound十有八九是lib目录下的jar没打进去404十有八九是Servlet没注册到web.xml。3.3 第一个ServletJSP联动登录页的最小实现先用最简代码把链路跑通再往里面加功能。一个登录功能的ServletWebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /product/list); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这段代码的逻辑说明WebServlet(/login)是Servlet 3.0之后的注解式注册如果项目用的是Tomcat 7以上就可以不用在web.xml里配Servlet映射。request.getParameter拿到的是表单提交的字段用户存在则把对象塞进session并重定向不存在则回登录页并携带错误提示。注意这里的编码设置request.setCharacterEncoding(UTF-8)必须放在读取参数之前否则中文用户名会乱码。4. 商城核心模块的实现套路购物车、订单与权限拦截4.1 商品列表与分页SQL层面怎么做商城主页的商品列表数据量少可以直接全查但为了课设报告里有“分页”这个知识点一般会做一个分页。分页核心不是写代码是写对SQL。MySQL用LIMIT offset, sizeoffset是当前页减一乘以每页条数public ListProduct findPage(int pageNum, int pageSize) { String sql SELECT * FROM t_product WHERE status 1 ORDER BY id DESC LIMIT ?, ?; ListProduct list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); p.setImage(rs.getString(image)); list.add(p); } } } catch (SQLException e) { e.printStackTrace(); } return list; }LIMIT ?, ?的两个参数都要用setInt设置第一个是起始行第二个是返回行数。分页的正确边界第一页是LIMIT 0, 10第二页是LIMIT 10, 10。有同学会写成LIMIT 10, 0结果一页都查不出来。另外商品价格用rs.getBigDecimal(price)取不要用getFloat或getDouble因为数据库是DECIMAL类型用BigDecimal接不会丢精度。4.2 购物车用Session还是数据库两者的边界购物车是这个项目里最值得抠细节的模块。家电类商城用户没登录也能逛、能加购物车所以购物车存在session里是标准做法——为什么第一购物车是临时数据没必要落库第二session天然和用户绑定不需要额外的用户ID关联。做法是定义一个CartItem类再用HashMapInteger, CartItem当购物车本体key是商品IDvalue包含商品信息和数量WebServlet(/cart/add) public class CartAddServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int productId Integer.parseInt(request.getParameter(productId)); int quantity Integer.parseInt(request.getParameter(quantity)); HttpSession session request.getSession(); MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } if (cart.containsKey(productId)) { CartItem item cart.get(productId); item.setQuantity(item.getQuantity() quantity); } else { Product product new ProductDao().findById(productId); cart.put(productId, new CartItem(product, quantity)); } response.sendRedirect(request.getContextPath() /cart/show); } }这段代码的逻辑说明先从session拿购物车拿不到就新建一个商品已经在购物车里就累加数量否则查一次数据库把商品信息塞进去最后重定向到购物车展示页。注意这里有个性能踩坑点每次加购都调findById查数据库量大了会慢。优化方案是加购时不查只存productId展示购物车时再用IN (?,?,?)一次查出所有商品信息但这属于进阶优化课设阶段能跑通就行。4.3 登录拦截与过滤器Filter的用法和坑商城系统有“我的订单”“结算”这类必须登录才能访问的页面如果每个Servlet都写一遍session判断代码会臭不可闻。这项目的标准解法是用Filter统一拦截。一个登录过滤器WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); String ctxPath request.getContextPath(); String path uri.substring(ctxPath.length()); // 放行登录、注册、静态资源和商品列表 if (path.equals(/login.jsp) || path.equals(/login) || path.equals(/register.jsp) || path.equals(/register) || path.startsWith(/product) || path.startsWith(/static)) { chain.doFilter(req, resp); return; } HttpSession session request.getSession(false); if (session ! null session.getAttribute(loginUser) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }这里有个原则过滤器里放行的路径必须和Servlet注解里的路径一一对应。比如/product/list放行了但/product开头的其他接口也全放行了如果后台管理页面也挂在/product下那拦截就失效了。课设里常见翻车是把后台管理路径/admin/*漏配导致登录了也进不去后台或者没登录也能进后台。5. jspservlet商城避坑指南乱码、路径与依赖冲突5.1 页面乱码三个位置必须保持一致现象登录用户名是中文存进数据库变??或者页面显示一堆乱码。原因JSP页面编码、请求编码、数据库连接编码三处不一致。比如JSP里是GBK数据库连接串写的是characterEncodingutf8Tomcat默认请求编码又是ISO-8859-1三方打架必然乱码。解决统一成UTF-8。JSP文件头写% page contentTypetext/html;charsetUTF-8 languagejava %Servlet里request.setCharacterEncoding(UTF-8)必须放在第一个getParameter之前数据库URL加上characterEncodingutf8mb4。此外在Tomcat的server.xml里给Connector加上URIEncodingUTF-8处理GET请求的QueryString参数乱码。顺序依次是Tomcat - JSP - Servlet - JDBC URL任何一环漏掉都白改。5.2 404和405URL映射与请求方式的错位现象点击页面跳转后浏览器报404或者报“HTTP 405 - 方法不允许”。原因404分两种情况——第一种是Servlet类没编译进war包检查Artifacts输出路径下有没有对应的.class第二种是WebServlet(/cart/add)注册的路径和页面表单里actioncart/add不一致少了开头的斜杠也是坑。405则是表单是methodpostServlet只写了doGet没写doPost或者反过来。解决用request.getContextPath()拼路径比如form action${pageContext.request.contextPath}/cart/add methodpost不要写绝对路径。Servlet里同时覆写doGet和doPost或者在doGet里调doPost最省事的写法是两个方法都调同一个service方法。5.3 mysql-connector的驱动名与版本之争现象启动项目后访问数据库报ClassNotFoundException: com.mysql.jdbc.Driver。原因你用了MySQL 8.x的驱动包但加载驱动的代码还写老驱动名com.mysql.jdbc.Driver。MySQL 8的驱动类改名成com.mysql.cj.jdbc.Driver老名字在8.x驱动里被移除了。反过来如果你用的MySQL是5.7但驱动包是8.x也有兼容性问题。解决统一版本。MySQL 8.x就用com.mysql.cj.jdbc.DriverMySQL 5.7就保持com.mysql.jdbc.Driver。如果源码里写的是老驱动名你可以在WEB-INF/lib里放5.1.49版本的mysql-connector-java.jar这是最稳妥的做法不会报时区警告。另一个附带坑是MySQL 8驱动要求URL里必须带serverTimezone参数才能连上不带就报SQLException: The server time zone value这个和驱动名一起检查。5.4 数据库连接不上的排查顺序现象Tomcat起来了页面能开但一登录就报Cannot create PoolableConnectionFactoryTomcat控制台刷红。原因连接池创建连接失败原因多半是数据库没启动、密码不对、URL写错、或者防火墙挡了3306端口。这个错排起来有一条固定顺序。解决第一步telnet 127.0.0.1 3306确认端口通不通第二步命令行里手动跑一下mysql -uroot -p123456确认密码和用户名第三步检查连接串里的localhost——如果你连的是远程数据库localhost会指向你自己机器一定要改IP第四步看Druid的报错日志如果提示Access denied for user rootlocalhost那是密码错了去DBUtil里改。注意mysql 8的密码加密方式默认是caching_sha2_password老驱动连接会报Public Key Retrieval is not allowed这时在URL后加一个allowPublicKeyRetrievaltrue。6. 课设交付前的最后一公里数据初始化与war包发布项目能本地跑通只是第一步交付时老师要的是能部署、能演示。先说数据初始化。源码数据库里如果只有建表SQL没有测试数据你打开首页是空的演示效果大打折扣。正确做法是写一个init_data.sql插入两三个分类和七八个家电商品图片地址用相对路径指向项目里的static/images/目录。家电商品描述里别写太简单加上“额定功率”“能效等级”“保修年限”这类属性展示时显得系统完整。war包发布是另一个容易卡住的点。在IDEA里Build Artifacts生成war包后直接复制到Tomcat的webapps目录下启动Tomcat会自动解压部署。这里有一个部署路径的坑Tomcat的部署方式决定了应用访问路径——如果你把war包命名为mall.war访问URL就是http://localhost:8080/mall/所有页面跳转和静态资源引用都必须包含/mall前缀。所以JSP页面里引用静态资源时标准写法是c:url value/static/css/style.css/或者${pageContext.request.contextPath}/static/css/style.css不能写死/static/...。再往后做别急着上框架先看这三点第一把硬编码的数据库配置抽到db.properties文件里不同环境下切换配置不用重新编译第二给订单生成时间加一个索引虽然课设数据量小用不上但面试时能顺嘴说出来“订单查询的慢SQL优化思路”第三把Servlet里的业务逻辑抽到Service层一个Servlet瘦到只剩参数接收和页面跳转这是最接近真实项目的写法也是答辩时老师最爱问的“你这个分层怎么分的”。这套jspservlet的家电商城核心价值不是它的代码有多高级而是它把JavaWeb的巴掌大链路完整走了一遍。我当年做课设时也被乱码折磨过两天最后发现是Tomcat的URIEncoding没设也踩过购物车用数据库存储导致并发超卖、最后改成session方案的坑。现在回头看这些坑恰恰是这门课真正要教的东西。希望这篇拆解能让你少走几趟弯路把时间花在真心想做的功能上。本文还有配套的精品资源点击获取
返回列表