ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSM+JSP母婴商城毕业设计:数据库脚本与部署全攻略

SSM+JSP母婴商城毕业设计:数据库脚本与部署全攻略 简介一套基于SSMJSP的母婴用品商城项目源码面向Java毕业设计、课程设计及期末大作业需求。项目包含完整前后端代码、数据库脚本与部署工具基于IDEA开发数据库采用MySQL 5.7可部署于Tomcat 7/8代码附注释易读易改。系统涵盖商品展示、购物车、订单管理、后台管理等电商典型模块界面美观、操作流畅经过严格调试简单部署即可运行。资源包约17.89MB共1370个文件以JSP页面、Java类、CSS样式、JavaScript脚本为主含MySQL建表SQL脚本及图片图标等静态资源目录规划清晰便于对照前端视图、后端逻辑与数据库设计。目前已有53人学习下载适合需要快速搭建完整SSM项目、完成毕设或巩固Java Web开发能力的同学。1. 这个SSMJSP的母婴用品毕业设计包里真正决定成败的是数据库脚本如果只把这个压缩包当成一份“能跑的Java代码”第一印象通常是Spring管理对象、SpringMVC接请求、MyBatis写SQL、JSP画页面四件事各干各的跑起来就能交差。实际上我在帮人排查这类毕业设计项目时发现绝大多数运行失败不是Controller写错而是数据库脚本导入出错、JDK与Tomcat版本不匹配、JSP编译后的class文件路径找不到这三类问题。这个标题里的“数据库”不是附赠品它才是整个项目的地基母婴商品、购物车、订单、用户地址这些数据表一旦字段对不上Service层的增删改查全部白写。这篇内容适合两类人一类是准备答辩、需要把这套SSMJSP项目真正跑通并讲清楚每张表为什么这么建的在校生另一类是把这类项目当脚手架、想快速接一个新主题或新模块的Java后端工程师。下面按“框架路径—JSP渲染—表结构—本地运行—加一个搜索功能”的顺序展开每一步都给出可直接复制的配置和代码。2. SSM框架在母婴网站里的职责请求从JSP到数据库的完整路径2.1 一次商品列表请求在SSM里的五次跳转SSM不是三个独立工具的拼盘而是一条严格分层的调用链。用户在前端页面点“母婴奶粉”分类浏览器发出一个GET请求这个请求先经过web.xml里配置的DispatcherServletSpringMVC根据RequestMapping把请求交给ProductController的list方法Controller不直接查库而是调用ProductServiceService调用ProductMapper接口MyBatis在运行时为这个接口生成代理对象执行Mapper XML里的SQLSQL结果再逐层返回最后封装成ModelAndView由视图解析器拼出JSP路径。Controller RequestMapping(/product) public class ProductController { Autowired private ProductService productService; RequestMapping(/list) public ModelAndView list(RequestParam(defaultValue 1) int pageNum, RequestParam(defaultValue 12) int pageSize) { PageInfoProduct page productService.queryPage(pageNum, pageSize); ModelAndView mv new ModelAndView(productList); mv.addObject(page, page); return mv; } }这段代码有四个地方值得说明。第一RequestParam(defaultValue 1)表示页面没传页码时默认查第一页避免空指针defaultValue 12配合商城常见的每行4个商品、3行的首页布局。第二queryPage返回的是PageInfoProduct这是MyBatis分页插件PageHelper的返回类型里面除了商品列表还有pageNum、pages、total等分页信息JSP直接通过${page.pageNum}就能取。第三ModelAndView传入的字符串productList不是完整路径它要配合视图解析器使用。第四如果你在这个项目里看到Controller返回String并单独用model.addAttribute加数据效果相同只是写法更旧一些。视图解析器的核心配置在spring-mvc.xml里它决定了JSP放在哪个目录、用什么后缀bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /beanprefix和suffix两个参数一旦写错项目会报“Unable to resolve view with name productList”。把JSP放进WEB-INF/jsp目录而不是webapp根目录是这类项目的标准安全做法WEB-INF下的资源不能被浏览器直接输入URL访问所有页面入口都必须经过Controller转发访问控制全部收口在后端。新手常见的坑是把productList.jsp直接放在webapp根目录结果Controller绕过了视图解析器页面数据取不到权限校验也失效。2.2 Spring容器管什么Service和Mapper的装配Spring在SSM里的角色是容器负责把Service、Mapper、事务管理器这些对象创建出来并注入到需要它们的地方。母婴商城里ProductServiceImpl要被注入到ProductControllerProductMapper接口要被注入到ProductServiceImpl这套关系既可以写在XML里也可以用注解简化。老项目通常用applicationContext.xml开启注解扫描和事务。context:component-scan base-packagecom.babymall.service/ bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/base-package必须覆盖到ServiceImpl所在的包漏掉的话Service不被扫描Controller里Autowired注入时会直接报NoSuchBeanDefinitionException。事务管理器的dataSource引用要和MyBatis的SqlSessionFactoryBean共用同一个数据源否则事务和SQL走的是两套连接Transactional注解形同虚设。这些配置决定了母婴库存扣减和订单创建能否在同一事务里成功或同时回滚。MyBatis部分的配置关注一个容易混淆的点Mapper接口扫描和XML文件位置是否匹配。如果接口在com.babymall.mapperXML放在src/main/resources/mapper下配置大致是bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.babymall.mapper/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /beanclasspath:mapper/*.xml这个通配符是排查重点。Maven项目里如果XML放在src/main/java目录下且pom.xml没有配置resources包含XML打包时XML不会进classes目录运行时MyBatis会报“Invalid bound statement (not found)”。正确位置是src/main/resources/mapper/编译后自动进入classes根目录。2.3 为什么毕业设计仍然大量使用SSMJSP既然Spring Boot加Vue已经成为主流为什么这类母婴项目还在用SSMJSP客观上很多高校的Java Web课程仍然按“Servlet—JSP—SSM”的顺序教学JSP是课程大纲要求的知识点。主观上SSMJSP这种服务端渲染模式对“数据库课程设计”类题目有一个不可替代的优势业务逻辑、SQL、页面三者都在一个应用里演示时只需开Tomcat和MySQL不需要额外启动Node环境或跨域处理。你要能说清这个选型理由而不是只会说“项目要求的”。3. JSP页面怎么取数商品列表的JSTL渲染、EL取值与分页传参3.1 用JSTL渲染母婴商品列表的最小片段Controller把PageInfoProduct放进Model后JSP的任务是把Java对象变成HTML。直接写%%脚本片段是十年前的做法维护性很差。SSM项目的JSP页面标准写法是JSTL核心标签库加EL表达式在JSP头部引入两个taglib% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfn urihttp://java.sun.com/jsp/jstl/functions % div classproduct-grid c:forEach items${page.list} vargoods varStatusst div classproduct-card span classindex${st.count}/span a href${pageContext.request.contextPath}/product/detail?id${goods.productId} img src${goods.coverImg} alt${goods.productName}/ /a h3${goods.productName}/h3 p classprice${goods.price}/p c:if test${goods.stock 5} span classtag仅剩${goods.stock}件/span /c:if /div /c:forEach /div这个片段里${page.list}对应Controller里mv.addObject(page, page)的keyitems指定要遍历的集合vargoods是循环变量名后面所有的${goods.xxx}都指向Product实体属性。pageContext.request.contextPath取到项目部署路径比如/babymall这是JSP拼接URL最可靠的方式写死绝对路径会导致项目换context后图片和链接全部404。varStatusst的st.count从1开始计数可以直接当作序号列不需要在Controller里额外算序号。${goods.stock 5}是EL表达式里的比较运算对应促销场景“低库存提醒”这个逻辑写在页面里还是Service里都行但只影响展示的话放JSP更简单避免为一个小标签改Controller。3.2 JSP经编译后的class文件保存在哪里理解JSP的本质是理解它会被Tomcat编译成Servlet。第一次访问一个JSP时Tomcat的Jasper引擎把它转成Java源文件再编译成class文件。很多人在IDEA里找不到JSP页面更新后没生效的原因就是不知道class文件产物在哪个目录。Tomcat 8/9常规启动方式下路径是{Tomcat安装目录}/work/Catalina/localhost/{应用context路径}/org/apache/jsp/例如应用context为/babymall则编译产物在work/Catalina/localhost/babymall/org/apache/jsp/下会看到product_list_jsp.java和product_list_jsp.class这样的文件文件名由JSP路径和文件名生成。使用IDEA内置Tomcat时这个work目录在{user.home}/.IntelliJIdea{版本}/system/tomcat/下按项目名分目录存放。一旦JSP改动后浏览器还是旧页面优先去这个目录下看对应class文件的修改时间若时间没更新说明JSP根本没触发重新编译。修改JSP却看不到效果时除了手动清除Tomcat的work目录还可以在Tomcat的conf/web.xml里检查JSP servlet配置确保development参数为true这样JSP文件修改时间变化时会自动重新编译生产环境才把它设为false。这个“development”参数是JSP调试场景下最该先检查的一项。3.3 分页参数从哪来、传回哪去母婴商品可能上百条一次全查会把首页拖慢所以分页是SSM商城的标配功能。分页链路分三步JSP页面把当前页码传给ControllerController调用PageHelper设置分页参数MyBatis查询后返回带分页信息的PageInfo。Controller接收参数的写法有两种常见风格RequestMapping(/list) public ModelAndView list(RequestParam(value pageNum, defaultValue 1) int pageNum, RequestParam(value categoryId, required false) Integer categoryId) { PageHelper.startPage(pageNum, 10); ListProduct productList productService.listByCategory(categoryId); PageInfoProduct pageInfo new PageInfo(productList); ModelAndView mv new ModelAndView(productList); mv.addObject(page, pageInfo); return mv; }PageHelper.startPage(pageNum, 10)必须在第一次查询前调用它通过MyBatis拦截器给紧接着的那条SQL自动拼上LIMIT提前调用或中间又执行了其他SQL都会导致分页错乱。new PageInfo(productList)可以把查询结果包装成带total、pages、hasNextPage等属性的对象这些正是JSP底部页码条要用的数据。分页的页码条在JSP里通常用JSTL生成注意判断首页和末页的禁用状态c:if test${page.hasPreviousPage} a href${pageContext.request.contextPath}/product/list?pageNum${page.prePage}categoryId${categoryId}上一页/a /c:if span第 ${page.pageNum} / ${page.pages} 页/span c:if test${page.hasNextPage} a href${pageContext.request.contextPath}/product/list?pageNum${page.nextPage}categoryId${categoryId}下一页/a /c:if这里的prePage和nextPage是PageInfo自带的属性翻页时要手动把categoryId拼接进URL否则从第1页翻到第2页会丢失分类筛选条件。这是页面分页最常见的功能性bug答辩时被问“为什么翻页后内容变了”也源于此。4. 数据库设计先行母婴商品的分类、购物车与订单快照建模4.1 母婴用品商城最少需要的7张表数据库是这个标题里权重最高的部分。母婴商城区别于普通电商的地方在于商品有严格的分类层级奶粉、尿不湿、玩具、辅食、童装同一商品有多个规格段位、尺码、口味订单需要记录下单时的商品快照。按常规第三范式设计最少需要7张表支撑“注册—浏览—加购—下单—收货”完整链路表名作用必须包含的关键字段user会员账号user_id, username, password, phoneproduct_category商品分类category_id, category_name, parent_id, sort_noproduct商品表product_id, category_id, product_name, price, stock, cover_img, statuscart_item购物车项cart_id, user_id, product_id, quantityorders订单主表order_id, order_no, user_id, total_amount, status, create_timeorder_item订单明细item_id, order_id, product_id, product_name, product_price, quantitydelivery_address收货地址address_id, user_id, receiver_name, receiver_phone, detail_address这张表去掉任何一张业务链路都会断。delivery_address容易被忽略但没有地址下单页面就无法选择收货人。cart_item不能设计成只存商品ID加数量还要冗余price字段避免用户加购后商品价格变动导致结算金额对不上。4.2 商品表和分类表的建表SQL与字段解释CREATE DATABASE babymall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE babymall; CREATE TABLE product_category ( category_id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL, parent_id INT DEFAULT 0, sort_no INT DEFAULT 0 ) ENGINEInnoDB; CREATE TABLE product ( product_id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, product_name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, cover_img VARCHAR(255), status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;数据库字符集必须用utf8mb4而不是utf8因为商品名称或用户填写的收货地址里可能出现Emoji表情utf8编码存不了四个字节的字符会直接报“Incorrect string value”错误。price字段用DECIMAL(10,2)绝不能使用FLOAT或DOUBLE浮点类型在金额计算中会产生0.10.20.30000000000000004这类精度问题下单金额对不上账就是这里引起的。create_time加DEFAULT CURRENT_TIMESTAMP是省事又可靠的做法插入时不用手动set时间。product表的status字段用来做上下架TINYINT的1表示上架、0表示下架列表查询都要带上status 1条件这个字段比直接删除商品记录更符合电商逻辑能保留历史订单对商品的引用。4.3 订单与订单明细为什么要冗余商品快照MySQL建表规范告诉你尽量消除冗余但订单设计反其道而行之。order_item表里既存product_id又冗余product_name、product_price、product_image这不是多余的存储浪费而是电商系统的经典快照设计。用户下单后如果管理员把商品改价或改名历史订单里的商品信息不该跟着变否则售后对账时连当时卖多少钱都说不清。下订单时从product表查出商品信息然后原样插入order_itemINSERT INTO order_item (order_id, product_id, product_name, product_price, quantity, product_image) SELECT #{orderId}, product_id, product_name, price, #{quantity}, cover_img FROM product WHERE product_id #{productId};注意这条INSERT是“查出来再插”的写法联表查询把product表的当前值一次性写入明细表。事务中还要同步扣减库存UPDATE product SET stock stock - #{quantity} WHERE product_id #{productId} AND stock #{quantity};WHERE stock #{quantity}这行是防超卖的关键。先SELECT再在Java里判断库存会导致并发下单时超卖把判断条件放进UPDATE让数据库的行锁来保证同一商品不会被两个事务同时扣减到负数。受影响行数为0时说明库存不足直接抛异常让整个订单回滚。4.4 商品列表“数据库增删改查”在MyBatis里的另一半标题里的“数据库”落到代码上是MyBatis的XML映射。单表增删改查IDE可以自动生成但带条件过滤的查询必须手写动态SQL。商城首页的“按分类查看”、“按名称搜索”都是同一个查询方法的扩展select idselectByCondition resultTypecom.babymall.entity.Product SELECT product_id, category_id, product_name, price, stock, cover_img, status FROM product where if testcategoryId ! null AND category_id #{categoryId} /if if testproductName ! null and productName ! AND product_name LIKE CONCAT(%, #{productName}, %) /if if teststatus ! null AND status #{status} /if /where ORDER BY create_time DESC /selectwhere标签会自动去掉第一个条件前的AND这段代码的巧妙之处在于只传分类时生成WHERE category_id ?只传商品名时生成WHERE product_name LIKE ?什么都不传时where标签自动消失生成不带条件的全表查询。使用CONCAT(%, #{productName}, %)而不是%${productName}%是为了配合MyBatis的预编译防止SQL注入。ORDER BY create_time DESC让新上架的商品排前面符合商城的展示习惯。5. 本地跑通SSMJSPJDK/Tomcat/IDEA配置顺序与三个常见报错5.1 环境配对先理清版本错乱是最大的坑这类SSM项目的报错有一半以上不是代码问题而是版本环境不兼容。最典型的是Tomcat 10及以上版本把Servlet API从javax.servlet迁移到了jakarta.servlet而SSM项目里的spring-web、JSP、web.xml全是按javax写的直接部署会抛NoClassDefFoundError: javax/servlet/...。常见的可靠组合JDK 8或11Tomcat 8.5或9.0Maven 3.6.xSpring 4.3或5.0系列MyBatis 3.4或3.5系列。IDEA里配置Tomcat时在Run Configuration窗口的Server标签页可以直接看到Tomcat版本和pom.xml里的javax依赖对不上就果断换版本。Java环境变量的配置是跑通前的第一道门槛。JDK装完后系统变量里要新建JAVA_HOME指向JDK安装根目录在Path里追加%JAVA_HOME%\bin然后在命令行执行java -version验证。很多项目启动失败其实是IDEA内置的JBR版本和项目编译级别不一致这时在Project Structure里把Project SDK选成本地JDKProject language level选成8或11。5.2 IDEA导入SSM项目并连接Tomcat的最小步骤Idea创建的JSP项目和普通Maven项目的目录结构是一样的区别在于Web资源放在src/main/webapp下。导入SSM项目的完整过程可以压缩为五个步骤打开IDEA选择File - New - Project from Existing Sources选中Maven项目根目录导入方式选“Import Maven Project”。等待Maven下载依赖。若jar包下载失败检查Maven的settings.xml配置的镜像源是否可用。在Project Structure - Artifacts里确认有war exploded产物没有则点号新建Web Application Exploded把webapp目录选为Web Resource Directory。打开Run - Edit Configurations点号新增Tomcat Server - Local在Deployment标签页把war exploded添加进去Application context设置为/babymall。启动前确认MySQL服务已开启且已执行数据库脚本然后点运行。IDEA连接Tomcat后会自动把编译产物部署到Tomcat的webapps目录下。Application context的值决定了访问路径这个值必须和数据库连接等配置无关但和JSP里${pageContext.request.contextPath}的取值直接相关。如果你在JSP里写死/babymall/xxx而实际context是/babymall页面能开但样式和图片全挂。5.3 数据库连接配置db.properties与连接池参数SSM项目的数据库连接集中在jdbc.properties里spring-mybatis.xml通过context:property-placeholder加载。这个文件是部署时唯一必须根据本机环境修改的配置jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/babymall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456serverTimezoneAsia/Shanghai是JDBC 8必须带的参数缺失会报“The server time zone value”异常这是因为新版驱动默认读取系统时区而MySQL 8的时区表是空的。useSSLfalse是为了避免本地开发时SSL握手警告生产环境才需要开启。密码为空时写jdbc.password即可不要留空格。如果项目用Druid连接池还会多出initialSize、maxActive等参数本地开发initialSize5够用调大不会提升性能反而增加启动耗时。5.4 三个常年出现的运行期报错与定位思路第一个报错是启动时“ClassNotFound”或“BeanCreationException”原因是某些依赖没下载完整或JDK版本不匹配。定位方法是看异常栈里找不到的类名属于哪个jar然后去Maven仓库看对应目录下jar包是否损坏损坏就删除该目录重新加载。第二个报错是“Invalid bound statement (not found)”这是Mapper接口和XML没配对按前面说的检查XML是否在src/main/resources/mapper目录下以及mybatis.mapper-locations路径是否一致。第三个是访问首页报404优先看IDEA控制台的“Deployment”输出确认实际部署的context路径再对比浏览器地址栏同时到{Tomcat}/webapps下看war包是否真的解压出来了。排错时把Tomcat的日志开关打开能省一半时间。在Tomcat的conf/logging.properties里把org.apache.catalina.core.ContainerBase.[Catalina].level调成FINE可以看到每个请求被哪个Servlet处理的详细信息这比在页面上猜要准确得多。部署和JSP的问题解决后只要数据库脚本能正常导入、登录能查到用户这个项目就算真正在本地站稳了。6. 在JSP商城项目里加一个价格区间搜索并验证分页当你把上述所有环节跑通后最加分的改造是往商品模块加一个“按价格区间筛选”的功能。这个功能涉及Controller参数接收、Mapper动态SQL和JSP表单回显三个层面恰好串起前面的知识点。先在ProductMapper.xml里扩展现有的查询条件if testminPrice ! null AND price gt; #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if注意gt;和lt;是XML里的转义写法直接写会被XML解析器当成标签开始符号整个映射文件加载时报错。minPrice和maxPrice都是BigDecimal类型在Controller接收时用RequestParam(required false)声明为可选参数。Controller里需要改动两处方法签名增加两个参数以及把这两个参数传回页面用于表单回显。RequestMapping(/list) public ModelAndView list(RequestParam(defaultValue 1) int pageNum, RequestParam(required false) BigDecimal minPrice, RequestParam(required false) BigDecimal maxPrice, RequestParam(required false) Integer categoryId) { PageHelper.startPage(pageNum, 10); ListProduct list productService.search(categoryId, minPrice, maxPrice); PageInfoProduct page new PageInfo(list); ModelAndView mv new ModelAndView(productList); mv.addObject(page, page); mv.addObject(minPrice, minPrice); mv.addObject(maxPrice, maxPrice); mv.addObject(categoryId, categoryId); return mv; }页面表单的写法要保证筛选后翻页条件不丢form action${pageContext.request.contextPath}/product/list methodget input typehidden namecategoryId value${categoryId}/ input typenumber nameminPrice value${minPrice} placeholder最低价/ input typenumber namemaxPrice value${maxPrice} placeholder最高价/ button typesubmit筛选/button /form隐藏的categoryId保证价格筛选时分类条件不丢value${minPrice}是回显用户上次输入的值避免每次筛选后输入框清空这是用户体验细节。翻页链接里也要拼上minPrice和maxPrice和前面说的categoryId是同一个道理。验证时打开商品列表页先选一个分类再输入50到200的价格范围点击筛选观察URL变成/product/list?categoryId2minPrice50maxPrice200pageNum1列表数据与SQL条件一致然后翻到第2页确认URL中仍然保留筛选参数最后把数据库里某条商品的价格改成0或9999验证边界值是否被正确过滤。价格区间搜索做完整个SSMJSP的闭环——页面发起请求、Controller接参、MyBatis动态SQL查库、JSP回显结果——就全部走通并有了可演示的增量功能。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进