ARTICLE DETAIL

资讯详情

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

SSM+MyBatis+MySQL珠宝商城毕设实战:从框架整合到部署排错

SSM+MyBatis+MySQL珠宝商城毕设实战:从框架整合到部署排错 简介一份基于SSMMyBatisMySQLBootstrap开发的珠宝商城购物网站毕业设计项目适合计算机相关专业学生用于课程设计或毕业设计参考。项目实现了登录验证、会员管理、商品类别管理、商品信息管理、订单信息管理等核心业务前后台界面基于Bootstrap响应式框架搭建。压缩包共276个文件主要包含48个Java源文件、48个编译后class文件、46个依赖jar包以及JSP页面、CSS/JS样式脚本、XML配置和SQL数据库脚本等整体大小15.37MB。其中Java源码覆盖控制层、业务层与持久层SQL脚本内含数据库表结构与初始数据可直接导入MySQL运行。项目结构清晰便于二次开发与学习目前已有90人浏览学习适合需要快速搭建商城类Web应用并理解SSM框架整合流程的开发者。1. 毕业设计做珠宝商城为什么选SSMMyBatisMySQLBootstrap这套组合珠宝商城是毕业设计里的经典选题商品造型多、SKU少、客单价高页面展示效果天生比卖图书、卖杂货的商城更容易出彩。而SSM这个组合恰好覆盖了Java Web阶段最常被考察的三件事Spring的IoC与事务、SpringMVC的请求分发、MyBatis的SQL控制。MySQL负责把订单和库存持久化Bootstrap保证前台列表页和数据后台在不写一行前端框架代码的情况下也看得过去。如果你拿到的源码包里已经带Java完整源代码和数据库脚本意味着你几乎不用从零造轮子但论文答辩的难点反而在于你能不能讲清楚为什么这样配置、SQL为什么这样写。我会按SSM整合原理、核心功能编码、MySQL表设计、部署排错的顺序展开最终让你能独立改接口、换页面。2. SSM整合原理与项目初始化理解Spring、SpringMVC、MyBatis各管哪一段在跑通珠宝商城之前先要把SSM三个框架的边界划清楚。Spring是容器管理所有Bean的生命周期、依赖注入和声明式事务SpringMVC是Spring的子模块负责从浏览器请求到达Controller、再从Controller返回页面的那一整套流程MyBatis是数据访问层的地图它把接口方法映射到XML里的SQL。MySQL负责落地数据Bootstrap只是视图层的样式工具理论上换成本地CSS也可以但用它的栅格和组件能省掉大量CSS调试时间。2.1 为什么SSM到现在还是毕业设计的常青树很多学校在Spring Boot成为主流后仍然保留SSM课程原因是SSM的XML配置让配置过程可见。Spring Boot把一切自动装配之后学生很难理解DispatcherServlet是怎么被注册的、事务切面是怎么织入的。而SSM要求你手写web.xml、applicationContext.xml、spring-mvc.xml答辩时老师一眼就能看出你是真会还是只会复制粘贴。在SSM里MyBatis和Spring整合需要借助mybatis-spring这个桥接包。它做的事情是把Spring数据源注入到SqlSessionFactoryBean里再把所有Mapper接口的代理类注册成Spring的Bean。这样你在Service里Autowired一个Mapper接口运行时拿到的是MyBatis自动生成的代理对象。2.2 用Maven搭出SSM项目骨架pom依赖和Spring配置创建一个Maven war包项目第一件事是把依赖坐标写对。这里给出一个经过验证的最小依赖集合注意版本号之间的兼容properties spring.version5.2.22.RELEASE/spring.version mybatis.version3.5.6/mybatis.version /properties dependencies !-- Spring核心与Web模块 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis与Spring桥接 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency !-- MySQL驱动与连接池 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.26/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.8/version /dependency !-- JSTL -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependenciesSpring 5.2.x搭配MyBatis 3.5.x是常见组合可以运行在Tomcat 8.5和9.0上。MySQL驱动8.0.26需要JDK 8并且driverClassName要写成com.mysql.cj.jdbc.Driver和5.x驱动不同。这里不要用6.x的驱动它在时区问题上的兼容性处理较差。依赖配好后用Spring配置文件把数据源、SqlSessionFactory和事务管理器串起来。下面是applicationContext.xml里的核心部分context:component-scan base-packagecom.jewelry.service/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/jewelry_mall?useSSLfalseamp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword value123456/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.jewelry.domain/ property nameconfiguration bean classorg.apache.ibatis.session.Configuration property namemapUnderscoreToCamelCase valuetrue/ /bean /property /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.jewelry.mapper/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/这段配置有三个关键点。第一url里的要写成否则XML解析报错。第二mapUnderscoreToCamelCase设置为true后数据库的create_time会自动映射到Java Bean的createTime省掉手写大量resultMap。第三MapperScannerConfigurer会扫描com.jewelry.mapper包把接口的代理Bean注册到Spring容器这样Service层才能直接注入。2.3 SpringMVC请求流程DispatcherServlet与常用注解SpringMVC的原理一句话所有请求先被DispatcherServlet拦下它根据HandlerMapping找到对应的Controller方法再经过参数解析、返回值处理最后把逻辑视图名交给ViewResolver渲染成JSP。在web.xml中要配置DispatcherServlet并指定spring-mvc.xml路径servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping注意url-pattern用/而不是/*否则JSP文件也会被DispatcherServlet接管造成404。在spring-mvc.xml中要开启注解驱动和组件扫描并配置视图解析器mvc:annotation-driven/ context:component-scan base-packagecom.jewelry.controller/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean mvc:default-servlet-handler/控制器里最常用的几个注解建议背熟答辩时高频出现注解作用使用示例Controller标记类是SpringMVC控制器类上RequestMapping映射请求URL类或方法上PathVariable从URL模板取参数/detail/{id}RequestParam从请求参数取值RequestParam(id)ResponseBody返回结果直接写HTTP响应体返回JSON时ModelAttribute把对象自动绑定到Model表单提交对象2.4 Bootstrap资源引用的正确姿势避免静态文件404Bootstrap在SSM项目里通常是原样拷贝到webapp/static/bootstrap目录然后在JSP中用绝对路径引用link relstylesheet href${pageContext.request.contextPath}/static/bootstrap/css/bootstrap.min.css script src${pageContext.request.contextPath}/static/jquery/jquery.min.js/script script src${pageContext.request.contextPath}/static/bootstrap/js/bootstrap.min.js/script这里有两个容易犯错的地方。第一Bootstrap 3/4的下拉菜单依赖jQuery必须先加载jquery再加载bootstrap.js否则下拉点不开。第二不要写static/bootstrap/...这样的相对路径因为当项目发布到Tomcat的webapps/jewelry/目录时页面URL里会带contextPath相对路径很容易拼错。用${pageContext.request.contextPath}拼接是最保险的。另外如果spring-mvc.xml里配置了mvc:default-servlet-handler/静态资源的请求会交给Tomcat默认Servlet处理Bootstrap的css、js、图片才能正确返回。如果忘了配浏览器会返回404而且控制台没有任何异常很多初学者卡在这一步。3. 珠宝商城核心模块编码从Mapper到Controller的完整链路商城最核心的是商品浏览和下单流程。这一章以商品列表、商品详情、购物车、提交订单为例走一遍MyBatis Mapper到Controller到JSP的完整编码过程。代码可以在你拿到的源码包里对应找到但建议你自己敲一遍答辩被问到任何一行都能接上。3.1 实体类与Mapper接口设计先在com.jewelry.domain包下建Product.java属性对应数据库字段。这里省略getter/setter实际代码需要补全public class Product { private Integer id; private Integer categoryId; private String name; private String subtitle; private BigDecimal price; private String imageUrl; private Integer stock; private Integer isOnSale; private Date createTime; }对应Mapper接口package com.jewelry.mapper; public interface ProductMapper { // 查询所有在售商品 ListProduct findAllOnSale(); // 根据主键查询商品 Product findById(Integer id); }XML映射文件放在resources/mapper/ProductMapper.xml注意namespace必须写接口全限定名mapper namespacecom.jewelry.mapper.ProductMapper select idfindAllOnSale resultTypeProduct SELECT id, category_id, name, subtitle, price, image_url, stock, is_on_sale, create_time FROM jm_product WHERE is_on_sale 1 ORDER BY create_time DESC /select select idfindById parameterTypeint resultTypeProduct SELECT id, category_id, name, subtitle, price, image_url, stock, is_on_sale, create_time FROM jm_product WHERE id #{id} /select /mapperresultType写成Product是因为之前在SqlSessionFactoryBean里配置了typeAliasesPackageMyBatis会自动把类名注册为别名。如果没配到这里就得写全限定名com.jewelry.domain.Product。#{}会使用PreparedStatement参数占位防止SQL注入而${}是字符串拼接即使业务需要也不应该直接接收用户输入。3.2 Service层下单和减库存必须放在一个事务里Service接口负责暴露业务方法实现类上标注Transactional让Spring在方法外开启事务。商品列表不需要事务但订单创建涉及多张表写入必须保证原子性。下面是一个订单创建的核心代码Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Autowired private ProductMapper productMapper; Transactional(rollbackFor Exception.class) public Integer createOrder(Order order, ListCartItem items) { // 先插入订单主表拿到自动生成的订单ID orderMapper.insert(order); // 插入订单明细并扣减库存 for (CartItem item : items) { Product p productMapper.findById(item.getProductId()); OrderItem oi new OrderItem(); oi.setOrderId(order.getId()); oi.setProductId(p.getId()); oi.setProductName(p.getName()); oi.setPrice(p.getPrice()); oi.setQuantity(item.getQuantity()); orderItemMapper.insert(oi); // 扣减库存 productMapper.decreaseStock(p.getId(), item.getQuantity()); } return order.getId(); } }注意Transactional(rollbackFor Exception.class)很重要Spring默认只在RuntimeException时回滚如果SQL执行抛的是受检异常不加这个参数是不会回滚的。库存扣减应该在商品表用UPDATE jm_product SET stock stock - #{qty} WHERE id #{id} AND stock #{qty}先判断库存充足再原子更新。Service层还应该处理购物车。购物车通常放在Session里因为珠宝商城允许游客先加购下单时才强制登录。如果每次加购都写数据库会造成大量垃圾数据。Session里的购物车本质上是一个Mapkey是商品IDvalue是数量。3.3 Controller层列表、详情、加购、下单ProductControllerController RequestMapping(/product) public class ProductController { Autowired private ProductService productService; RequestMapping(/list) public String list(Model model) { model.addAttribute(products, productService.getOnSaleProducts()); return product/list; } RequestMapping(/detail/{id}) public String detail(PathVariable Integer id, Model model) { model.addAttribute(product, productService.findById(id)); return product/detail; } }CartControllerController RequestMapping(/cart) public class CartController { RequestMapping(/add/{productId}) public String add(PathVariable Integer productId, HttpSession session) { MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new HashMap(); } cart.merge(productId, 1, Integer::sum); session.setAttribute(cart, cart); return redirect:/product/list; } }这里cart.merge是Java 8的Map方法等价于逻辑如果购物车已有该商品数量加1否则放入数量1。用HttpSession session作为Controller方法参数SpringMVC会自动从容器中解析。注意返回的是redirect:关键字跳转会重新发起一次请求避免刷新页面时重复提交加购。Controller中不要写SQL或业务逻辑只做参数接收、调用Service、把数据放Model。答辩老师一般会看Controller是否臃肿如果把事务直接写在Controller里会被认为是设计缺陷。下面用表格总结这一部分涉及的URL与视图文件HTTP方法请求路径Controller方法返回视图/行为GET/product/listProductController.listproduct/list.jspGET/product/detail/{id}ProductController.detailproduct/detail.jspGET/cart/add/{productId}CartController.add重定向到/product/listPOST/order/checkoutOrderController.checkout重定向到/order/success3.4 JSP视图中使用Bootstrap组件渲染商品卡片列表页用Bootstrap栅格系统一个商品占四分之一列在PC上显示四列在平板显示两列手机上自动堆叠% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % div classcontainer div classrow c:forEach items${products} varp div classcol-md-3 col-sm-6 div classcard mb-3 img src${p.imageUrl} classcard-img-top alt${p.name} div classcard-body h5 classcard-title${p.name}/h5 p classcard-text价格${p.price}/p a href${pageContext.request.contextPath}/product/detail/${p.id} classbtn btn-outline-primary查看详情/a a href${pageContext.request.contextPath}/cart/add/${p.id} classbtn btn-primary加入购物车/a /div /div /div /c:forEach /div /div所有路径都拼接了pageContext.request.contextPath这是JSP中获取项目根路径的标准写法避免部署在带contextPath的Tomcat环境下出现404。Bootstrap自带的下拉菜单、轮播图、表单校验样式都可以直接复用但下拉菜单必须配合jQuery才能展开这也是为什么前面的JSP里要顺序加载jquery与bootstrap.js。4. MySQL数据库脚本设计珠宝商城的表怎么建才不返工数据库脚本是毕业设计里最容易被老师翻看的部分。建表是否合理、字段类型是否得当、是否用utf8mb4都会成为评分点。珠宝商城建议设计五张表用户表、分类表、商品表、订单表、订单明细表。购物车如果落库需要附带用户ID和商品ID但多数毕设用Session管理购物车脚本里可以省去答辩时说明理由即可。4.1 表结构设计字段类型和约束按这个模板来下面是一份可以直接导入MySQL的建表脚本兼容MySQL 5.7与8.0CREATE DATABASE IF NOT EXISTS jewelry_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE jewelry_mall; DROP TABLE IF EXISTS jm_order_item; DROP TABLE IF EXISTS jm_order; DROP TABLE IF EXISTS jm_product; DROP TABLE IF EXISTS jm_category; DROP TABLE IF EXISTS jm_user; CREATE TABLE jm_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT用户表; CREATE TABLE jm_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, parent_id INT NOT NULL DEFAULT 0 ) ENGINEInnoDB COMMENT商品分类表; CREATE TABLE jm_product ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(200) NOT NULL, subtitle VARCHAR(500), price DECIMAL(10,2) NOT NULL, image_url VARCHAR(255), stock INT NOT NULL DEFAULT 0, is_on_sale TINYINT NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_category_id (category_id), KEY idx_is_on_sale (is_on_sale) ) ENGINEInnoDB COMMENT商品表; CREATE TABLE jm_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-待付款 1-已付款 2-已发货 3-已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES jm_user (id) ) ENGINEInnoDB COMMENT订单表; CREATE TABLE jm_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, product_name VARCHAR(200) NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, KEY idx_order_id (order_id), CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES jm_order (id) ) ENGINEInnoDB COMMENT订单明细表;设计要点如下金额全部用DECIMAL(10,2)不要用FLOAT或DOUBLE浮点数在计算总价时会出现0.999这类误差答辩时容易暴露。所有表都用InnoDB引擎因为需要事务和外键虽然MyBatis一般只在内存里维护关联关系但外键在演示时可以体现出表间关系。订单表单独保存product_name和price是为了防止商品改名或调价后历史订单里的信息跟着变。各表之间的关系可以总结为表名用途主要字段关联jm_user用户登录信息id, username, password被订单关联jm_category商品分类id, name, parent_id商品关联jm_product商品基础信息id, name, price, stock属于分类jm_order订单主表order_no, user_id, total_price关联用户jm_order_item订单明细order_id, product_id, quantity关联订单4.2 数据库脚本导入命令行与可视化工具命令行导入是最保险的方式假设脚本文件叫jewelry_mall.sqlmysql -u root -p jewelry_mall.sql或者在MySQL Workbench中打开脚本文件选中全部后执行再点击Schema刷新看到jewelry_mall库。如果脚本提示语法错误先检查MySQL版本因为utf8mb4的排序规则在不同版本写法略有不同。还有一种常见问题是Windows下文本文件编码不对把SQL文件用Notepad转成UTF-8 without BOM再导入。导入后建议先执行一下最小查询验证数据表存在SHOW TABLES FROM jewelry_mall; SELECT COUNT(*) FROM jm_product;提示如果MySQL版本是8.0.16以上还可以用CHECK约束限制price 0但为了兼容5.7建议在应用层校验。4.3 数据库配置与连接时的3个坑连接数据库时最容易遇到的问题集中在三个方面。第一个是时区问题URL必须加serverTimezoneAsia/Shanghai否则MySQL 8.0会报Server returns invalid timezone错误。第二个是驱动类名老项目里写com.mysql.jdbc.Driver新驱动必须写com.mysql.cj.jdbc.Driver两个都可以用但建议写成带cj的避免控制台打印警告。第三个是字符集建库建表都用了utf8mb4连接URL里的characterEncodingutf8也要保留否则中文保存后可能变成问号。在Spring配置文件中数据库密码通常直接写在applicationContext.xml里但答辩提交的源码包最好把密码改成容易说明的123456并在readme中说明修改位置。如果你的机器上MySQL端口不是3306或者用了MAMP、XAMPPurl里的端口要同步改。另外为了演示方便商品表里可以预置20条左右的珠宝数据图片链接用网上公开的占位图或者本地static/images目录下的图片。注意脚本中不要包含明文密码因为MD5在答辩时可能被追问最简单的方式是用固定的MD5字符串并在代码里用同一个加密工具类校验。5. 部署与排错把SSM项目跑起来的5个关键检查点5.1 从源码包到可运行三步启动法拿到项目源码后我一般会按三个步骤启动。第一步导入数据库脚本jewelry_mall.sql到MySQL。第二步在IDEA中选择Maven的Tomcat插件直接运行或者打war包丢到Tomcat/webapps目录。第三步启动后看到index.jsp说明欢迎页正常再分别访问商品列表、详情、加购和下单。这里给出一个用Maven Tomcat插件快速启动的命令mvn tomcat7:run-war -Dmaven.tomcat.port8080注意Tomcat插件版本要和JDK匹配有些项目用的是tomcat7插件如果你用的是JDK 11以上需要改用cargo插件或配置本地Tomcat否则可能起不来。5.2 高频报错与解决对照现象原因解决页面404控制台无日志DispatcherServlet拦截了JSP或静态资源检查url-pattern不是/*配置 mvc:default-servlet-handler/查询中文乱码页面、数据库、连接编码不一致页面 contentType指定UTF-8数据库统一utf8mb4url加characterEncodingutf8MyBatis查询返回空字段下划线转驼峰没开启在SqlSessionFactoryBean的configuration中设置mapUnderscoreToCamelCasetrue加购后刷新重复提交Controller返回转发而不是重定向加购成功后返回redirect:/product/list下拉菜单点击没反应忘记加载jQuery或加载顺序错bootstrap.js必须放在jquery之后MySQL连接失败驱动版本或URL参数不匹配使用com.mysql.cj.jdbc.Driverurl加serverTimezone5.3 MyBatis数字比较与缓存的进阶排查如果Mapper的SQL里写了类似if teststatus ! null and status ! 而status是IntegerMyBatis的Ognl表达式会把Integer与空字符串比较有时直接报ExpressionError或得到错误结果。建议对数字类型只判null和0不要跟空字符串比较。比如select idfindByStatus resultTypeProduct SELECT * FROM jm_product where if teststatus ! null and status ! 0 AND is_on_sale #{status} /if /where /select这里用status ! 0取代status ! 避免Ognl的类型比较问题。另一个常被问的是MyBatis缓存默认一级缓存是SqlSession级别的同一个SqlSession内两次相同查询不会重复查数据库但每次更新操作会清空该缓存二级缓存需要显式开启。在毕业设计里建议保持默认不需要在mapper.xml中加cache/因为商城商品频繁改价缓存处理不当反而显示旧数据。这些是部署时最常见的拦截点按顺序排查很快能定位。最后你可以打开logback或log4j的DEBUG级别单独打印MyBatis执行的SQL和参数比在代码里加System.out靠谱得多。本文还有配套的精品资源点击获取
返回列表