十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

SSM框架实战:从零搭建新闻系统,掌握Java Web分层架构与配置精髓

SSM框架实战:从零搭建新闻系统,掌握Java Web分层架构与配置精髓 简介这是一套基于SpringSpringMVCMyBatisSSM框架开发的简易新闻系统完整源码面向Java Web初学者与中级开发者旨在帮助理解三大主流框架的整合原理与典型MVC项目结构。资源包含227个文件涵盖36个核心Java源码含Controller、Service、DAO及实体类、54个编译后class文件、24个XML配置与Mapper映射文件、23个JSP视图页面及23个JS交互脚本辅以31个运行依赖jar包、6个图片资源及SQL建表脚本等整体压缩包仅12.21MB轻量易导入。已有83人学习下载适合快速部署调试、对照源码理解事务管理、AOP日志、MyBatis动态SQL及前后端数据绑定等关键实践点。项目采用标准分层架构预览可见NewsExample、UserExample等自动生成的Criteria类表明已集成MyBatis Generator便于扩展维护同时包含完整后台管理与前端展示功能可作为课程设计、毕业设计或SSM入门实战的可靠参考模板。1. 项目背景与核心价值最近在整理硬盘时翻到了一个老项目——“基于SpringSpringMVCMyBatis SSM框架的简易新闻系统”。这个压缩包躺在角落里好几年了文件名朴实无华但打开一看却是一个相当完整的Java Web项目骨架。在如今Spring Boot大行其道的时代回过头来看这个经典的SSM组合反而有种“温故而知新”的感觉。这个项目麻雀虽小五脏俱全从前台新闻展示、分类浏览到后台的新闻发布、审核、用户管理都实现了基础功能。对于很多刚学完Java基础想踏入企业级开发门槛的朋友来说SSM框架就像一道绕不开的“必修课”。它不像Spring Boot那样开箱即用需要你手动配置各种XML和注解但正是这个过程能让你真正理解一个Java Web应用是如何从零开始组装起来的。这个简易新闻系统就是一个绝佳的“解剖样本”。为什么现在还要看SSM首先很多存量项目尤其是传统企业的内部系统依然运行在SSM架构上。理解它是维护和迭代这些系统的前提。其次SSM清晰地分离了SpringIoC容器、事务管理、SpringMVCWeb层控制和MyBatis数据持久层这种分层思想是软件设计的基石。即使你以后用Spring Boot其自动配置背后依然是这些组件的集成。最后从学习曲线来看直接上Spring Boot可能会让你对底层细节感到模糊而通过手动搭建一个SSM项目你能清晰地看到请求如何被DispatcherServlet拦截、Service层的事务如何生效、SQL语句如何被MyBatis动态代理执行。这个新闻系统项目就提供了这样一个完整的视角。2. 项目结构深度解析与核心依赖配置拿到一个陌生的项目源码第一件事就是看它的目录结构。这个新闻系统的结构是典型的Maven多模块思想在单模块中的体现虽然它可能只是一个单独的Web项目但分层非常清晰。2.1 源码目录与包结构设计通常一个标准的SSM项目会包含以下核心目录和包src/main/java ├── com.xxx.news │ ├── controller // SpringMVC控制器处理HTTP请求 │ ├── service // 业务逻辑层接口 │ │ └── impl // 业务逻辑层实现类 │ ├── dao // 数据访问层接口MyBatis Mapper接口 │ ├── entity // 实体类与数据库表对应或叫model/pojo │ └── dto // 数据传输对象可能没有视项目复杂度 src/main/resources ├── mapper // MyBatis的SQL映射XML文件 ├── spring // Spring相关配置文件 │ ├── spring-mvc.xml // SpringMVC配置 │ ├── spring-mybatis.xml // 整合Spring与MyBatis │ └── spring-service.xml // Service层事务等配置 ├── jdbc.properties // 数据库连接配置 └── log4j.properties // 日志配置 src/main/webapp ├── WEB-INF │ ├── web.xml // Web应用部署描述符 │ └── views // JSP视图文件也可能是HTML ├── static // 静态资源CSS, JS, images └── index.jsp // 首页在这个新闻系统中controller里会有NewsController、AdminControllerservice里会有NewsService、UserServicedao里是对应的NewsMapper、UserMapper接口entity里则是News、User、Category等实体类。这种分层的目的是为了解耦。Controller只负责接收参数、调用Service、返回视图或数据Service处理复杂的业务逻辑并调用多个Dao完成数据操作Dao则只关心数据的增删改查。这样当数据库从MySQL换成Oracle或者前端从JSP换成Thymeleaf时你只需要改动相应的层而不必牵一发而动全身。2.2 核心配置文件详解与避坑点SSM的整合精髓就在那几个XML配置文件里。很多初学者在这里栽跟头。1. web.xml应用的入口这个文件配置了Spring的监听器和SpringMVC的前端控制器。!-- 配置Spring的监听器加载applicationContext.xml或你命名的Spring总配置文件 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-*.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- 配置SpringMVC的DispatcherServlet -- servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name servlet-mapping url-pattern//url-pattern /servlet-mapping注意这里有两个contextConfigLocation。第一个是给ContextLoaderListener用的它负责加载业务层Service、Dao的Spring配置如spring-service.xml,spring-mybatis.xml。第二个是给DispatcherServlet用的它只加载Web层Controller、视图解析器的配置spring-mvc.xml。这种父子容器的设计是为了实现关注点分离。一个常见的坑是在spring-mvc.xml里也配置了Service的Bean扫描导致Service被加载了两次可能引发事务失效等问题。正确的做法是在spring-mvc.xml中只扫描controller包而业务层和持久层的Bean在由ContextLoaderListener加载的配置文件中扫描。2. spring-mybatis.xml数据持久化核心这个文件负责整合Spring和MyBatis。!-- 1. 加载数据库连接属性文件 -- context:property-placeholder locationclasspath:jdbc.properties/ !-- 2. 配置数据源这里常用阿里的Druid -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ property namedriverClassName value${jdbc.driver}/ !-- 连接池其他配置初始大小、最小、最大等 -- /bean !-- 3. 配置SqlSessionFactoryBean -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ !-- 指定MyBatis全局配置文件位置可选很多配置可在此处直接设置 -- property nameconfigLocation valueclasspath:mybatis-config.xml/ !-- 指定Mapper XML文件的位置这是关键 -- property namemapperLocations valueclasspath:mapper/*.xml/ !-- 配置实体类别名方便Mapper XML中使用短名 -- property nametypeAliasesPackage valuecom.xxx.news.entity/ /bean !-- 4. 配置MapperScannerConfigurer自动扫描Dao接口并注入Spring -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.xxx.news.dao/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean实操心得mapperLocations的配置路径一定要和你的mapperXML文件存放位置对上。另一个高频错误是typeAliasesPackage配置后在XML里写resultTypeNews但运行时报错找不到类。这通常是因为实体类没有放在配置的包下或者包名写错了。建议打开sqlSessionFactory的调试日志检查扫描过程。3. spring-mvc.xmlWeb请求处理中枢!-- 开启注解驱动这是使用Controller、RequestMapping等注解的前提 -- mvc:annotation-driven/ !-- 配置静态资源处理否则CSS/JS/图片会被DispatcherServlet拦截 -- mvc:resources mapping/static/** location/static// !-- 配置控制器扫描包 -- context:component-scan base-packagecom.xxx.news.controller use-default-filtersfalse context:include-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan !-- 配置视图解析器 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean !-- 配置文件上传解析器如果新闻系统需要上传图片 -- bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value10485760/ !-- 10MB -- /bean避坑指南mvc:resources这一行至关重要。如果没有它你对/static/css/style.css的请求会被DispatcherServlet其url-pattern是/拦截因为它找不到对应的Controller来处理从而返回404。这个配置告诉SpringMVC对于匹配/static/**的请求直接到/static/目录下找资源不要走控制器。另一个细节是context:component-scan里的use-default-filtersfalse配合include-filter这确保了只扫描带有Controller注解的类避免和父容器由ContextLoaderListener加载的重复扫描Service等组件。3. 从数据库到前端新闻模块的完整链路实现我们以新闻系统的核心——“新闻发布与展示”为例拆解SSM框架中各层是如何协作的。这个过程就像一条生产线数据库是原料仓库MyBatis是搬运工Spring Service是加工车间SpringMVC Controller是销售前台JSP是产品包装。3.1 数据层Dao/MapperMyBatis的灵活运用首先定义实体类News属性对应数据库news表的字段id, title, content, author, create_time, category_id等。接着在dao包下创建接口NewsMapper.javapublic interface NewsMapper { int insert(News news); int deleteById(Integer id); int update(News news); News selectById(Integer id); ListNews selectAll(); // 动态条件查询根据标题关键词和分类ID查询新闻列表 ListNews selectByCondition(Param(title) String title, Param(categoryId) Integer categoryId); }然后在resources/mapper/目录下创建NewsMapper.xml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.xxx.news.dao.NewsMapper resultMap idBaseResultMap typeNews id columnid propertyid/ result columntitle propertytitle/ result columncontent propertycontent/ !-- 其他字段映射 -- /resultMap insert idinsert parameterTypeNews useGeneratedKeystrue keyPropertyid INSERT INTO news (title, content, author, category_id, create_time) VALUES (#{title}, #{content}, #{author}, #{categoryId}, now()) /insert select idselectByCondition resultMapBaseResultMap SELECT * FROM news WHERE 11 if testtitle ! null and title ! AND title LIKE CONCAT(%, #{title}, %) /if if testcategoryId ! null AND category_id #{categoryId} /if ORDER BY create_time DESC /select /mapper核心技巧useGeneratedKeystrue keyPropertyid这个配置在插入数据后会自动将数据库生成的主键值回填到传入的News对象的id属性中这样在Service层就能直接拿到这条新闻的ID非常方便。selectByCondition方法展示了MyBatis动态SQL的强大if标签会根据传入参数动态拼接WHERE子句。注意#{title}是预编译占位符能防止SQL注入而${}是字符串替换有安全风险除非必要如动态指定表名、列名否则慎用。3.2 业务层Service事务管理与业务逻辑在service包下创建接口NewsService.java然后在impl包下创建实现类NewsServiceImpl.java。Service // 由Spring的Service层配置文件扫描注册为Bean public class NewsServiceImpl implements NewsService { Autowired private NewsMapper newsMapper; Override Transactional // 声明式事务注解 public boolean publishNews(News news) { // 1. 参数校验如标题不能为空 if (news.getTitle() null || news.getTitle().trim().isEmpty()) { throw new IllegalArgumentException(新闻标题不能为空); } // 2. 设置默认值如作者、创建时间 news.setCreateTime(new Date()); // 3. 调用Dao层插入数据 int result newsMapper.insert(news); // 4. 可能还有其他业务操作比如更新分类下的新闻计数 // updateCategoryCount(news.getCategoryId()); return result 0; } Override public PageInfoNews getNewsList(String keyword, Integer categoryId, Integer pageNum, Integer pageSize) { // 使用PageHelper进行物理分页 PageHelper.startPage(pageNum, pageSize); ListNews newsList newsMapper.selectByCondition(keyword, categoryId); return new PageInfo(newsList); } }事务深度解析Transactional注解是Spring声明式事务管理的核心。它被标注在publishNews方法上意味着这个方法里的所有数据库操作insert和可能存在的updateCategoryCount会在同一个事务中执行要么全部成功要么全部回滚。它的工作原理是基于AOP面向切面编程。Spring会在运行时为这个NewsServiceImpl类创建一个代理对象。当你调用publishNews时实际上是先调用代理对象的方法代理方法会先开启事务再调用你真实的publishNews方法最后根据是否抛出异常来决定提交或回滚事务。这里有个大坑事务失效。常见原因有1. 方法不是public的2. 在同一个类内部方法调用如A方法调用本类的B方法B方法上的Transactional会失效3. 异常类型不对默认只回滚RuntimeException和Error如果抛出Exception需要额外配置rollbackFor4. 数据源没有配置事务管理器。在这个新闻系统里确保在spring-service.xml中配置了tx:annotation-driven transaction-managertransactionManager/和对应的事务管理器Bean。3.3 控制层ControllerSpringMVC的请求调度在controller包下创建NewsController.java。Controller RequestMapping(/news) public class NewsController { Autowired private NewsService newsService; // 前台新闻列表页 RequestMapping(/list) public String listNews(RequestParam(value keyword, required false) String keyword, RequestParam(value categoryId, required false) Integer categoryId, RequestParam(value pageNum, defaultValue 1) Integer pageNum, Model model) { PageInfoNews pageInfo newsService.getNewsList(keyword, categoryId, pageNum, 10); model.addAttribute(pageInfo, pageInfo); model.addAttribute(keyword, keyword); model.addAttribute(categoryId, categoryId); return news/list; // 对应 /WEB-INF/views/news/list.jsp } // 后台发布新闻通常会有权限校验这里简化 RequestMapping(value /admin/publish, method RequestMethod.POST) ResponseBody // 返回JSON数据用于AJAX提交 public MapString, Object publish(Valid News news, BindingResult result) { MapString, Object response new HashMap(); if (result.hasErrors()) { response.put(success, false); response.put(message, 参数校验失败); return response; } try { boolean success newsService.publishNews(news); response.put(success, success); response.put(message, success ? 发布成功 : 发布失败); } catch (Exception e) { response.put(success, false); response.put(message, 系统错误: e.getMessage()); } return response; } }经验之谈RequestMapping可以定义在类级别和方法级别组合成最终的URL路径。RequestParam的requiredfalse表示参数非必填defaultValue提供默认值。Model对象用于向视图传递数据。注意前台展示和后台管理的URL路径最好通过前缀区分如/news/list和/news/admin/publish便于后期做权限拦截。Valid注解配合实体类中的JSR-303注解如NotBlank可以实现自动参数校验BindingResult用来接收校验结果。ResponseBody注解会让SpringMVC使用HttpMessageConverter如Jackson将返回值序列化成JSON这是实现前后端分离接口的关键。3.4 视图层JSP数据的最终呈现在/WEB-INF/views/news/目录下创建list.jsp。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle新闻列表/title/head body h1新闻中心/h1 form action${pageContext.request.contextPath}/news/list methodget input typetext namekeyword value${keyword} placeholder搜索标题 select namecategoryId option value全部分类/option c:forEach items${categories} varcat option value${cat.id} c:if test${categoryId eq cat.id}selected/c:if${cat.name}/option /c:forEach /select button typesubmit搜索/button /form ul c:forEach items${pageInfo.list} varnews li a href${pageContext.request.contextPath}/news/detail?id${news.id}${news.title}/a span作者${news.author} | 时间${news.createTime}/span /li /c:forEach /ul !-- 分页组件 -- div c:if test${pageInfo.hasPreviousPage} a href?pageNum${pageInfo.prePage}keyword${keyword}categoryId${categoryId}上一页/a /c:if c:forEach begin1 end${pageInfo.pages} vari c:choose c:when test${i eq pageInfo.pageNum} strong${i}/strong /c:when c:otherwise a href?pageNum${i}keyword${keyword}categoryId${categoryId}${i}/a /c:otherwise /c:choose /c:forEach c:if test${pageInfo.hasNextPage} a href?pageNum${pageInfo.nextPage}keyword${keyword}categoryId${categoryId}下一页/a /c:if /div /body /html注意JSP中使用了JSTL标签库c:forEach等需要在项目中引入jstl的依赖。分页信息PageInfo来自PageHelper插件它封装了总记录数、总页数、当前页、数据列表等在JSP中渲染非常方便。这里的分页是后端物理分页PageHelper.startPage会修改后续第一条SQL自动加上LIMIT相比前端分页或内存分页在处理大量数据时性能更好。4. 项目运行、调试与进阶优化实战把代码和配置都写好了怎么让它跑起来运行过程中遇到问题怎么排查这个简单的系统还有哪些可以优化的地方这部分我们来解决这些实际问题。4.1 环境搭建与项目启动基础环境确保本地安装了JDK1.8或以上、Maven3.x、Tomcat8.x或9.x和MySQL5.7或以上。导入项目用IDEA或Eclipse导入这个Maven项目。IDE会自动识别pom.xml并下载依赖。关键依赖包括spring-context,spring-webmvc,mybatis,mybatis-spring,mysql-connector-java,druid,jackson-databind用于JSON处理,jstl等。数据库初始化在MySQL中创建一个数据库如news_db然后执行项目里可能附带的SQL脚本或根据实体类自己建表。记得修改jdbc.properties中的连接信息。部署运行方式一IDE集成在IDEA中配置一个Tomcat Server将项目添加为Artifact指定war exploded格式然后启动。这是最方便的调试方式。方式二手动打包在项目根目录下执行mvn clean package会在target目录下生成news.war文件将其复制到Tomcat的webapps目录下启动Tomcat即可。启动常见问题排查ClassNotFoundException 或 NoClassDefFoundError检查pom.xml依赖是否完整Maven仓库是否下载成功。可以尝试mvn clean compile看是否有编译错误。BeanCreationExceptionSpring容器启动失败通常是XML配置错误或Bean扫描路径不对。仔细检查applicationContext.xml或spring-*.xml文件看context:component-scan的包路径是否正确是否有循环依赖。404错误首先检查Tomcat是否成功启动并部署了应用。然后检查访问的URL是否与RequestMapping定义的路径一致。最后检查web.xml中DispatcherServlet的url-pattern是否是/以及spring-mvc.xml中是否配置了正确的视图解析器前缀和后缀。数据库连接失败检查jdbc.properties中的URL、用户名、密码。确认MySQL服务已启动且数据库名、表名正确。可以尝试用Navicat或命令行手动连接测试。4.2 日志配置与问题追踪良好的日志是调试的利器。这个项目通常会使用log4j或logback。在resources目录下的log4j.properties中可以这样配置# 设置根日志级别为INFO输出到控制台和文件 log4j.rootLoggerINFO, stdout, file # 控制台输出 log4j.appender.stdoutorg.apache.log4j.ConsoleAppender log4j.appender.stdout.TargetSystem.out log4j.appender.stdout.layoutorg.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %m%n # 文件输出 log4j.appender.fileorg.apache.log4j.RollingFileAppender log4j.appender.file.Filelogs/news.log log4j.appender.file.MaxFileSize10MB log4j.appender.file.MaxBackupIndex10 log4j.appender.file.layoutorg.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %m%n # 单独设置MyBatis的日志级别为DEBUG方便查看执行的SQL log4j.logger.org.mybatisDEBUG # 设置Spring事务相关日志 log4j.logger.org.springframework.jdbcDEBUG配置好后在代码中通过private static final Logger logger LoggerFactory.getLogger(NewsServiceImpl.class);引入日志对象在关键位置打上logger.debug(查询参数: {}, keyword);。这样当SQL执行异常或事务行为不符合预期时通过查看日志文件就能快速定位问题。4.3 性能优化与安全加固建议这个简易系统跑起来后可以从以下几个角度进行优化和加固1. MyBatis层优化二级缓存对于不常变动的数据如新闻分类可以开启MyBatis的二级缓存。在NewsMapper.xml中添加cache/标签。但要注意在分布式环境下本地缓存会导致数据不一致此时应考虑使用Redis等集中式缓存。批量操作后台需要批量删除新闻时应使用foreach标签实现批量SQL而不是在循环中逐条删除。delete iddeleteByIds DELETE FROM news WHERE id IN foreach collectionids itemid open( separator, close) #{id} /foreach /deleteSQL优化使用EXPLAIN分析慢查询为title,category_id,create_time等常用查询条件建立索引。2. 服务层优化异步处理新闻发布后如果需要执行一些耗时的后续操作如生成静态页面、发送通知可以使用Spring的Async注解将方法改为异步执行避免阻塞主线程。Async public void asyncPostPublishTask(Integer newsId) { // 耗时操作... }需要在Spring配置中开启异步支持task:annotation-driven /。连接池调优在druid数据源配置中根据实际并发量调整initialSize、minIdle、maxActive等参数。3. Web层优化与安全防XSS攻击新闻内容在JSP页面显示时应使用JSTL的c:out value${news.content}/或EL表达式的fn:escapeXml()函数进行转义防止恶意脚本注入。防CSRF攻击对于后台管理的重要操作如删除新闻应添加CSRF Token验证。Spring Security提供了开箱即用的支持但在SSM中需要手动实现或在表单中添加一个随机Token进行校验。文件上传安全限制上传文件的类型通过后缀名和文件头校验、大小并将上传目录设置为不可执行防止上传木马文件。4. 前端体验优化引入前端框架将JSP替换为更现代化的前后端分离架构。例如前端使用Vue.js或React通过Ajax调用后端RestController提供的JSON接口。这样前后端职责更清晰也便于团队协作。页面静态化对于访问量巨大的新闻详情页可以考虑在新闻发布时使用模板引擎如FreeMarker生成静态HTML文件直接通过Nginx提供服务极大减轻数据库和Java容器的压力。这个基于SSM的简易新闻系统虽然技术栈不是最新的但它所蕴含的分层思想、配置方式、问题排查思路是Java Web开发的通用基础。通过亲手搭建和剖析这样一个项目你能获得的远不止是SSM本身的使用技能更是对Java EE技术体系一个扎实而全面的理解。在后续接触Spring Boot时你会恍然大悟原来那些自动配置都是为了简化我们今天手动做的这些事。本文还有配套的精品资源点击获取
返回列表