一、项目结构与依赖配置
1.1 项目结构
text
src/main/java
├── com.example
│ ├── Application.java # SpringBoot启动类
│ ├── config/ # 配置类目录
│ │ ├── DataSourceConfig.java # 数据源配置
│ │ ├── MybatisConfig.java # MyBatis配置
│ │ ├── WebMvcConfig.java # SpringMVC配置
│ │ └── TransactionConfig.java # 事务配置
│ ├── controller/ # 控制层
│ ├── service/ # 业务层
│ │ ├── UserService.java
│ │ └── impl/UserServiceImpl.java
│ ├── mapper/ # 数据访问层
│ ├── entity/ # 实体类
│ ├── dto/ # 数据传输对象
│ └── common/ # 公共组件
│ ├── Result.java # 统一响应
│ ├── PageResult.java # 分页响应
│ └── exception/ # 异常处理
└── resources
├── application.yml # 主配置文件
├── mapper/ # MyBatis映射文件
│ └── UserMapper.xml
└── static/ # 静态资源
1.2 Maven 依赖配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-ssm</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<mybatis-spring-boot.version>2.3.1</mybatis-spring-boot.version>
<druid.version>1.2.18</druid.version>
<pagehelper.version>1.4.2</pagehelper.version>
</properties>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- PageHelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Jackson 日期处理 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- 验证框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、配置文件
2.1 application.yml 主配置
yaml
# 应用配置
server:
port: 8080
servlet:
context-path: /api
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1
# Spring配置
spring:
application:
name: springboot-ssm-demo
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
# Druid连接池配置
druid:
# 初始化连接数量
initial-size: 5
# 最小空闲连接数
min-idle: 5
# 最大活跃连接数
max-active: 20
# 获取连接时最大等待时间,单位毫秒
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
# 用来检测连接是否有效的sql,要求是一个查询语句
validation-query: SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性
test-while-idle: true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
test-on-borrow: false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
test-on-return: false
# 是否缓存preparedStatement,也就是PSCache
pool-prepared-statements: true
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计
filters: stat,wall,log4j
# 合并多个DruidDataSource的监控数据
use-global-data-source-stat: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 配置DruidStatViewServlet
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
reset-enable: false
# 配置DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
# Jackson配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write-dates-as-timestamps: false
deserialization:
fail-on-unknown-properties: false
# 文件上传配置
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
# MyBatis配置
mybatis:
# 映射文件位置
mapper-locations: classpath:mapper/*.xml
# 实体类别名包
type-aliases-package: com.example.entity
configuration:
# 开启驼峰命名自动映射
map-underscore-to-camel-case: true
# 开启缓存
cache-enabled: true
# 配置日志实现
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# PageHelper分页插件配置
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
# 日志配置
logging:
level:
com.example: debug
com.example.mapper: trace
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
file:
name: logs/application.log
# 自定义配置
app:
jwt:
secret: mySecretKey
expiration: 86400000
file:
upload-dir: /tmp/uploads
2.2 多环境配置
yaml
# application-dev.yml (开发环境)
spring:
datasource:
url: jdbc:mysql://localhost:3306/ssm_demo_dev
redis:
host: localhost
port: 6379
logging:
level:
com.example: debug
---
# application-test.yml (测试环境)
spring:
datasource:
url: jdbc:mysql://test-db:3306/ssm_demo_test
username: test_user
password: test_pass
redis:
host: test-redis
port: 6379
logging:
level:
com.example: info
---
# application-prod.yml (生产环境)
spring:
datasource:
url: jdbc:mysql://prod-db:3306/ssm_demo_prod
username: ${DB_USERNAME:prod_user}
password: ${DB_PASSWORD:prod_pass}
druid:
initial-size: 10
min-idle: 10
max-active: 50
redis:
host: ${REDIS_HOST:redis}
password: ${REDIS_PASSWORD}
logging:
level:
com.example: warn
file:
name: /app/logs/ssm-demo.log
三、配置类
3.1 启动类
java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.example.mapper") // 扫描Mapper接口
@EnableTransactionManagement // 开启事务管理
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.2 MyBatis 配置类
java
package com.example.config;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@MapperScan("com.example.mapper")
public class MybatisConfig {
@Autowired
private DataSource dataSource;
/**
* 配置SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 设置映射文件位置
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
// 配置分页插件
sessionFactory.setPlugins(new Interceptor[]{pageInterceptor()});
// MyBatis配置
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(true);
configuration.setLazyLoadingEnabled(false);
configuration.setAggressiveLazyLoading(false);
configuration.setMultipleResultSetsEnabled(true);
configuration.setUseGeneratedKeys(true);
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}
/**
* 分页插件配置
*/
@Bean
public PageInterceptor pageInterceptor() {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("params", "count=countSql");
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
}
3.3 Spring MVC 配置类
java
package com.example.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 静态资源映射
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
// Swagger UI 资源映射
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
/**
* 跨域配置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
/**
* 拦截器配置
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加日志拦截器
registry.addInterceptor(logInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static/**", "/error");
// 添加认证拦截器
registry.addInterceptor(authInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/login", "/api/auth/register");
}
/**
* 消息转换器配置
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
}
/**
* Jackson消息转换器
*/
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper());
return converter;
}
/**
* 自定义ObjectMapper
*/
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 处理LocalDateTime
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
));
objectMapper.registerModule(timeModule);
// 处理Long类型(防止前端精度丢失)
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
// 配置
objectMapper.setDateFormat(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
return objectMapper;
}
@Bean
public LogInterceptor logInterceptor() {
return new LogInterceptor();
}
@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor();
}
}
3.4 事务配置类
java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
/**
* 配置事务管理器
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
四、核心组件实现
4.1 统一响应封装
java
package com.example.common;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result<T> {
// 状态码枚举
public enum Code {
SUCCESS(200, "成功"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "资源不存在"),
INTERNAL_ERROR(500, "服务器内部错误"),
BUSINESS_ERROR(600, "业务异常");
private final Integer code;
private final String message;
Code(Integer code, String message) {
this.code = code;
this.message = message;
}
}
private Integer code;
private String message;
private T data;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime timestamp;
private String path;
private Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = LocalDateTime.now();
}
// 成功响应
public static <T> Result<T> success(T data) {
return new Result<>(Code.SUCCESS.code, Code.SUCCESS.message, data);
}
public static <T> Result<T> success() {
return success(null);
}
// 失败响应
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
public static <T> Result<T> error(Code code) {
return new Result<>(code.code, code.message, null);
}
public static <T> Result<T> error(String message) {
return new Result<>(Code.BUSINESS_ERROR.code, message, null);
}
// 设置路径
public Result<T> path(String path) {
this.path = path;
return this;
}
}
4.2 分页结果封装
java
package com.example.common;
import lombok.Data;
import java.util.List;
@Data
public class PageResult<T> {
private List<T> list;
private Long total;
private Integer pageNum;
private Integer pageSize;
private Integer pages;
public PageResult(List<T> list, Long total, Integer pageNum, Integer pageSize) {
this.list = list;
this.total = total;
this.pageNum = pageNum;
this.pageSize = pageSize;
this.pages = (int) Math.ceil((double) total / pageSize);
}
public static <T> PageResult<T> of(List<T> list, Long total, Integer pageNum, Integer pageSize) {
return new PageResult<>(list, total, pageNum, pageSize);
}
}
4.3 异常处理
java
package com.example.common.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class BusinessException extends RuntimeException {
private Integer code;
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(String message) {
this(500, message);
}
public BusinessException(Result.Code code) {
this(code.code, code.message);
}
}
java
package com.example.common.exception;
import com.example.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
log.warn("业务异常: {} - {}", request.getRequestURI(), e.getMessage());
return Result.error(e.getCode(), e.getMessage()).path(request.getRequestURI());
}
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException e, HttpServletRequest request) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验失败: {} - {}", request.getRequestURI(), message);
return Result.error(400, message).path(request.getRequestURI());
}
/**
* 处理数据绑定异常
*/
@ExceptionHandler(BindException.class)
public Result<Void> handleBindException(BindException e, HttpServletRequest request) {
String message = e.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("数据绑定异常: {} - {}", request.getRequestURI(), message);
return Result.error(400, message).path(request.getRequestURI());
}
/**
* 处理参数校验异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
String message = e.getConstraintViolations().stream()
.map(violation -> violation.getMessage())
.collect(Collectors.joining(", "));
log.warn("参数校验异常: {} - {}", request.getRequestURI(), message);
return Result.error(400, message).path(request.getRequestURI());
}
/**
* 处理其他所有异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<Void> handleException(Exception e, HttpServletRequest request) {
log.error("系统异常: {} - {}", request.getRequestURI(), e.getMessage(), e);
return Result.error(Result.Code.INTERNAL_ERROR).path(request.getRequestURI());
}
}
五、数据层实现
5.1 实体类
java
package com.example.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
private String username;
private String password;
private String email;
private String phone;
private Integer age;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
// 状态枚举
public enum Status {
ACTIVE(1, "活跃"),
INACTIVE(0, "非活跃"),
LOCKED(-1, "锁定");
private final Integer code;
private final String desc;
Status(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public Integer getCode() {
return code;
}
public String getDesc() {
return desc;
}
public static Status valueOf(Integer code) {
for (Status status : values()) {
if (status.getCode().equals(code)) {
return status;
}
}
throw new IllegalArgumentException("未知的状态码: " + code);
}
}
}
5.2 Mapper 接口
java
package com.example.mapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserMapper {
/**
* 根据ID查询用户
*/
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
/**
* 查询所有用户
*/
@Select("SELECT * FROM user")
List<User> selectAll();
/**
* 根据用户名查询用户
*/
@Select("SELECT * FROM user WHERE username = #{username}")
User selectByUsername(String username);
/**
* 插入用户
*/
@Insert("INSERT INTO user (username, password, email, phone, age, status, create_time, update_time) " +
"VALUES (#{username}, #{password}, #{email}, #{phone}, #{age}, #{status}, #{createTime}, #{updateTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
/**
* 更新用户
*/
@Update("UPDATE user SET username=#{username}, email=#{email}, phone=#{phone}, " +
"age=#{age}, status=#{status}, update_time=#{updateTime} WHERE id=#{id}")
int update(User user);
/**
* 删除用户
*/
@Delete("DELETE FROM user WHERE id = #{id}")
int delete(Long id);
/**
* 根据条件查询用户(使用XML配置)
*/
List<User> selectByCondition(User user);
/**
* 分页查询用户(使用XML配置)
*/
List<User> selectByPage(@Param("offset") Integer offset,
@Param("limit") Integer limit,
@Param("user") User user);
/**
* 统计用户数量
*/
Long countByCondition(User user);
}
5.3 XML 映射文件
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.example.entity.User">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
<result column="email" property="email" />
<result column="phone" property="phone" />
<result column="age" property="age" />
<result column="status" property="status" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
</resultMap>
<sql id="Base_Column_List">
id, username, password, email, phone, age, status, create_time, update_time
</sql>
<!-- 根据条件查询用户 -->
<select id="selectByCondition" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
<if test="phone != null and phone != ''">
AND phone LIKE CONCAT('%', #{phone}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 分页查询用户 -->
<select id="selectByPage" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM user
<where>
<if test="user.username != null and user.username != ''">
AND username LIKE CONCAT('%', #{user.username}, '%')
</if>
<if test="user.email != null and user.email != ''">
AND email LIKE CONCAT('%', #{user.email}, '%')
</if>
<if test="user.phone != null and user.phone != ''">
AND phone LIKE CONCAT('%', #{user.phone}, '%')
</if>
<if test="user.age != null">
AND age = #{user.age}
</if>
<if test="user.status != null">
AND status = #{user.status}
</if>
</where>
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
<!-- 统计用户数量 -->
<select id="countByCondition" resultType="java.lang.Long">
SELECT COUNT(*) FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
<if test="phone != null and phone != ''">
AND phone LIKE CONCAT('%', #{phone}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
</mapper>
六、业务层实现
6.1 Service 接口
java
package com.example.service;
import com.example.common.PageResult;
import com.example.entity.User;
import java.util.List;
public interface UserService {
/**
* 根据ID查询用户
*/
User getUserById(Long id);
/**
* 查询所有用户
*/
List<User> getAllUsers();
/**
* 根据用户名查询用户
*/
User getUserByUsername(String username);
/**
* 创建用户
*/
void createUser(User user);
/**
* 更新用户
*/
void updateUser(User user);
/**
* 删除用户
*/
void deleteUser(Long id);
/**
* 根据条件查询用户
*/
List<User> getUsersByCondition(User user);
/**
* 分页查询用户
*/
PageResult<User> getUsersByPage(Integer pageNum, Integer pageSize, User user);
/**
* 用户登录
*/
User login(String username, String password);
}
6.2 Service 实现类
java
package com.example.service.impl;
import com.example.common.PageResult;
import com.example.common.exception.BusinessException;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional(readOnly = true)
public User getUserById(Long id) {
log.info("查询用户,ID: {}", id);
User user = userMapper.selectById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
return user;
}
@Override
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return userMapper.selectAll();
}
@Override
@Transactional(readOnly = true)
public User getUserByUsername(String username) {
return userMapper.selectByUsername(username);
}
@Override
public void createUser(User user) {
// 校验用户名是否已存在
User existingUser = userMapper.selectByUsername(user.getUsername());
if (existingUser != null) {
throw new BusinessException("用户名已存在");
}
// 设置默认值
user.setStatus(User.Status.ACTIVE.getCode());
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
// 密码加密
user.setPassword(encryptPassword(user.getPassword()));
// 保存用户
int result = userMapper.insert(user);
if (result <= 0) {
throw new BusinessException("创建用户失败");
}
log.info("创建用户成功: {}", user.getUsername());
}
@Override
public void updateUser(User user) {
// 检查用户是否存在
User existingUser = userMapper.selectById(user.getId());
if (existingUser == null) {
throw new BusinessException("用户不存在");
}
// 更新用户信息
user.setUpdateTime(LocalDateTime.now());
int result = userMapper.update(user);
if (result <= 0) {
throw new BusinessException("更新用户失败");
}
log.info("更新用户成功: {}", user.getId());
}
@Override
public void deleteUser(Long id) {
// 检查用户是否存在
User existingUser = userMapper.selectById(id);
if (existingUser == null) {
throw new BusinessException("用户不存在");
}
int result = userMapper.delete(id);
if (result <= 0) {
throw new BusinessException("删除用户失败");
}
log.info("删除用户成功: {}", id);
}
@Override
@Transactional(readOnly = true)
public List<User> getUsersByCondition(User user) {
return userMapper.selectByCondition(user);
}
@Override
@Transactional(readOnly = true)
public PageResult<User> getUsersByPage(Integer pageNum, Integer pageSize, User user) {
// 设置分页参数
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<User> userList = userMapper.selectByCondition(user);
PageInfo<User> pageInfo = new PageInfo<>(userList);
return PageResult.of(
pageInfo.getList(),
pageInfo.getTotal(),
pageInfo.getPageNum(),
pageInfo.getPageSize()
);
}
@Override
public User login(String username, String password) {
User user = userMapper.selectByUsername(username);
if (user == null) {
throw new BusinessException("用户名或密码错误");
}
// 验证密码
String encryptedPassword = encryptPassword(password);
if (!encryptedPassword.equals(user.getPassword())) {
throw new BusinessException("用户名或密码错误");
}
// 检查用户状态
if (!User.Status.ACTIVE.getCode().equals(user.getStatus())) {
throw new BusinessException("用户状态异常");
}
log.info("用户登录成功: {}", username);
return user;
}
/**
* 密码加密
*/
private String encryptPassword(String password) {
return DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
}
}
七、控制层实现
7.1 Controller 类
java
package com.example.controller;
import com.example.common.PageResult;
import com.example.common.Result;
import com.example.entity.User;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.List;
@Slf4j
@Validated
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据ID查询用户
*/
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable @Min(1) Long id) {
log.info("查询用户详情: {}", id);
User user = userService.getUserById(id);
return Result.success(user);
}
/**
* 查询所有用户
*/
@GetMapping
public Result<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return Result.success(users);
}
/**
* 创建用户
*/
@PostMapping
public Result<Void> createUser(@Valid @RequestBody User user) {
log.info("创建用户: {}", user.getUsername());
userService.createUser(user);
return Result.success();
}
/**
* 更新用户
*/
@PutMapping("/{id}")
public Result<Void> updateUser(@PathVariable Long id, @Valid @RequestBody User user) {
user.setId(id);
log.info("更新用户: {}", id);
userService.updateUser(user);
return Result.success();
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable Long id) {
log.info("删除用户: {}", id);
userService.deleteUser(id);
return Result.success();
}
/**
* 条件查询用户
*/
@GetMapping("/search")
public Result<List<User>> searchUsers(User user) {
List<User> users = userService.getUsersByCondition(user);
return Result.success(users);
}
/**
* 分页查询用户
*/
@GetMapping("/page")
public Result<PageResult<User>> getUsersByPage(
@RequestParam(defaultValue = "1") @Min(1) Integer pageNum,
@RequestParam(defaultValue = "10") @Min(1) Integer pageSize,
User user) {
PageResult<User> pageResult = userService.getUsersByPage(pageNum, pageSize, user);
return Result.success(pageResult);
}
/**
* 用户登录
*/
@PostMapping("/login")
public Result<User> login(@RequestParam String username, @RequestParam String password) {
log.info("用户登录: {}", username);
User user = userService.login(username, password);
return Result.success(user);
}
}
7.2 DTO 对象
java
package com.example.dto;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@Data
public class UserCreateDTO {
@NotBlank(message = "用户名不能为空")
@Length(min = 3, max = 20, message = "用户名长度3-20位")
private String username;
@NotBlank(message = "密码不能为空")
@Length(min = 6, max = 20, message = "密码长度6-20位")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@NotNull(message = "年龄不能为空")
private Integer age;
}
八、拦截器实现
8.1 日志拦截器
java
package com.example.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
private ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
startTime.set(System.currentTimeMillis());
String requestURI = request.getRequestURI();
String method = request.getMethod();
String queryString = request.getQueryString();
log.info("请求开始: {} {}?{}", method, requestURI, queryString);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
long cost = System.currentTimeMillis() - startTime.get();
int status = response.getStatus();
log.info("请求完成: {} {} - 状态: {} - 耗时: {}ms",
request.getMethod(), request.getRequestURI(), status, cost);
startTime.remove();
}
}
8.2 认证拦截器
java
package com.example.config;
import com.example.common.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 放行OPTIONS请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
// 获取Token
String token = getTokenFromRequest(request);
if (token == null) {
sendErrorResponse(response, 401, "未授权访问");
return false;
}
try {
// 验证Token(这里需要实现具体的验证逻辑)
if (!validateToken(token)) {
sendErrorResponse(response, 401, "Token无效");
return false;
}
return true;
} catch (Exception e) {
sendErrorResponse(response, 401, "Token验证失败");
return false;
}
}
private String getTokenFromRequest(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7);
}
return request.getParameter("token");
}
private boolean validateToken(String token) {
// 实现Token验证逻辑
// 这里可以集成JWT、Redis等
return true; // 示例
}
private void sendErrorResponse(HttpServletResponse response,
int code, String message) throws Exception {
response.setStatus(code);
response.setContentType("application/json;charset=UTF-8");
Result<Void> result = Result.error(code, message);
String json = objectMapper.writeValueAsString(result);
response.getWriter().write(json);
response.getWriter().flush();
}
}
九、测试用例
9.1 Service 层测试
java
package com.example.service;
import com.example.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@SpringBootTest
@Transactional
@Rollback
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testCreateUser() {
User user = new User();
user.setUsername("testuser");
user.setPassword("123456");
user.setEmail("test@example.com");
user.setPhone("13800138000");
user.setAge(25);
userService.createUser(user);
User savedUser = userService.getUserByUsername("testuser");
assert savedUser != null;
assert savedUser.getEmail().equals("test@example.com");
}
@Test
void testGetUsersByPage() {
User user = new User();
user.setUsername("test");
var pageResult = userService.getUsersByPage(1, 10, user);
assert pageResult != null;
assert pageResult.getList() != null;
}
}
9.2 Controller 层测试
java
package com.example.controller;
import com.example.entity.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@Slf4j
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void testGetAllUsers() throws Exception {
mockMvc.perform(get("/users")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").isArray());
}
@Test
void testCreateUser() throws Exception {
User user = new User();
user.setUsername("testuser2");
user.setPassword("123456");
user.setEmail("test2@example.com");
user.setPhone("13800138001");
user.setAge(26);
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
}
}
十、部署与启动
10.1 启动类增强
java
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Slf4j
@SpringBootApplication
public class Application {
public static void main(String[] args) throws UnknownHostException {
SpringApplication app = new SpringApplication(Application.class);
Environment env = app.run(args).getEnvironment();
String protocol = "http";
if (env.getProperty("server.ssl.key-store") != null) {
protocol = "https";
}
log.info("\n----------------------------------------------------------\n\t" +
"Application '{}' is running! Access URLs:\n\t" +
"Local: \t\t{}://localhost:{}\n\t" +
"External: \t{}://{}:{}\n\t" +
"Profile(s): \t{}\n----------------------------------------------------------",
env.getProperty("spring.application.name"),
protocol,
env.getProperty("server.port"),
protocol,
InetAddress.getLocalHost().getHostAddress(),
env.getProperty("server.port"),
env.getProperty("spring.profiles.active"));
}
}
10.2 生产环境启动脚本
bash
#!/bin/bash # start.sh # 设置JVM参数 JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200" # 设置Spring Profile SPRING_PROFILES_ACTIVE="prod" # 启动应用 nohup java $JAVA_OPTS \ -Dspring.profiles.active=$SPRING_PROFILES_ACTIVE \ -jar springboot-ssm-1.0.0.jar \ > /dev/null 2>&1 & echo "Application started with PID: $!"
十一、总结
11.1 整合要点
-
依赖管理:使用Spring Boot Starter简化依赖配置
-
自动配置:利用Spring Boot的自动配置特性
-
配置分离:使用YAML文件,支持多环境配置
-
组件扫描:合理使用注解进行组件扫描
-
事务管理:使用声明式事务管理
11.2 最佳实践
-
统一响应格式:规范API返回格式
-
全局异常处理:统一异常处理机制
-
参数校验:使用Validation进行参数校验
-
日志规范:统一日志格式和级别
-
分页处理:使用PageHelper进行分页
11.3 性能优化建议
-
连接池配置:合理配置Druid连接池参数
-
SQL优化:使用MyBatis的缓存和SQL优化
-
分页优化:大数据量分页使用游标分页
-
索引优化:为查询条件添加合适的索引
通过以上完整的整合方案,你可以快速构建一个基于Spring Boot + SSM的高效、可维护的企业级应用。

被折叠的 条评论
为什么被折叠?



