资讯详情

资讯详情

完整SSM项目实战:多模块搭建、MyBatis与Spring MVC高频避坑

简介这套资源基于Spring、Spring MVC与MyBatis三大框架整合而成是一套可直接运行的完整Java Web项目很适合初学SSM的开发者理解框架整合方式也可作为课程设计或二次开发的基础工程。项目源码中包含完整的目录结构controller、service、dao分层清晰配置方面涵盖Spring的bean配置、Spring MVC的请求映射与视图解析、MyBatis的SQL映射以及数据库连接属性文件运行调试时能直观看到依赖注入、面向切面编程、MVC模式和持久层操作等机制如何协作。压缩包共226个文件约56MB其中包含32个Java源码、32个Class文件、26个Jar依赖库、20个JSP页面、20个XML配置另有JS、CSS、图片等前端静态资源还带有少量APK文件整体结构完整。已有2386人学习下载。通过阅读和修改源码可掌握常见配置文件的标准写法理清请求从Controller到Service再到DAO的完整调用流程并在此基础上继续扩展模块逐步积累企业级Java Web项目开发经验。1. 为什么一个完整的SSM项目值得你下载一个能直接启动的业务闭环很多刚入行 Java 开发的同学手里堆了十几个零散的 SSM 练习代码有的只有 Service 层有的把 SQL 写死在控制器里真正能从git clone到mvn tomcat7:run一键起服务、还能处理登录鉴权和分页查询的完整项目反而不多。这份资源之所以值得下载是因为它不是一个 hello world而是一个带用户权限、商品分类、订单流转的模拟项目X也就是某公司内部 OA 系统的简化版。它把 Spring、Spring MVC、MyBatis 三者的分工落到了每一个请求里MyBatis 管数据库读写Spring 管 Service 层事务Spring MVC 管 HTTP 出入参。如果你正在为毕业设计或入职笔试准备项目经验或者你想快速搞清楚 SSM 项目在真实部署时怎么拆分模块、怎么统一异常处理这份代码比看十篇博客都直接。2. 搭建骨架Maven多模块与数据库初始化2.1 Maven模块划分与POM配置要点拿到资源后第一件事不是看代码是看顶层pom.xml的模块声明。这个项目不是单模块结构而是拆成了common、dao、service、web四个子模块。这样拆的好处是后续微服务化时dao和service可以直接被抽取为独立 JAR 包不需要动页面代码。在父 POM 里统一管理依赖版本我用惯了这种做法子模块不再重复填版本号直接继承dependencyManagement dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.20/version /dependency !-- 其他依赖按相同模式管理 -- /dependencies /dependencyManagementdependencyManagement段只是声明依赖版本不直接引入依赖子模块需要显式声明dependency才会加载。我建议你维护这个段时把 SSM 三件套、Jackson、MySQL 驱动和 Druid 连接池这五类放一起方便后面排查版本冲突。2.2 数据库脚本设计权限与业务表的拆分项目里sql文件夹下有三个脚本init.sql、data.sql、permission.sql。init.sql只建表结构data.sql写测试数据permission.sql单独管理用户、角色、菜单三张关系表。这里有个细节值得模仿业务表的主键用BIGINT UNSIGNED自增而三张权限表的关联字段用INT刻意区分。因为权限表的数据量永远小于业务表用更短的整型做关联可以减少索引空间。CREATE TABLE user ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(128) NOT NULL COMMENT BCrypt密文, status tinyint(4) DEFAULT 1 COMMENT 1-启用,0-禁用, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表;status字段设计成tinyint是因为后面做登录拦截时只需要查询一次状态不需要 join 大表。所有脚本都用了utf8mb4这是给中文字段预留四字节的 emoji 空间实际业务里用户昵称都可能带特殊符号老项目用utf8mb3遇到 emoji 会直接报Incorrect string value。3. 打通Spring与MyBatis数据源、事务与Mapper层的细节3.1 Spring配置文件中的关键参数说明这个项目的applicationContext.xml用的是 JavaConfig 加 XML 混合方式因为有些老配置在 XML 里维护更直观。核心数据源配的是 Druid不是 DBCP。为什么不用 DBCPDruid 的监控页能直接看到慢查询和活跃连接数排查线上问题时比 DBCP 的黑匣子强太多。数据源部分参数如下bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property namedriverClassName valuecom.mysql.cj.jdbc.Driver / property nameurl valuejdbc:mysql://localhost:3306/ssm_demo?useUnicodetrueamp;characterEncodingutf8mb4amp;serverTimezoneAsia/Shanghai / property nameinitialSize value5 / property namemaxActive value20 / property namemaxWait value60000 / property namevalidationQuery valueSELECT 1 / /beanvalidationQuery一定不要漏否则连接闲置八小时后MySQL 主动断开Druid 还持有旧连接第一次请求会报Communications link failure。maxWait设为 60000 毫秒是给连接池排队请求一个保底时间避免线程无限挂起。3.2 通用Mapper与手写SQL两种风格的取舍代码里dao模块同时存在两种 Mapper 风格。BaseMapper.java是通用 Mapper通过继承tk.mybatis.mapper.common.MapperT直接获得insert、selectByPrimaryKey等单表 CRUD适合用户表这种字段稳定、操作简单的场景。而OrderMapper.java手写了selectOrderWithItems这种多表联查 SQL因为订单和订单明细的聚合逻辑在通用 Mapper 里写不出来必须用Select注解或 XMLSelect({ script, SELECT o.id, o.order_no, SUM(oi.quantity * oi.price) AS total_amount, FROM order o LEFT JOIN order_item oi ON o.id oi.order_id, WHERE o.user_id #{userId}, if teststatus ! null AND o.status #{status}/if, GROUP BY o.id, o.order_no, /script }) ListOrderVO selectUserOrders(Param(userId) Long userId, Param(status) Integer status);注意script标签里的#{userId}是预编译占位符MyBatis 会生成?绑定参数这样能防 SQL 注入。如果换成${userId}就是字符串拼接等于把 SQL 注入的坑留给自己踩。后面章节专门说这个坑。4. Spring MVC到前端接口设计、拦截器与页面渲染4.1 控制器层的返回规范与异常处理控制器层没有像很多博客那样把业务逻辑堆在 Controller 里而是每层分明。UserController.java里有个很关键的习惯所有接口返回ResultVO对象这个对象从common模块引用包含code、message、data三个字段。这么做的好处是前后端分离时接口不用改协议直接返回 JSON。RestController RequestMapping(/api/user) public class UserController { Resource private UserService userService; PostMapping(/login) public ResultVO login(RequestBody LoginDTO dto) { String token userService.login(dto.getUsername(), dto.getPassword()); return ResultVO.success(token); } }RestController是Controller加ResponseBody的合成注解省得每个方法都写ResponseBody。RequestBody接收 JSON 请求体如果前端传的是表单application/x-www-form-urlencoded用这个注解就会报HttpMediaTypeNotSupportedException原因就是你注解和请求头不匹配后面避坑章节细说。4.2 Thymeleaf模板与静态资源的版本管理页面部分没有用 JSP而是用了 Thymeleaf。理由很简单JSP 在 Spring Boot 里的支持越来越边缘化Thymeleaf 的语法和 HTML 天然兼容前端拿到模板直接改样式也不会报 JSP 那种Unable to find tag library descriptor的错。项目里web模块的application.properties配置了模板前缀和后缀spring.thymeleaf.prefixclasspath:/templates/ spring.thymeleaf.suffix.html spring.thymeleaf.cachetrue spring.mvc.static-path-pattern/static/**spring.mvc.static-path-pattern/static/**这行是配置静态资源的访问路径默认是/**。为什么这里要缩窄到/static/下因为你把 JS、CSS 都丢在static目录之后可以单独配置 CDN 或 Nginx 对/static路径做缓存不会和动态请求混在一起。cachetrue在生产环境开着本地调试时建议临时改成false不然改了模板刷新不生效容易误以为代码没改对。5. 避坑手册SSM项目中遇到的高频故障与排查记录5.1 故障一Spring容器启动时Bean创建嵌套异常现象项目启动直接抛BeanCreationException根因是Error creating bean with name userService: Injection of autowired dependencies failed。看起来像是 UserService 的问题但把Autowired的代码删掉后异常变成了 DataSource 初始化失败一会儿报驱动类找不到一会儿又报数据库 URL 前缀不对。原因Maven 多模块依赖没拉完整。dao模块编译时能通过但web模块运行时缺mysql-connector.jar。还有一例是 Druid 的driverClassName配错MySQL 8.x 之后的驱动类名是com.mysql.cj.jdbc.Driver老项目里写成com.mysql.jdbc.Driver5.x 和 8.x 两个版本都存在但 8.x 的驱动类会打警告日志某些低版本连接池直接罢工。解决第一步不是看代码是跑mvn dependency:tree检查web模块的依赖路径里有没有mysql-connector-java。第二步确认 URL 里带不带serverTimezone不带的话MySQL 8.x 默认要求指定时区报错信息里会提示The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。这两种问题我都遇到过前者是依赖范围搞错后者是配置惯性。5.2 故障二MyBatis的#{}和${}导致SQL报错现象Mapper 里的排序字段用${orderByColumn}写正常情况下没问题但传入orderByColumn id desc; drop table user;数据库直接执行了多条语句控制台报SQLSyntaxErrorException。另一个场景是#{columnName}用在ORDER BY后查询结果永远是固定排序怎么传参都不变。原因#{}是预编译占位符MyBatis 会把参数包装成PreparedStatement的?所以传进去的内容永远被当数据不能用它替换字段名和关键字${}是字符串替换直接把参数拼进 SQL可以换字段但引入注入风险。解决排序字段这类位置用白名单校验。在 Service 层写一个静态方法只允许id、create_time、update_time三个值通过其他一律抛IllegalArgumentException。public void validateSortField(String field) { if (!Arrays.asList(id, create_time, update_time).contains(field)) { throw new IllegalArgumentException(非法的排序字段); } }从那以后我再写 MyBatis 的 XML 时遇到动态排序、动态表名这类场景就把可选项枚举出来宁可有十个 if 判断也不开放${}入口。5.3 故障三前端传参格式不对导致400/415错误现象前端用axios.post(/api/user/login, { username: admin, password: 123456 })提交后端login方法用RequestBody LoginDTO接收结果请求直接返回 415 Unsupported Media Type。前端改成表单提交方式后端login用普通 POJO 接收又报 400 Bad Request后端getParameter拿到的全是 null。原因415 是请求体内容和接口期望的内容类型不匹配。axios 默认把对象序列化成 JSONContent-Type是application/json;charsetUTF-8后端接口如果没有RequestBodySpring MVC 不认为它该接收 JSON。400 则相反后端用RequestBody期望 JSON前端却传了表单格式application/x-www-form-urlencoded所以解析失败。解决统一前后端约定。项目里所有 POST 接口一律用RequestBody接收 JSON前端 axios 请求里显式加Content-Type: application/json请求头。如果某些接口必须兼容表单比如文件上传那就在multipart/form-data场景下独立写接口不要和 JSON 接口混在一起。axios.post(/api/user/login, { username: this.username, password: this.password }, { headers: { Content-Type: application/json } });这里还有一个隐蔽问题如果LoginDTO里有个字段是Integer status前端传了空字符串Jackson 反序列化时会报Can not deserialize instance of java.lang.Integer out of START_OBJECT token。原因不是代码问题是前端没有对空值做过滤。后来我在全局配置里给 ObjectMapper 加了一个DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT才算从根上解决但这毕竟掩盖了前端的类型不严谨还是建议前端做校验。6. 进阶收尾从能跑通到跑得好6.1 性能排查的简单手段项目跑通后第一件事是去 Druid 的监控页看一眼。路径默认是/druid/index.html本地启动后访问可以看到 SQL 执行次数、慢查询列表、活跃连接数。我一般会重点看SlowSql标签只要出现耗时超过 500ms 的 SQL先去看对应表有没有索引。比如订单明细表order_item里order_id字段如果建表脚本里没有加索引查询一个包含几十个订单的列表就可能触发全表扫描。mysql EXPLAIN SELECT * FROM order_item WHERE order_id 10086;看输出里的type列如果是ALL说明没走索引回头检查sql/init.sql里有没有给order_id建索引。这是最简单的性能排查动作不用 JProfiler 这类重量级工具。6.2 安全加固注意密码存储与接口防刷资源里的登录密码存储用的是 BCrypt不是 MD5。原因很简单MD5 加盐的套路还是能被 GPU 批量碰撞BCrypt 的强度参数是动态成本因子单次计算就需要几十毫秒暴力破解性价比太低。如果你自己在改项目记得BCryptPasswordEncoder的strength参数选 10 到 12太低等于没加密。接口防刷方面代码里对/api/user/login做了简单的请求频率限制同一个 IP 在 60 秒内最多请求 20 次超过就返回TooManyRequestsException。实现是写了一个拦截器配合 Guava 的RateLimiter不是精确计数但足够挡住初学者写的 for 循环脚本。其实这套项目最让我意外的不是代码本身而是注释里的坑记录。写OrderServiceImpl的开发者留下了一段话事务方法必须走代理this.orderMethod()自己调自己不会触发Transactional。因为 Spring 的事务是基于 AOP 代理实现的内部调用拿不到代理对象。从那以后我每次写事务方法都在另一个 Service 里注入当前 Service保证入口是代理对象。如果你也是拿着 SSM 项目做毕业设计或者准备面试把这几个坑写进文档里比把功能清单写满更有说服力因为面试官问的就是这些真实问题。希望这份资源能帮到你少踩几个我已经踩过的坑。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →