【SSM框架 |day29-springboot整合SSM框架】

一、项目结构与依赖配置

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 整合要点

  1. 依赖管理:使用Spring Boot Starter简化依赖配置

  2. 自动配置:利用Spring Boot的自动配置特性

  3. 配置分离:使用YAML文件,支持多环境配置

  4. 组件扫描:合理使用注解进行组件扫描

  5. 事务管理:使用声明式事务管理

11.2 最佳实践

  1. 统一响应格式:规范API返回格式

  2. 全局异常处理:统一异常处理机制

  3. 参数校验:使用Validation进行参数校验

  4. 日志规范:统一日志格式和级别

  5. 分页处理:使用PageHelper进行分页

11.3 性能优化建议

  1. 连接池配置:合理配置Druid连接池参数

  2. SQL优化:使用MyBatis的缓存和SQL优化

  3. 分页优化:大数据量分页使用游标分页

  4. 索引优化:为查询条件添加合适的索引

通过以上完整的整合方案,你可以快速构建一个基于Spring Boot + SSM的高效、可维护的企业级应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值