SSM电商项目实战:从零搭建可运行的Java电商系统
简介这是一套基于SSMSpringSpringMVCMyBatis框架开发的完整Java电商系统源码专为计算机、通信、人工智能等相关专业学生设计适用于毕业设计、课程设计及期末大作业等实践场景兼顾入门学习与功能拓展需求。压缩包共1053个文件涵盖41个核心Java类、35个HTML页面、288个CSS样式文件、304个PNG与158个JPG图片资源、84个JS交互脚本以及SQL数据库脚本、XML配置、JSP视图和FTL模板等整体结构清晰、分层规范便于理解MVC架构与前后端协作逻辑包体大小为15.88MB。已有347人下载学习项目经实际调试运行验证答辩评分高达95分附带完整前端UI含EasyUI、UEditor等组件、商品管理、用户中心、订单流程等典型电商模块可直接部署运行亦支持二次开发与功能迭代。1. 这不是“又一个SSM demo”而是一套能跑通真实电商闭环的Java工程骨架你搜“SSM电商项目”点开十几个压缩包解压后发现首页能显示登录能跳转但加购物车报500下单直接空指针后台管理页连数据库连接都配错了——这种“半成品教学项目”我亲手拆过不下四十个。今天这篇要讲的是真正从零开始、按生产环境逻辑搭建的基于SSM框架的Java电商项目.zip它不是为应付课程设计写的玩具代码而是我在带三届校招实习生时反复打磨出的实战模板用户注册→商品浏览→加入购物车→生成订单→支付模拟→后台商品/订单/用户管理全链路可运行且每一层都留有清晰的扩展入口。核心关键词就三个SSM框架、Java、电商项目——没有Spring Boot的自动配置糖衣没有MyBatis-Plus的CRUD魔法就是最原始的SpringSpringMVCMyBatis三层协作让你看清IOC容器怎么接管Bean、DispatcherServlet如何分发请求、SqlSessionTemplate怎样避免线程安全问题。适合两类人一是正在准备Java面试、被问到“SSM整合原理”就卡壳的求职者二是刚学完Java基础、想用真实业务场景练手的自学者。它不教你“怎么写Hello World”而是带你亲手把“用户点击立即购买”这个动作拆解成Controller接收参数→Service校验库存→Mapper执行扣减→TransactionManager保证一致性这一整条技术链路。下面所有内容都来自我去年在某电商平台做老系统迁移时用这套SSM结构重写订单模块的真实经验。2. 为什么坚持用原生SSM而不是Spring Boot这背后有三重现实考量2.1 技术选型不是赶时髦而是匹配团队能力与维护成本很多人一看到“SSM”就皱眉“都2024年了还写XML配置”但现实是我上一家公司维护的六个Java系统里四个仍是SSM架构其中两个上线已超七年。原因很实在第一老系统改造预算有限Spring Boot的Starter机制虽好但要把Log4j2换成Logback、把Druid换成HikariCP、把JSP模板换成Thymeleaf光兼容性测试就得两周第二团队里有三位十年经验的老开发他们对Spring的XML Schema熟稔如背家谱改一行 标签比记十个SpringBootApplication注解更顺手第三也是最关键的——SSM的显式配置让新人能一眼看懂“数据源在哪定义”“事务切面挂在哪里”“拦截器生效顺序是什么”。比如这个电商项目里的事务控制!-- applicationContext.xml -- tx:advice idtxAdvice transaction-managertransactionManager tx:attributes tx:method nameadd* propagationREQUIRED/ tx:method nameupdate* propagationREQUIRED/ tx:method namedelete* propagationREQUIRED/ tx:method nameget* read-onlytrue/ tx:method namefind* read-onlytrue/ /tx:attributes /tx:advice aop:config aop:pointcut idserviceMethods expressionexecution(* com.xxx.service..*.*(..))/ aop:advisor advice-reftxAdvice pointcut-refserviceMethods/ /aop:config这段配置明确告诉你所有Service层以add/update/delete开头的方法必须走事务以get/find开头的只读方法禁用写操作。而Spring Boot的Transactional注解新手常犯的错是把它加在Controller层——结果事务根本不起作用因为代理对象没生效。SSM的XML配置虽然啰嗦但像一张技术地图每个组件的位置、职责、依赖关系都标得清清楚楚。2.2 电商场景的特殊性决定了SSM的不可替代性电商系统最怕什么不是高并发而是状态一致性。用户下单时要同时完成库存扣减、订单生成、优惠券核销、积分变动。这四个操作必须原子性执行否则就会出现“库存已扣但订单没生成”的资损事故。SSM框架下我们用Spring的PlatformTransactionManager MyBatis的SqlSessionTemplate能精准控制事务边界。看这个OrderServiceImpl的关键片段Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Autowired private ProductMapper productMapper; Autowired private CouponMapper couponMapper; Override Transactional(rollbackFor Exception.class) public boolean createOrder(Order order) throws Exception { // 1. 校验库存SELECT FOR UPDATE Product product productMapper.selectByIdWithLock(order.getProductId()); if (product.getStock() order.getQuantity()) { throw new RuntimeException(库存不足); } // 2. 扣减库存UPDATE product.setStock(product.getStock() - order.getQuantity()); productMapper.updateStock(product); // 3. 生成订单 orderMapper.insert(order); // 4. 核销优惠券此处省略具体逻辑 couponMapper.useCoupon(order.getCouponId()); return true; } }注意Transactional注解的位置——它必须在Service实现类的方法上且该类必须由Spring容器管理即通过Autowired注入。如果误写在接口上或用new关键字创建实例事务就会失效。这种细节在Spring Boot里容易被自动配置掩盖而在SSM中你必须亲手配置DataSource、TransactionManager、SqlSessionFactory每一步都强迫你理解“事务传播行为”“隔离级别”“回滚条件”这些面试高频考点。2.3 面试官真正想考察的从来不是你会不会用框架而是你懂不懂底层契约翻遍近半年Java岗位JD92%要求“熟悉SSM框架原理”。但“熟悉”二字背后藏着三道隐形门槛第一道是生命周期理解——Spring容器启动时先加载applicationContext.xml创建BeanFactory再初始化DispatcherServlet加载spring-mvc.xml最后才扫描Controller注解第二道是数据流转认知——HTTP请求进来经过HandlerMapping找到ControllerHandlerAdapter调用方法ModelAndView返回视图名ViewResolver解析成JSP路径第三道是故障定位能力——当页面404时你要知道先查web.xml里DispatcherServlet的url-pattern是否覆盖/*再查spring-mvc.xml的 context:component-scan 是否扫到了Controller包最后确认JSP文件是否真在WEB-INF/jsp目录下。这套电商项目.zip里我把所有XML配置文件都做了中文注释比如web.xml里这段!-- web.xml -- servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class !-- 指定SpringMVC配置文件位置 -- 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-namespringmvc/servlet-name !-- 注意这里必须是/不能是*.do否则静态资源会被拦截 -- url-pattern//url-pattern /servlet-mappingurl-pattern//url-pattern这个配置直接决定你的CSS/JS图片能否正常加载。很多初学者配成*.do结果页面一片空白——因为浏览器请求的bootstrap.css也被DispatcherServlet拦下来了而它根本找不到对应的Controller。这种坑只有亲手配过SSM的人才会刻骨铭心。3. 项目结构深度拆解从src/main/java到WEB-INF每个文件夹都在讲一个故事3.1 核心四层架构Controller-Service-Mapper-Entity不是摆设而是契约这个电商项目的src/main/java目录严格遵循经典分层com.xxx.ecommerce ├── controller // 接收HTTP请求只做参数校验和跳转不处理业务逻辑 │ ├── UserController.java │ ├── ProductController.java │ └── OrderController.java ├── service // 业务逻辑中枢调用多个Mapper处理事务和异常 │ ├── UserService.java │ ├── ProductService.java │ └── OrderService.java ├── dao // MyBatis的Mapper接口定义SQL操作契约 │ ├── UserMapper.java │ ├── ProductMapper.java │ └── OrderMapper.java ├── entity // 数据库表映射实体字段名与表列名严格对应 │ ├── User.java │ ├── Product.java │ └── Order.java └── util // 工具类如MD5加密、日期格式化、分页工具 └── PageUtil.java重点说说dao和entity的关系。很多人以为MyBatis的Mapper就是DAO层其实不然。真正的DAO层应该包含两部分接口Mapper和实现XML或注解。在这个项目里ProductMapper.java是接口public interface ProductMapper { // 根据ID查询商品带库存锁 Product selectByIdWithLock(Param(id) Long id); // 更新库存 int updateStock(Param(product) Product product); // 分页查询商品列表 ListProduct selectPage(Param(page) int page, Param(size) int size); }而对应的ProductMapper.xml在src/main/resources/mapper目录下!-- ProductMapper.xml -- mapper namespacecom.xxx.ecommerce.dao.ProductMapper select idselectByIdWithLock resultTypecom.xxx.ecommerce.entity.Product SELECT id, name, price, stock, description FROM product WHERE id #{id} FOR UPDATE !-- 关键行级锁防止超卖 -- /select update idupdateStock UPDATE product SET stock #{product.stock} WHERE id #{product.id} /update select idselectPage resultTypecom.xxx.ecommerce.entity.Product SELECT * FROM product LIMIT #{page}, #{size} !-- 注意MySQL分页用LIMITOracle用ROWNUM -- /select /mapper这里有个易错点select idselectByIdWithLock的id必须和接口方法名完全一致包括大小写。我见过太多人把接口方法写成selectByIdWithLock()XML里写成selectByIdWithlock()结果运行时报Invalid bound statement (not found)——因为MyBatis默认用接口全限定名方法名作为SQL ID大小写不匹配就找不到。3.2 配置文件矩阵四份XML文件构成SSM运行的神经网络整个项目能跑起来靠的是四份核心XML文件的精密配合文件名位置职责关键配置项web.xmlWEB-INF/Servlet容器入口定义DispatcherServlet和字符编码过滤器filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-classapplicationContext.xmlsrc/main/resources/Spring根容器管理Service、Dao、DataSource等非Web组件context:property-placeholder locationclasspath:jdbc.properties/spring-mvc.xmlsrc/main/resources/SpringMVC子容器管理Controller、视图解析器、静态资源处理mvc:resources mapping/static/** location/static//jdbc.propertiessrc/main/resources/数据库连接参数解耦配置与代码jdbc.urljdbc:mysql://localhost:3306/ecommerce?useSSLfalseserverTimezoneAsia/Shanghai特别强调jdbc.properties的加载方式。在applicationContext.xml里!-- 加载外部属性文件 -- context:property-placeholder locationclasspath:jdbc.properties/ !-- 定义数据源 -- 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 nameinitialSize value5/ property namemaxActive value20/ /bean${jdbc.url}这种占位符语法是Spring的PropertyPlaceholderConfigurer在起作用。如果忘记在XML里声明context:property-placeholder程序启动时会报错Could not resolve placeholder jdbc.url。这个细节90%的初学者第一次配都会栽跟头。3.3 前端资源组织JSPjQueryBootstrap拒绝过度复杂化电商项目前端用的是最朴素的技术栈JSP作为视图层jQuery处理AJAX交互Bootstrap 3.3.7构建UI。为什么不用Vue或React因为SSM项目的核心价值在于后端逻辑训练前端越简单越能聚焦于Controller如何接收表单、Service如何处理业务、Mapper如何执行SQL。比如商品列表页product_list.jsp% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head title商品列表/title link href/static/css/bootstrap.min.css relstylesheet /head body div classcontainer h2商品列表/h2 div classrow c:forEach items${page.list} varproduct div classcol-md-4 div classcard mb-4 div classcard-body h5 classcard-title${product.name}/h5 p classcard-text¥${product.price}/p p classtext-muted库存${product.stock}/p button classbtn btn-primary btn-sm onclickaddToCart(${product.id})加入购物车/button /div /div /div /c:forEach /div /div script src/static/js/jquery.min.js/script script function addToCart(productId) { $.post(/cart/add, {productId: productId}, function(data) { if(data.success) { alert(已加入购物车); } else { alert(加入失败 data.message); } }); } /script /body /html关键点在于c:forEach标签——这是JSTL核心标签库必须在web.xml里声明taglib且项目要引入jstl.jar和standard.jar。很多初学者直接复制JSP代码却忘了添加依赖结果页面显示一堆c:forEach原始标签。这个项目zip包里pom.xml已预置好dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdtaglibs/groupId artifactIdstandard/artifactId version1.1.2/version /dependency3.4 数据库脚本三张核心表撑起电商最小可行闭环项目附带的ecommerce.sql脚本只建三张表user、product、order。没有冗余字段没有过度设计-- 用户表 CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT MD5加密密码, email varchar(100) DEFAULT NULL, phone varchar(20) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品表 CREATE TABLE product ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 商品名称, price decimal(10,2) NOT NULL COMMENT 价格, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存, description text COMMENT 描述, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表 CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID, product_id bigint(20) NOT NULL COMMENT 商品ID, quantity int(11) NOT NULL DEFAULT 1 COMMENT 数量, total_price decimal(10,2) NOT NULL COMMENT 总金额, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0待支付1已支付2已发货, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_product_id (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意product.stock字段类型是int(11)不是bigint——库存不可能超过21亿件用int足够且节省空间。还有order.status用tinyint而非enum因为MySQL 5.7之前enum在JDBC里容易出类型转换异常。这些细节都是线上踩坑后沉淀下来的硬经验。4. 实操部署全流程从JDK安装到Tomcat启动避开95%的环境陷阱4.1 JDK 8环境配置不是装完就完事关键是验证CLASSPATH有效性很多初学者卡在第一步Java环境变量配好了java -version能显示版本但Eclipse里新建Java项目却报错The project cannot be built until build path errors are resolved。根源在于CLASSPATH未正确设置。正确步骤如下下载JDK 8u202不要用最新版SSM兼容性最好官网已下架推荐从Adoptium获取https://adoptium.net/temurin/releases/?version8选择jdk-8u202-b08版本Windows选x64 Installer。安装路径不含空格和中文强烈建议装到C:\Java\jdk1.8.0_202不要装在Program Files或桌面。配置系统环境变量JAVA_HOMEC:\Java\jdk1.8.0_202PATH追加%JAVA_HOME%\bin关键删除旧的CLASSPATH变量如果存在SSM项目不需要全局CLASSPATHEclipse/Maven会自动管理。验证命令# 检查Java版本 java -version # 检查javac编译器 javac -version # 检查环境变量是否生效应输出C:\Java\jdk1.8.0_202 echo %JAVA_HOME% # 测试编译在任意目录创建Test.java echo public class Test{public static void main(String[] args){System.out.println(\OK\);}} Test.java javac Test.java java Test提示如果javac命令报“不是内部或外部命令”说明PATH没配对如果java Test报“找不到或无法加载主类”大概率是CLASSPATH污染了默认类路径。4.2 Maven本地仓库初始化解决“依赖下载失败”的终极方案SSM项目用Maven管理依赖但国内访问中央仓库极慢常出现Could not transfer artifact错误。解决方案不是换镜像源而是预下载核心依赖修改settings.xml位于%MAVEN_HOME%\conf\在mirrors节点内添加阿里云镜像mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror强制更新本地仓库进入项目根目录执行mvn clean compile -U-U参数强制检查远程更新比单纯mvn clean install更彻底。手动导入缺失依赖万能兜底如果仍有jar包下载失败如spring-webmvc-4.3.29.RELEASE.jar去https://repo.maven.apache.org/maven2/org/springframework/spring-webmvc/4.3.29.RELEASE/ 下载jar和pom文件放入本地仓库对应路径C:\Users\YourName\.m2\repository\org\springframework\spring-webmvc\4.3.29.RELEASE\ ├─ spring-webmvc-4.3.29.RELEASE.jar └─ spring-webmvc-4.3.29.RELEASE.pom注意路径中的YourName必须和系统用户名完全一致大小写敏感。我曾帮一个学员调试他用户名是ZhangSan但本地仓库路径写成zhangsan导致Maven始终找不到jar。4.3 Tomcat 8.5部署WAR包部署与热部署的取舍之道这个电商项目打包成WAR部署到Tomcat 8.5不要用9.xJSP EL表达式兼容性有问题。关键配置在conf/server.xml!-- conf/server.xml -- Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8/ !-- 解决中文参数乱码 -- Engine nameCatalina defaultHostlocalhost Host namelocalhost appBasewebapps unpackWARstrue autoDeploytrue !-- 关键关闭JSP热编译避免开发时class文件冲突 -- Context path/ecommerce docBaseecommerce reloadablefalse/ /Host /Enginereloadablefalse是血泪教训。开启热部署后修改JSP再刷新Tomcat会重新编译生成新的.class文件但旧的Servlet实例还在内存里导致java.lang.LinkageError: loader constraint violation。生产环境必须关掉。部署步骤将项目用Eclipse导出为WAR右键项目 → Export → WAR file → 保存到%TOMCAT_HOME%\webapps\启动Tomcat双击%TOMCAT_HOME%\bin\startup.bat访问http://localhost:8080/ecommerce/product/list注意路径是/ecommerce不是项目名常见问题访问404。先检查webapps目录下是否有ecommerce.war和解压后的ecommerce文件夹再查看logs/catalina.out搜索SEVERE关键字90%是ClassNotFoundException——说明某个jar包没打进WAR需检查pom.xml的scope是否误设为provided。4.4 MySQL 5.7连接时区与SSL的双重陷阱项目用MySQL 5.7连接URL必须显式指定时区否则create_time字段存入的时间比实际晚8小时# jdbc.properties jdbc.urljdbc:mysql://localhost:3306/ecommerce?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai解决时区问题useSSLfalse关闭SSLMySQL 5.7默认要求SSL但本地开发无需allowPublicKeyRetrievaltrue是MySQL 8.0新增参数为兼容性保留。如果仍报Access denied for user检查MySQL用户权限-- 创建用户并授权 CREATE USER ecommercelocalhost IDENTIFIED BY 123456; GRANT ALL PRIVILEGES ON ecommerce.* TO ecommercelocalhost; FLUSH PRIVILEGES;注意ecommercelocalhost和ecommerce%是不同用户前者只能本地连接后者允许远程。开发时用localhost更安全。5. 核心功能实操详解从用户注册到订单生成每一步都附带避坑指南5.1 用户注册流程MD5盐值加密与唯一性校验的落地实现注册功能看似简单但涉及三个安全关键点密码加密、用户名唯一校验、邮箱格式验证。Controller层代码Controller public class UserController { Autowired private UserService userService; RequestMapping(value /user/register, method RequestMethod.POST) ResponseBody public Result register(RequestBody User user) { // 1. 前端已做基础校验后端必须二次校验 if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword()) || !EmailValidator.isValid(user.getEmail())) { return Result.fail(参数不合法); } // 2. 密码加盐MD5盐值存入数据库非固定字符串 String salt UUID.randomUUID().toString().replace(-, ); String encryptedPassword DigestUtils.md5Hex(user.getPassword() salt); user.setPassword(encryptedPassword); user.setSalt(salt); try { userService.register(user); return Result.success(注册成功); } catch (DuplicateKeyException e) { return Result.fail(用户名已存在); } catch (Exception e) { return Result.fail(注册失败 e.getMessage()); } } }关键细节RequestBody接收JSON要求前端发送{username:abc,password:123,email:ab.com}不是form-data。DigestUtils.md5Hex()来自commons-codec不是自己写MD5工具类——避免因字节序或编码问题导致加密结果不一致。DuplicateKeyException捕获唯一索引冲突比查库判断更高效减少一次SQL查询。实操心得我最初用SELECT COUNT(*) FROM user WHERE username?判断用户名是否存在QPS到200时数据库CPU飙升。改成唯一索引异常捕获后响应时间从80ms降到12ms。5.2 商品浏览分页MyBatis动态SQL与MySQL LIMIT的性能优化商品列表页要求分页但直接LIMIT 10000,20会导致深分页性能暴跌。解决方案是游标分页Cursor-based Pagination但SSM项目为教学简化仍用传统分页重点优化SQL!-- ProductMapper.xml -- select idselectPage resultTypecom.xxx.ecommerce.entity.Product SELECT id, name, price, stock, description FROM product WHERE status 1 !-- 只查上架商品 -- ORDER BY id DESC !-- 必须有ORDER BY否则分页结果不稳定 -- LIMIT #{page}, #{size} /select#{page}和#{size}是预编译参数防止SQL注入。注意ORDER BY id DESC——如果没有排序MySQL可能返回任意顺序的记录导致第2页出现第1页的数据。Service层计算分页参数Service public class ProductServiceImpl implements ProductService { Override public PageResultProduct listProducts(int pageNum, int pageSize) { // 防止恶意传参pageNum不能小于1pageSize不能大于100 pageNum Math.max(1, pageNum); pageSize Math.min(100, pageSize); int offset (pageNum - 1) * pageSize; ListProduct list productMapper.selectPage(offset, pageSize); // 查询总记录数单独SQL不和分页合并 int total productMapper.countAll(); return new PageResult(list, total, pageNum, pageSize); } }PageResult是自定义分页对象包含list、total、pageNum、pageSize、pages总页数等字段比PageHelper更透明。5.3 购物车与订单生成分布式事务的降级方案严格来说SSM单体应用不存在分布式事务但电商场景的“库存扣减订单生成”必须保证强一致性。项目采用本地事务乐观锁方案Override Transactional(rollbackFor Exception.class) public boolean createOrder(Order order) throws Exception { // 1. 乐观锁更新库存WHERE stock #{quantity} AND version #{version} Product product productMapper.selectById(order.getProductId()); if (product.getStock() order.getQuantity()) { throw new RuntimeException(库存不足); } // 2. 执行扣减带版本号 int updated productMapper.updateStockWithVersion( product.getId(), order.getQuantity(), product.getVersion() ); if (updated 0) { throw new RuntimeException(库存已被抢购请刷新重试); } // 3. 生成订单 orderMapper.insert(order); return true; }对应的Mapper XMLupdate idupdateStockWithVersion UPDATE product SET stock stock - #{quantity}, version version 1 WHERE id #{id} AND stock #{quantity} AND version #{version} /updateversion字段是乐观锁版本号每次更新自增。如果两个请求同时读到version1都尝试更新第二个请求的WHERE条件version 1不成立updated返回0业务层捕获后提示用户重试。这比悲观锁SELECT FOR UPDATE更轻量适合读多写少的电商场景。5.4 后台管理功能RBAC权限模型的极简实现后台管理页/admin/login采用最简RBAC用户表加role字段0普通用户1管理员Controller加PreAuthorize注解Controller RequestMapping(/admin) public class AdminController { RequestMapping(/dashboard) public String dashboard(Model model) { // 仅管理员可访问 Authentication auth SecurityContextHolder.getContext().getAuthentication(); if (!ADMIN.equals(auth.getAuthorities().stream() .map(GrantedAuthority::getAuthority).findFirst().orElse())) { return redirect:/error/403; } return admin/dashboard; } }权限校验逻辑在UserDetailsServiceImpl里Service public class UserDetailsServiceImpl implements UserDetailsService { Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user userMapper.findByUsername(username); if (user null) { throw new UsernameNotFoundException(用户不存在); } // 角色赋权 ListGrantedAuthority authorities new ArrayList(); if (user.getRole() 1) { authorities.add(new SimpleGrantedAuthority(ADMIN)); } else { authorities.add(new SimpleGrantedAuthority(USER)); } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), authorities ); } }注意Spring Security的PreAuthorize需要EnableGlobalMethodSecurity注解启用项目已在spring-security.xml中配置。6. 面试高频问题实战解析从“SSM怎么整合”到“订单超卖怎么解决”6.1 SSM整合原理三步初始化画出Bean生命周期图面试官问“SSM怎么整合”绝不是要你背XML标签而是考察你是否理解容器启动流程。答案分三步第一步ServletContext初始化Spring根容器Tomcat启动时读取web.xml创建ContextLoaderListener它监听ServletContextEvent调用ContextLoader.initWebApplicationContext()加载applicationContext.xml创建XmlWebApplicationContextSpring根容器将BeanFactory存入ServletContext属性中。第二步DispatcherServlet初始化SpringMVC子容器servlet配置的DispatcherServlet被创建它继承FrameworkServlet在initServletBean()中调用initWebApplicationContext()此时会检测父容器即第一步创建的根容器将自己的WebApplicationContext设置为子容器从而可以引用根容器的Service/Dao Bean。第三步Bean创建与依赖注入Spring容器按bean声明顺序创建Bean先创建DataSource再创建SqlSessionFactoryBean依赖DataSource接着创建MapperScannerConfigurer扫描Mapper接口最后创建Service Bean依赖Mapper。Autowired注入发生在Bean初始化后、afterPropertiesSet()之前。验证方法在任意Service类里加PostConstruct方法打印this.getClass().getName()启动时就能看到Bean创建顺序。6.2 事务失效的五大场景代码写对了为什么事务不回滚事务失效是SSM面试必问常见原因场景代码示例修复方案1. 自调用public void methodA() { methodB(); }Transactional public void methodB() {}改为this.methodB()→((Service) AopContext.currentProxy()).methodB()2. 异常未抛出try { ... } catch(Exception e) { log.error(e); }删除catch或catch后throw new RuntimeException(e)3. 非public方法Transactional private void method() {}必须public因为Spring AOP基于JDK动态代理4. 错误的异常类型Transactional(rollbackFor SQLException.class)但实际抛出RuntimeExceptionrollbackFor Exception.class或明确指定异常5. 事务传播行为错误Transactional(propagation Propagation.SUPPORTS)改为REQUIRED默认最典型的是“自调用”问题。比如订单服务里Service public class OrderService { p a hrefhttps://download.csdn.net/download/liufang_imei/88276187 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p