简介:基于SSM+JSP的母婴用品网站项目资源包,是一套适合Java方向毕业设计、课程设计与期末大作业的完整实战项目,面向有一定Java基础、希望系统了解SSM整合开发流程的读者,覆盖从需求理解、模块拆分到编码部署的常见开发环节。项目除完整前后端源码外,还附带数据库脚本、项目配置文件与相关软件工具,且关键代码包含注释,部署难度较低,便于快速还原运行环境。资源共1370个文件,压缩包大小17.89MB,其中127个Java类与169个JSP页面构成核心业务逻辑和页面展示,配合JS、CSS、Bootstrap等前端资源以及SQL数据库脚本,目录结构清晰,可按模块查阅,也适合按个人节奏逐模块研读。目前已有53人学习下载。通过该项目可以重点练习SSM三层框架整合、JSP与后端的数据交互、MySQL表结构设计以及Tomcat部署等关键技能;完整的母婴商城业务代码既适合用于毕业设计演示与课程答辩,也便于作为后续二次开发的基础。
1. SSM+JSP的母婴用品网站:为什么这套老技术栈仍是毕设的稳妥选择
每到毕业设计季,总有人对着「基于SSM+JSP的母婴用品网站」这种题目犹豫:都什么年代了还在用JSP?但打开压缩包看到源码、数据库和教程三件套齐整地躺在里面,又觉得香。这个题目本质上是一个完整的Java Web全栈练习——SSM负责业务逻辑和持久层,JSP负责服务端渲染页面,MySQL存数据,串起来就是一条从前端页面到数据库的标准链路。它能解决的是你「不知道毕设做什么、又怕做不出来」的焦虑,适合Java方向想快速落地一个可用系统的学生,也适合刚学完SSM想找个完整项目练手的开发者。这个组合最大的价值不在于技术新,而在于每个环节都能独立验证、出了问题网上资料一抓一大把。
2. 从压缩包到能访问的首页:环境版本与最小启动路径
2.1 环境匹配:JDK、Tomcat、MySQL先对齐版本
拿到「SSM+JSP母婴用品网站」项目包之后,第一步不是急着开IDEA,而是先把运行时环境理清楚。这类毕设项目大多是老版本配出来的,常见组合是JDK 1.8、Maven 3.6以内、Tomcat 8.5或9.0、MySQL 5.7。用太新的JDK(比如17、21)跑老项目,经常会报出Java Base模块相关的奇怪错误,这不是你代码写得有问题,而是Tomcat版本和JDK版本不兼容。先用命令确认本机环境:
java -version mvn -v mysql --version逻辑说明:三条命令分别确认JDK、Maven、MySQL的版本号。java -version能看到是1.8还是更高版本,如果显示的是openjdk 17或者更高,后面启动Tomcat报错概率会直线上升。mvn -v检查Maven是否装了,如果没装,后面编译打包会很痛苦。mysql --version确认数据库版本,5.7和8.0在连接驱动和密码加密方式上有差别,项目里的jdbc.properties里写的驱动如果是com.mysql.jdbc.Driver,配MySQL 8.0会有兼容问题。
参数说明:JDK建议直接锁1.8,不要犹豫。Tomcat如果项目里没有特定的版本要求,8.5是最稳的——它对JSP 2.3和EL 3.0的支持和老代码兼容性最好。MySQL用5.7能省掉很多连接层面的麻烦,如果本地只有8.0,后面连接时记得把驱动换成com.mysql.cj.jdbc.Driver,URL里加上serverTimezone=Asia/Shanghai,否则报时区错误会让你在深夜怀疑人生。
环境对齐之后,把项目包解压。注意看目录结构,通常是一个标准的Maven工程,src/main/java、src/main/resources、src/main/webapp该有的都有。如果解压后只有一个war包没有源码文件夹,说明这是编译好的产物,开发调试会很别扭。正常毕设包里应该是源码+SQL脚本+教程文档三部分,SQL脚本一般在db或sql目录下,文件名类似mom_shop.sql或baby_shop.sql。
2.2 初始化数据库:SQL脚本导入与连接配置
数据库是这套系统的地基,商品、订单、会员全要靠表撑着。打开SQL脚本先浏览一遍,重点看建库语句和插入语句的格式。很多项目的SQL脚本是用5.7导出的,到了8.0里执行会出现Unknown collation这类报错。先登录MySQL,创建一个专用账号并授权,然后用命令行导入脚本:
mysql -u root -p CREATE DATABASE IF NOT EXISTS baby_shop DEFAULT CHARSET utf8mb4; USE baby_shop; SOURCE /path/to/baby_shop.sql;逻辑说明:CREATE DATABASE先建库,utf8mb4保证表情符号和生僻字不丢,母婴用品商品描述里经常有特殊符号,用utf8会翻车。SOURCE是MySQL从外部文件执行SQL的命令,路径要写绝对路径,不要写相对路径,否则MySQL会跑到自己的数据目录下找文件,你一定找不到。
导入完成后,用SHOW TABLES;确认表都建出来了。常见的表有会员表、商品表、商品分类表、购物车表、订单表、订单明细表、评论表,表格数量在8到15张之间都正常。如果表数特别少(比如只有两三张),说明是简化版,功能可能不够撑答辩。
接下来改数据库连接配置。在src/main/resources目录下找jdbc.properties或db.properties,把连接信息改成你自己数据库的用户名和密码:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/baby_shop?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=你自己的密码逻辑说明:这份properties文件是MyBatis数据源配置的入口,Spring容器启动时会读取它来创建数据源。useUnicode=true&characterEncoding=utf8这两参数必须带上,否则后面页面展示商品名会出现中文乱码,这算是SSM项目的经典坑。如果你的MySQL是8.0,驱动和URL改成上面说的那种写法。
参数说明:localhost是数据库地址,3306是默认端口,/baby_shop对应你创建的库名。user和password不一定用root,建议新建一个普通账号专门给项目用,权限只给SELECT、INSERT、UPDATE、DELETE就够,不要图省事直接拿root跑项目,这个习惯能让你以后在公司里少挨骂。
2.3 部署到Tomcat:IDEA配置与war包两种方式
环境配好、数据库通了,就差把项目挂到Tomcat上跑。常见做法是用IDEA里的Tomcat插件直接跑,配置上有个关键点:打开Run/Debug Configurations,新建Tomcat Server,选Local,在Deployment标签页把Artifact加进去。注意Artifact的类型一定是war exploded,不是war——前者直接拿编译目录跑,改JSP不用重新打包,后者每次改完页面都要重新构建一次,调试效率低到让你怀疑人生。
如果你习惯用传统方式部署,也可以先打包再扔进Tomcat的webapps目录:
mvn clean package -DskipTests cp target/baby_shop.war /path/to/tomcat/webapps/ cd /path/to/tomcat/bin ./startup.sh逻辑说明:mvn clean package先把旧的编译产物清掉,再打包成war,-DskipTests跳过测试类,避免测试代码报错导致打包失败。war包复制到webapps目录后,Tomcat启动时会自动解压部署。启动后浏览器访问http://localhost:8080/baby_shop,URL里的baby_shop是war包的文件名,跟项目上下文路径有关系,改名就要改访问路径。
启动之后会看到Tomcat的日志刷刷往上滚,最怕的是最后一行不是Server startup in XXX ms,而是SEVERE开头的报错——这时候不要慌,去logs/catalina.out看完整堆栈。第一行带Caused by的异常往往才是根因,前面几行都只是表象。
到这里,一个环境OK的SSM+JSP项目就能跑出首页了。首页通常会推荐一批母婴商品,配着轮播图和分类导航。但首页出来了不代表系统就真的能交付,业务功能验证才是后面几章的重点。
3. 母婴用品业务的数据建模:商品、订单、会员三张核心表怎么设计
3.1 商品与分类表:为什么需要冗余字段但不冗余数据
打开数据库里最核心的几张表,通常能看出当初创作者的设计水平。母婴用品网站的商品和普通电商没本质区别,分类表与商品表是父子关系。典型的结构是分类表存一级分类和二级分类,商品表通过category_id挂在分类上。设计要点在于分类表不要只存一个ID,还要存parent_id,用自关联的方式支持两级分类,母婴用品里「奶粉 » 婴儿奶粉」这种层级才放得下。
商品表的核心字段一般是这样:
CREATE TABLE `product` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL COMMENT '商品名称', `category_id` INT(11) NOT NULL COMMENT '所属分类', `subtitle` VARCHAR(200) DEFAULT NULL COMMENT '副标题', `main_image` VARCHAR(500) DEFAULT '' COMMENT '主图URL', `detail` TEXT COMMENT '商品详情', `price` DECIMAL(10,2) NOT NULL COMMENT '价格', `stock` INT(11) NOT NULL COMMENT '库存', `status` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '1上架 2下架', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;逻辑说明:price用DECIMAL(10,2)而不是FLOAT,是电商系统的常识——浮点数存价格会在精度上出幺蛾子,比如19.99被存成19.989999。main_image直接存URL字符串,不走外键关联图片表,这是为了查商品列表时少一次关联查询,属于用空间换时间的取舍。status用TINYINT而不是VARCHAR('上架'),是为了后续SQL条件查询方便,WHERE status=1就能筛出上架商品。
参数说明:DECIMAL(10,2)总长度10位,小数占2位,上限是千万级别,母婴商品价格够用。stock存的是可售库存量,一个商品真正能卖多少,得在订单里判断库存扣减,这里存的是当前余量。很多新手在detail字段上直接存富文本HTML,把页面样式写死在里面——这没问题,但要注意JSP页面取出来时要配合${product.detail}的EL表达式输出才能正常渲染,这在后面JSP章节会展开。
3.2 订单与购物车:事务边界划在哪张表上
订单是这套系统里最见功力的部分。母婴用品的购买决策链路长,会员可能先把商品丢进购物车,过几天再下单,所以购物车表和订单表要拆开。但拆开之后事务怎么控制就成了关键——点击结算时,是先清购物车还是先建订单?正确答案是:先建订单和订单明细,再清购物车,两者放在同一个事务里。如果先清购物车再建订单,万一订单创建失败,用户的购物清单就没了,这属于数据一致性上的翻车。
订单表通常会拆成两张:订单主表和订单明细表。主表存收货人、总价、订单状态;明细表存每个商品ID、购买数量、当时的快照价格。快照价格是重点,用户在结算一刻看到的价格必须被冻结在订单里,否则以后改商品价格,历史订单金额也跟着变,财务上说不通。对应的建表语句大致如下:
CREATE TABLE `order_main` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单号', `user_id` INT(11) NOT NULL COMMENT '下单用户', `total_price` DECIMAL(10,2) NOT NULL, `status` TINYINT(4) NOT NULL DEFAULT '10' COMMENT '10待支付 20已支付 30已发货 40已完成 50已取消', `receiver_name` VARCHAR(50) NOT NULL, `receiver_phone` VARCHAR(20) NOT NULL, `receiver_address` VARCHAR(200) NOT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `order_item` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `order_id` INT(11) NOT NULL, `product_id` INT(11) NOT NULL, `product_name` VARCHAR(100) NOT NULL, `product_image` VARCHAR(500) DEFAULT NULL, `current_price` DECIMAL(10,2) NOT NULL COMMENT '下单时价格快照', `quantity` INT(11) NOT NULL COMMENT '购买数量', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;逻辑说明:order_no用UNIQUE索引约束,是为了防止并发下单时生成相同订单号。毕设项目用时间戳加随机数就能扛住,但真正上线要配分布式ID方案——这套单机系统不需要,别过度设计。order_item里冗余了product_name和product_image,看似违背了「不冗余」的数据库规范,但实际查询订单详情时不用再去商品表join,而且商品标题改了也不影响历史订单展示,这在电商行业是标准做法。
参数说明:order_main的status设计成数字状态机而不是简单的0/1,是为了后续订单流转能加状态。毕设答辩时如果能说出「这里我预留了状态机扩展位,后续接入支付后可以加60退款中、70退款完成」这种话,比堆功能更能体现设计感。订单表还必须有receiver_*三个字段,因为收货信息是下单那一刻的抄录,跟用户表里的最新收货地址是两回事。
3.3 MyBatis逆向工程:Dao层代码从哪来
表结构定了,Dao层代码有三种来源:手写SQL、MyBatis逆向工程生成、通用Mapper。这个项目里的Dao层大概率是逆向工程或者手写SQL混搭。手写SQL的好处是每一条select语句你都看得懂,答辩时老师问「你这个查询是怎么实现的」你能答得上来。坏处是表一多,重复的增删改查能写到吐。
逆向工程生成的是基础的CRUD,复杂的多表关联还是要自己补。比如查询商品列表需要连分类表拿分类名称,这种SQL逆向工程搞不定,得自己在Mapper XML里写:
<select id="selectProductWithCategory" resultType="map"> SELECT p.id, p.name, p.price, p.main_image, c.name AS category_name FROM product p LEFT JOIN category c ON p.category_id = c.id WHERE p.status = 1 ORDER BY p.id DESC </select>逻辑说明:LEFT JOIN保证即使商品分类被删除(理论上不应该删,但系统里可能会发生),商品记录依然能查出来。resultType="map"适合临时查询不需要建VO的场景,但如果这个查询要被多个地方复用,建议建一个专门的ProductWithCategoryVO类,持久层返回map这个偷懒行为在毕设项目里能过,在工作里会被同事骂死。
4. SSM三件套的整合配置:Spring、SpringMVC、MyBatis逐条拆解
4.1 web.xml是总开关:DispatcherServlet与编码过滤器
SSM项目的入口不在Java类里,而在web.xml。别看这个文件平时不动它,里面每一行都有自己的使命。毕设项目的web.xml里有两样东西必须存在:ContextLoaderListener监听器用来加载Spring根容器,DispatcherServlet用来接管所有请求。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>逻辑说明:ContextLoaderListener负责加载Spring的根容器,里面管的是Service、Dao、数据源这些业务层组件。DispatcherServlet单独加载spring-mvc.xml,只管Controller和视图解析。这两个容器有父子关系,子容器能看见父容器的Bean,反过来不行。如果把Controller的扫描配置塞到applicationContext里,启动时会报Bean找不到的错。
参数说明:CharacterEncodingFilter的url-pattern要写成/*而不是/,/*才能拦到所有请求包括JSP,/只会拦到非JSP的请求。这个过滤器必须在最前面,如果顺序放错,POST请求的中文参数照样乱码——曾经有人把过滤器写到别的Filter后面,排查了一下午,最后发现是过滤器顺序问题。
spring-mvc.xml是SpringMVC的大脑,管Controller扫描、视图解析器、静态资源放行三件事:
<mvc:annotation-driven/> <context:component-scan base-package="com.babyshop.controller"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:resources mapping="/static/**" location="/static/"/>逻辑说明:annotation-driven开启注解驱动,让@RequestMapping、@RequestBody这些注解生效。component-scan只扫controller包,千万别把service包也扫进来——Spring容器已经管了Service,SpringMVC再扫一遍会出事务失效的坑。InternalResourceViewResolver的意思是Controller返回的"index"字符串,会拼成/WEB-INF/jsp/index.jsp去找页面。mvc:resources把/static/**路径下的静态资源放行给Tomcat默认Servlet处理,不然CSS、JS全会被DispatcherServlet拦下来返回404。
参数说明:prefix和suffix的路径必须对应你的实际目录。有些项目JSP页面放在src/main/webapp/WEB-INF/jsp下,有些放在src/main/webapp根下,放在WEB-INF下更安全——外部浏览器没法直接访问WEB-INF目录里的文件,所有页面必须经过Controller跳转,这也是一种安全防护。
4.2 数据源与事务:数据库连接池两个参数最容易配错
MyBatis与Spring整合的核心是SqlSessionFactoryBean,它的数据源决定了所有SQL从哪取连接。常见配置如下:
<context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="initialSize" value="5"/> <property name="maxActive" value="20"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.babyshop.dao"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>逻辑说明:property-placeholder用来读取jdbc.properties,让数据库配置和Spring配置分离,换环境只改properties文件,不用动XML。DruidDataSource是阿里连接池,比裸的JDBC强在支持监控SQL、防SQL注入、池化管理连接。SqlSessionFactoryBean的mapperLocations指向XML文件的位置,basePackage指定Mapper接口所在包,MyBatis自动生成实现类并把它们注册成Spring Bean。
参数说明:initialSize=5是连接池启动时就预创建的连接数,maxActive=20是最大连接数。这里有个典型反模式:事务方法里SELECT操作拿着连接不释放,maxActive设小了之后,并发一多就会报Cannot get a connection,这个问题在第五章会细说。DruidDataSource忘了配timeBetweenEvictionRunsMillis会让空闲连接被MySQL服务端断开,重启应用时第一次请求必报Connection is not available。
4.3 事务是毕设答辩的高频提问点:@Transactional在Service层怎么落
事务配置到位了,但真正决定数据一致性的还是在Service实现类里有没有正确地加@Transactional。这个注解加的位置很有讲究——必须加在public方法上、必须通过Spring代理调用才能生效。也就是说,同一个类里methodA调用methodB,就算methodB标了@Transactional也不生效,因为调用没有经过代理对象。
@Service public class OrderServiceImpl implements OrderService { @Resource private OrderMapper orderMapper; @Resource private OrderItemMapper orderItemMapper; @Resource private CartMapper cartMapper; @Override @Transactional(rollbackFor = Exception.class) public void createOrder(OrderMain order, List<OrderItem> items, List<Integer> cartIds) { orderMapper.insert(order); for (OrderItem item : items) { orderItemMapper.insert(item); } cartMapper.deleteBatchByIds(cartIds); } }逻辑说明:@Transactional(rollbackFor = Exception.class)里的rollbackFor很重要。Spring默认只对RuntimeException回滚,Exception(受检异常)不会自动回滚。毕设项目里Service层经常自己抛一些受检异常,不写rollbackFor的话,第一个商品插进去了,第二个失败,整个订单数据就残了。createOrder内部三个操作——插订单表、插明细表、清购物车——必须保证全成功或全失败,这才能跟答辩老师讲清楚事务边界。
参数说明:@Transactional的propagation默认REQUIRED就够了,不要乱调。isolation保持默认的数据库级别,除非你能讲清楚脏读、不可重复读的差异,否则在答辩时不要主动提隔离级别,给自己挖坑。
5. JSP项目常见问题排查:404、乱码、热更新失效的五个真实场景
5.1 页面404但Tomcat没报错:请求被DispatcherServlet吃掉了
现象:启动不报错,浏览器访问http://localhost:8080/baby_shop/index返回404,但http://localhost:8080/baby_shop/首页能出来。
原因:DispatcherServlet的url-pattern配成/后,所有请求都会进SpringMVC,Controller里如果没写@RequestMapping("/index"),或者写了但返回的视图名不对,就会404。还有一种隐蔽情况是Controller返回了页面路径,但路径拼出来不对——视图解析器前缀是/WEB-INF/jsp/,你返回了"index",可实际JSP文件在/WEB-INF/jsp/index.jsp吗?文件不存在也会404,但Tomcat日志里不报错。
解决:打开浏览器开发者工具的Network面板,看请求的响应码。如果是404,先确认访问路径和@RequestMapping里的值是否完全一致,大小写、斜杠都不能差。再检查JSP文件是否存在、路径是否匹配视图解析器前缀后缀。
5.2 商品名显示问号:CharacterEncodingFilter没拦到POST
现象:后台录入商品时填的中文名称,在商品列表页显示成???。
原因:录入商品的表单是POST提交,Tomcat 8.5以上对POST请求默认按ISO-8859-1解码,中文会变成乱码。虽然web.xml里配了CharacterEncodingFilter,但如果顺序在其他Filter之后,或者后端JSP里用的还是老式的request.setCharacterEncoding("UTF-8")被过滤器覆盖,都会出问题。
解决:把CharacterEncodingFilter的url-pattern改成/*并放在Filter链第一位。同时确认页面JSP顶部加了<%@ page contentType="text/html;charset=UTF-8" language="java" %>,数据库连接URL里也带了characterEncoding=utf8。这三个地方只要有一处漏掉,中文就会在某一个环节变成乱码。这属于SSM项目最多的血泪经验。
5.3 改完JSP页面不生效:浏览器缓存还是没重新编译
现象:改了JSP里的文字,刷新页面还是旧内容。
原因:两种可能。第一是浏览器缓存,尤其是Chrome对本地静态内容缓存得很狠;第二是IDEA里跑的是war exploded还是war,如果用的是war方式部署,每次改动JSP都要重新打一次war包,否则改的是source目录,Tomcat跑的是webapps里的旧包。
解决:先强刷Ctrl+F5排除浏览器缓存,再改一个JSP文件后启动Tomcat,观察启动日志里有没有重新编译JSP的痕迹。如果没反应,说明部署方式不对,回到IDEA的Run/Debug Configurations里把Deployment改成war exploded。
5.4 应用跑着跑着突然连不上数据库:连接池耗尽了
现象:系统刚开始用都正常,点几个页面后开始报Cannot get a connection之类的错误。
原因:连接池的maxActive设得太小或者连接泄漏了。Druid连接池默认的testWhileIdle为true,但如果一个连接从连接池拿出来之后,在方法里没有被正常归还,连接就会一直占着,池子很快被耗尽。
解决:检查Service方法里有没有自己开Connection、用完不关的情况——用MyBatis一般不会,但如果是自己写了JDBC的代码块就得小心。用Druid监控页面看一眼活跃连接数,如果只增不减就是泄漏,重点查finally里没有conn.close()的方法。处理方式是给连接池加上removeAbandoned=true和removeAbandonedTimeout=60,让Druid自动回收超时未归还的连接——但这是治标,治本是把裸JDBC全换成MyBatis。
5.5 本地能跑,部署到服务器上就404:路径被写死了
现象:本地IDEA跑一切正常,用mvn clean package打成war包部署到Linux服务器上,CSS样式没了、页面跳转404。
原因:JSP页面里用了绝对路径或者相对路径不对。比如直接写href="/static/css/main.css",如果项目部署在Tomcat的根路径,这个没问题;但如果war包是baby_shop.war,访问路径是http://ip:8080/baby_shop/,静态资源就在/baby_shop/static/css/main.css,直接/static开头就找不到了。
解决:JSP里用${pageContext.request.contextPath}拼路径:
<% String path = request.getContextPath(); %> <link rel="stylesheet" href="<%=path %>/static/css/main.css">逻辑说明:getContextPath()返回的是Web应用的上下文路径,比如/baby_shop。它跟部署的war包名联动,不管部署到哪一级路径都不会出错。用JSTL的话也可以写<c:url value="/static/css/main.css"/>,效果一样。后端Controller里return "redirect:/product/list"这种跳转也应该用redirect:${pageContext.request.contextPath}拼好再返回。
排查建议:出现404先看浏览器地址栏URL,再对比静态资源请求的完整URL。凡是静态资源路径里少了/baby_shop前缀,八成就是没有拼接contextPath的错。
6. 毕业设计怎么验收:从注册到下单的核心链路验证与答辩加分项
项目能跑通只是第一步,答辩前的系统验证才是决定成绩的分水岭。别等答辩前一天才临阵磨枪,建议按一条完整的用户链路走一遍并截图存档:新用户注册 → 登录 → 浏览首页商品 → 查看商品详情 → 加入购物车 → 从购物车结算下单 → 在后台订单列表中看到新订单 → 修改订单状态。这条链路走通,系统的主干功能就证明没问题了。
验证时重点关注三个容易出bug的环节。注册时用户名重复有没有提示;下单后库存有没有同步扣减;后台修改商品上下架状态后,前端列表有没有即时变化。这三处直接对应用户表SELECT查重、订单事务里的UPDATE product SET stock=stock-?、商品列表的WHERE status=1条件,是答辩老师最爱追问的点。
如果想让项目在答辩时脱颖而出,不需要大改架构,加两个小而实用的功能就够:订单列表加分页(用PageHelper插件,三行配置搞定);后台登录加拦截器(HandlerInterceptor验证session里有没有用户)。这两个功能都是能画出示意图、能讲清楚原理的加分项,比加一个华丽的首页更有说服力。
我的习惯是留一份验证记录文档,把每一步操作和截图按顺序贴好,再到答辩前把war包重新打一次,确认服务器上从零到跑通全流程没遗漏。这样答辩时老师问「项目部署遇到过什么坑」,你可以把数据库乱码、路径写死这些真实排错经历说成自己踩过又解决的案例——评委想听到的不仅仅是能跑的代码,更想知道你是不是真的动手做过。项目包里的教程写得再细,也不如实操过一次印象深刻。希望帮到你,祝你答辩顺利。
本文还有配套的精品资源,点击获取