1. SSM框架网上商城系统概述
SSM(Spring+SpringMVC+MyBatis)作为JavaEE开发的经典组合框架,在企业级应用开发中占据重要地位。网上商城系统作为典型的电子商务应用,其核心功能模块包括用户管理、商品展示、购物车、订单处理等,非常适合采用SSM框架进行开发。
这个开源项目完整实现了基于SSM框架的网上商城系统,特别注重CRUD(增删改查)功能的实现细节。与市面上很多只展示核心代码的教程不同,本项目从环境搭建到功能实现,再到部署测试,提供了全流程的详细指导。
提示:对于刚接触SSM框架的开发者,建议先理解MVC架构模式和各层职责划分,这对后续开发至关重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境与项目初始化
2.1 基础环境配置
开发网上商城系统需要准备以下环境:
- JDK 1.8及以上版本
- Tomcat 8.0或9.0应用服务器
- MySQL 5.7数据库
- Maven 3.6+项目管理工具
- Eclipse或IntelliJ IDEA开发工具
在环境配置中,MySQL的字符集设置需要特别注意:
sql复制# 创建数据库时指定字符集
CREATE DATABASE eshop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
2.2 项目结构搭建
标准的SSM项目采用分层架构,主要目录结构如下:
code复制eshop/
├── src/
│ ├── main/
│ │ ├── java/ # Java源代码
│ │ │ ├── com/
│ │ │ │ └── eshop/
│ │ │ │ ├── controller/ # 控制器层
│ │ │ │ ├── service/ # 业务逻辑层
│ │ │ │ ├── dao/ # 数据访问层
│ │ │ │ └── entity/ # 实体类
│ │ ├── resources/ # 资源文件
│ │ │ ├── spring/ # Spring配置文件
│ │ │ ├── mybatis/ # MyBatis映射文件
│ │ │ └── jdbc.properties # 数据库配置
│ │ └── webapp/
│ │ ├── WEB-INF/
│ │ │ └── views/ # JSP视图文件
│ │ └── static/ # 静态资源
└── pom.xml # Maven配置文件
3. 核心功能模块实现
3.1 商品管理模块CRUD实现
商品管理是网上商城的核心模块,我们以这个模块为例展示完整的CRUD实现流程。
实体类设计:
java复制public class Product {
private Integer id;
private String name;
private BigDecimal price;
private Integer stock;
private String description;
private String imageUrl;
private Date createTime;
private Date updateTime;
// 省略getter/setter
}
DAO层实现:
java复制@Repository
public interface ProductDao {
int insert(Product product);
int deleteById(Integer id);
int update(Product product);
Product selectById(Integer id);
List<Product> selectAll();
List<Product> selectByCondition(@Param("name") String name,
@Param("minPrice") BigDecimal minPrice,
@Param("maxPrice") BigDecimal maxPrice);
}
对应的MyBatis映射文件:
xml复制<mapper namespace="com.eshop.dao.ProductDao">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO product(name, price, stock, description, image_url)
VALUES(#{name}, #{price}, #{stock}, #{description}, #{imageUrl})
</insert>
<update id="update">
UPDATE product SET
name = #{name},
price = #{price},
stock = #{stock},
description = #{description},
image_url = #{imageUrl},
update_time = NOW()
WHERE id = #{id}
</update>
<select id="selectById" resultType="Product">
SELECT * FROM product WHERE id = #{id}
</select>
</mapper>
3.2 业务逻辑层实现
Service层负责业务逻辑处理,以商品服务为例:
java复制@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductDao productDao;
@Override
@Transactional
public boolean addProduct(Product product) {
if(product == null || StringUtils.isEmpty(product.getName())) {
throw new IllegalArgumentException("商品信息不完整");
}
return productDao.insert(product) > 0;
}
@Override
@Transactional(readOnly = true)
public PageInfo<Product> getProducts(int pageNum, int pageSize,
String name, BigDecimal minPrice, BigDecimal maxPrice) {
PageHelper.startPage(pageNum, pageSize);
List<Product> products = productDao.selectByCondition(name, minPrice, maxPrice);
return new PageInfo<>(products);
}
}
3.3 控制器层实现
Controller层处理HTTP请求并返回响应:
java复制@Controller
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/list")
public String listProducts(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String name,
@RequestParam(required = false) BigDecimal minPrice,
@RequestParam(required = false) BigDecimal maxPrice,
Model model) {
PageInfo<Product> pageInfo = productService.getProducts(
pageNum, pageSize, name, minPrice, maxPrice);
model.addAttribute("pageInfo", pageInfo);
return "product/list";
}
@PostMapping("/add")
@ResponseBody
public Result addProduct(@Valid Product product, BindingResult result) {
if(result.hasErrors()) {
return Result.error(result.getFieldError().getDefaultMessage());
}
return productService.addProduct(product) ?
Result.success("添加成功") : Result.error("添加失败");
}
}
4. 关键技术与实现细节
4.1 分页查询实现
网上商城的商品列表通常数据量较大,必须实现分页查询。我们使用PageHelper插件实现:
- 添加Maven依赖:
xml复制<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>
- Spring配置:
xml复制<bean id="pageInterceptor" class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
- 使用示例:
java复制// 在查询方法前调用startPage
PageHelper.startPage(pageNum, pageSize);
List<Product> products = productDao.selectByCondition(name, minPrice, maxPrice);
PageInfo<Product> pageInfo = new PageInfo<>(products);
4.2 文件上传功能
商品图片上传是商城系统的常见需求,SpringMVC提供了MultipartResolver支持:
- 配置文件上传解析器:
xml复制<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!-- 10MB -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
- 控制器处理方法:
java复制@PostMapping("/upload")
@ResponseBody
public Result uploadImage(@RequestParam("file") MultipartFile file) {
if(file.isEmpty()) {
return Result.error("请选择文件");
}
try {
String fileName = UUID.randomUUID() +
file.getOriginalFilename().substring(
file.getOriginalFilename().lastIndexOf("."));
String filePath = "/upload/" + fileName;
File dest = new File(request.getServletContext().getRealPath(filePath));
file.transferTo(dest);
return Result.success(filePath);
} catch (IOException e) {
return Result.error("上传失败");
}
}
4.3 事务管理配置
商城系统的订单处理等操作需要保证事务一致性:
xml复制<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在Service方法上使用@Transactional注解:
java复制@Override
@Transactional
public boolean placeOrder(Order order) {
// 1. 扣减库存
productDao.reduceStock(order.getProductId(), order.getQuantity());
// 2. 创建订单
orderDao.insert(order);
// 3. 记录日志
logDao.insert(new OrderLog(order.getId(), "订单创建"));
return true;
}
5. 常见问题与解决方案
5.1 中文乱码问题
SSM项目中的中文乱码是常见问题,需要多层面解决:
- 数据库连接配置:
properties复制jdbc.url=jdbc:mysql://localhost:3306/eshop?useUnicode=true&characterEncoding=UTF-8
- web.xml配置过滤器:
xml复制<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- JSP页面设置:
jsp复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5.2 跨域问题处理
前后端分离开发时可能遇到跨域问题,解决方案:
- 使用@CrossOrigin注解:
java复制@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*", maxAge = 3600)
public class ApiController {
// ...
}
- 配置全局CORS过滤器:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.maxAge(3600);
}
}
5.3 性能优化建议
- MyBatis二级缓存配置:
xml复制<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 在Mapper XML中启用缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
- 连接池配置(以Druid为例):
properties复制# 初始连接数
druid.initialSize=5
# 最大连接数
druid.maxActive=20
# 获取连接等待超时时间
druid.maxWait=60000
# 间隔多久检测需要关闭的空闲连接
druid.timeBetweenEvictionRunsMillis=60000
# 连接最小生存时间
druid.minEvictableIdleTimeMillis=300000
- SQL优化建议:
- 为常用查询字段添加索引
- 避免SELECT *,只查询需要的字段
- 复杂查询考虑使用JOIN替代子查询
- 大数据量表考虑分表分库
6. 项目部署与测试
6.1 项目打包部署
使用Maven打包项目:
bash复制mvn clean package
生成的war包部署到Tomcat的webapps目录下,或者配置Tomcat直接运行项目:
- 在Eclipse中配置Server:
- 右键项目 → Run As → Run on Server
- 选择已配置的Tomcat服务器
- 访问地址:
code复制http://localhost:8080/eshop/
6.2 单元测试编写
使用JUnit进行Service层测试:
java复制@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-*.xml")
public class ProductServiceTest {
@Autowired
private ProductService productService;
@Test
public void testAddProduct() {
Product product = new Product();
product.setName("测试商品");
product.setPrice(new BigDecimal("99.99"));
product.setStock(100);
boolean result = productService.addProduct(product);
assertTrue(result);
assertNotNull(product.getId());
}
}
6.3 接口测试工具
使用Postman测试RESTful接口:
- GET请求测试:
code复制GET http://localhost:8080/eshop/product/list?pageNum=1&pageSize=10
- POST请求测试(JSON格式):
code复制POST http://localhost:8080/eshop/product/add
Content-Type: application/json
{
"name": "新款手机",
"price": 2999.00,
"stock": 50,
"description": "最新款智能手机"
}
7. 项目扩展与进阶
7.1 集成Redis缓存
- 添加依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置RedisTemplate:
java复制@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
- 使用缓存示例:
java复制@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String PRODUCT_CACHE_PREFIX = "product:";
@Override
public Product getProductById(Integer id) {
String key = PRODUCT_CACHE_PREFIX + id;
Product product = (Product) redisTemplate.opsForValue().get(key);
if(product == null) {
product = productDao.selectById(id);
if(product != null) {
redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS);
}
}
return product;
}
}
7.2 集成Swagger文档
- 添加依赖:
xml复制<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
- 配置Swagger:
java复制@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.eshop.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("网上商城API文档")
.description("SSM框架实现的网上商城系统接口文档")
.version("1.0")
.build();
}
}
- 访问地址:
code复制http://localhost:8080/eshop/swagger-ui.html
7.3 安全控制集成
- 添加Spring Security依赖:
xml复制<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.6.1</version>
</dependency>
- 安全配置类:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/", "/home", "/product/list").permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.and()
.logout()
.logoutSuccessUrl("/");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}password").roles("USER")
.and()
.withUser("admin").password("{noop}admin").roles("ADMIN");
}
}
在实际项目中,我通常会为每个Controller方法添加细粒度的权限控制,同时将用户信息存储在数据库中而非内存中。对于密码存储,一定要使用BCrypt等安全哈希算法,而不是明文或简单加密。
