1. SSM框架的技术定位与时代背景
2015年前后,SSM(Spring+SpringMVC+MyBatis)组合曾是企业级Java开发的黄金标准。这套技术栈的流行并非偶然——Spring的轻量级IoC容器解决了EJB的臃肿问题,MyBatis用灵活的SQL映射取代了Hibernate的"全自动魔法",而SpringMVC则提供了清晰的MVC分离架构。当时我们团队接手一个电商后台系统时,技术选型会上SSM几乎全票通过,现在看来这个选择确实经受住了时间考验。
提示:虽然现在Spring Boot已成主流,但国内仍有大量存量系统采用SSM架构,理解其设计思想对维护老系统和面试中的"考古题"都很有帮助
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建的魔鬼细节
2.1 Maven依赖的版本炼狱
当年最头疼的就是处理依赖冲突。比如同时引入Spring 4.2.5和MyBatis 3.4.6时,必须手动排除掉冲突的javassist版本。这是典型的问题依赖树:
xml复制<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
<exclusions>
<exclusion>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</exclusion>
</exclusions>
</dependency>
2.2 XML配置的智慧
现在回忆起来,当年那些冗长的XML配置其实藏着不少设计智慧。比如Spring的DispatcherServlet配置中,
3. 架构设计的经典模式
3.1 分层结构的边界守卫
标准的四层架构(controller-service-dao-entity)看似简单,但实际项目中经常出现层级污染。最典型的反例就是在Controller里直接调用Dao,或者把业务逻辑写在Entity的getter方法中。我们团队曾制定过严格的代码审查规则:
-
Controller层只允许出现:
- 参数校验注解(@Valid)
- 简单的数据组装
- 统一的异常捕获
-
Service层必须包含:
- 事务边界(@Transactional)
- 业务完整性校验
- 领域逻辑组合
3.2 MyBatis的SQL管理哲学
与Hibernate不同,MyBatis主张"SQL也是需要精心设计的艺术品"。我们项目中有条商品查询SQL,通过动态标签实现了十几种组合查询,却依然保持可读性:
xml复制<select id="selectProducts" resultMap="productResult">
SELECT * FROM product
<where>
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
<if test="minPrice != null">
AND price >= #{minPrice}
</if>
<choose>
<when test="sortBy == 'price'">
ORDER BY price ${order}
</when>
<otherwise>
ORDER BY create_time DESC
</otherwise>
</choose>
</where>
</select>
4. 性能优化的实战经验
4.1 连接池的隐藏成本
很多团队直接使用默认的DBCP配置,却不知道在并发场景下可能成为性能瓶颈。我们通过JMeter压测发现,调整这些参数后TPS提升了3倍:
| 参数 | 默认值 | 优化值 | 作用说明 |
|---|---|---|---|
| maxActive | 8 | 50 | 最大活跃连接数 |
| maxWait | -1 | 3000 | 获取连接超时时间(ms) |
| validationQuery | null | "SELECT 1" | 连接有效性检测SQL |
4.2 二级缓存的陷阱
MyBatis的二级缓存看似美好,但在分布式环境中可能引发严重的数据一致性问题。我们曾在促销系统中踩过坑——由于没有正确实现Serializable接口,导致缓存反序列化失败。最终采用的解决方案是:
- 实体类必须实现Serializable
- 为每个Mapper配置单独的缓存策略
- 关键业务方法添加@CacheEvict注解
5. 向Spring Boot的平滑迁移
5.1 配置的现代化改造
老项目迁移时,可以逐步将XML配置转为Java Config。比如把这样的spring-dao.xml:
xml复制<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
转换为配置类:
java复制@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
return factory.getObject();
}
}
5.2 兼容性处理技巧
在混合架构过渡期,我们总结出这些经验:
- 保持老版Spring的XML配置在独立文件中
- 使用@ImportResource引入旧配置
- 新功能直接用Spring Boot方式开发
- 统一用Spring Boot的properties管理配置
6. 那些年我们踩过的坑
6.1 事务失效的N种姿势
SSM中最容易出错的就是事务管理。有一次我们花了三天时间排查,最终发现是因为:
java复制public class UserService {
// 错误示例:自调用导致事务失效
public void createUser(User user) {
validateUser(user); // 内部方法的事务注解不生效
userDao.insert(user);
}
@Transactional
private void validateUser(User user) {
// 验证逻辑...
}
}
6.2 JSON序列化的幽灵字段
当Entity中存在双向关联时,Jackson序列化可能导致栈溢出。比如:
java复制@Entity
public class Order {
@ManyToOne
private User user;
}
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
解决方案是使用@JsonIgnore或@JsonManagedReference/@JsonBackReference注解控制序列化边界。
7. 留给后来者的建议
虽然现在更推荐使用Spring Boot+MyBatis-Plus组合,但理解SSM的底层机制仍然有价值。如果现在要启动一个新项目,我的建议是:
- 小项目直接用Spring Boot Starter
- 复杂系统可以考虑保留MyBatis的灵活性
- 一定要建立清晰的代码分层规范
- 自动化测试必须覆盖事务边界
那些年在SSM中积累的经验,比如SQL优化、事务管理、缓存策略等,在任何Java技术栈中都不会过时。最近在面试候选人时,我仍然会通过SSM相关的问题考察其对ORM和Spring原理的理解深度。
