Java 后端开发

Spring Boot + MyBatis + MySQL — 制造业数据可视化系统的后端基石

01 Spring Boot 是什么

💡 自动挡汽车 = Spring Boot

传统 Spring(手动挡):你手动配置 XML、手动管理事务、手动部署 WAR 包到 Tomcat。
Spring Boot(自动挡):自动配置、内嵌 Tomcat、一键启动。你只需要关注业务代码,框架帮你搞定基础设施。

核心特性

创建第一个 Spring Boot 应用

src/main/java/com/example/MyApp.java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// @SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}
pom.xml — Maven 依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
</parent>

<dependencies>
    <!-- Web: Spring MVC + 内嵌 Tomcat + Jackson -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis: 数据库访问 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
</dependencies>
✅ 启动结果
$ mvn spring-boot:run

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v3.2.0)

Tomcat started on port 8080 (http)
Started MyApp in 2.341 seconds

02 REST Controller

Spring Boot 用 @RestController 定义 RESTful 接口,@GetMapping/@PostMapping 映射 HTTP 方法。

src/main/java/com/example/controller/ProductController.java
package com.example.controller;

import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;

    // 构造器注入 (推荐, 不可变 + 可测试)
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    // GET /api/products — 查询全部
    @GetMapping
    public List<Product> list() {
        return productService.findAll();
    }

    // GET /api/products/{id} — 查询单个
    @GetMapping("/{id}")
    public Product getById(@PathVariable Long id) {
        return productService.findById(id);
    }

    // POST /api/products — 新增
    @PostMapping
    public Product create(@RequestBody @Valid CreateProductDTO dto) {
        return productService.create(dto);
    }

    // PUT /api/products/{id} — 更新
    @PutMapping("/{id}")
    public Product update(@PathVariable Long id, @RequestBody UpdateProductDTO dto) {
        return productService.update(id, dto);
    }

    // DELETE /api/products/{id} — 删除
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        productService.delete(id);
    }
}
⚠️ @Controller vs @RestController

@Controller 返回视图名(HTML 页面),@RestController = @Controller + @ResponseBody,返回 JSON/XML 数据。前后端分离项目一律用 @RestController

03 RESTful API 设计

💡 RESTful = 用 URL 表达资源,用 HTTP 方法表达操作

好的 API 设计让前端不需要看文档就能猜到接口。资源用名词(/products),操作用 HTTP 方法(GET/POST/PUT/DELETE)。

RESTful 规范速查
操作HTTPURL说明
查询列表GET/api/products?page=1&size=20✓ 幂等
查询单个GET/api/products/123✓ 幂等
新增POST/api/products✗ 不幂等
全量更新PUT/api/products/123✓ 幂等
部分更新PATCH/api/products/123✗ 不幂等
删除DELETE/api/products/123✓ 幂等

统一响应格式

Result.java — 统一响应包装
@Data
@Builder
public class Result<T> {
    private int code;       // 200=成功, 400=参数错误, 500=系统错误
    private String message;
    private T data;
    private long timestamp;

    public static <T> Result<T> success(T data) {
        return Result.<T>builder()
            .code(200).message("success")
            .data(data).timestamp(System.currentTimeMillis())
            .build();
    }

    public static <T> Result<T> fail(int code, String message) {
        return Result.<T>builder()
            .code(code).message(message)
            .timestamp(System.currentTimeMillis())
            .build();
    }
}
API 响应示例
// GET /api/products/1
{
  "code": 200,
  "message": "success",
  "data": {
    "id": 1,
    "name": "800G Optical Transceiver",
    "category": "光模块",
    "price": 15000.00,
    "stock": 200
  },
  "timestamp": 1718000000000
}

04 连接 MySQL

Spring Boot + MyBatis 是国内最主流的数据库访问方案。配置 HikariCP 连接池 + MyBatis Mapper。

application.yml — 数据库配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/factory_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8
    username: root
    password: ${DB_PASSWORD:secret}  # 环境变量优先
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
      connection-timeout: 30000  # 30s 获取连接超时
      leak-detection-threshold: 30000  # 30s 泄漏检测

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true  # snake_case → camelCase
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印 SQL
ProductMapper.java — Mapper 接口
@Mapper
public interface ProductMapper {

    @Select("SELECT * FROM products WHERE id = #{id}")
    Product findById(@Param("id") Long id);

    @Select("SELECT * FROM products ORDER BY id LIMIT #{offset}, #{size}")
    List<Product> findPage(@Param("offset") int offset, @Param("size") int size);

    @Insert("INSERT INTO products(name, price, stock) VALUES(#{name}, #{price}, #{stock})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Product product);

    @Update("UPDATE products SET stock = #{stock} WHERE id = #{id}")
    int updateStock(@Param("id") Long id, @Param("stock") int stock);

    @Delete("DELETE FROM products WHERE id = #{id}")
    int deleteById(Long id);
}
📚 MyBatis \#{} vs ${}

#{"{}"} 是预编译参数(?),防 SQL 注入,用于
${"{}"} 是字符串拼接,有注入风险,只用于表名/列名/排序字段等不能参数化的地方。
永远优先用 #{}

05 Filter 与 Interceptor

💡 Filter = 餐厅门口保安,Interceptor = 大堂经理

Filter 是 Servlet 规范的,在 DispatcherServlet 前后执行,适合 CORS/编码/安全头。
Interceptor 是 Spring MVC 的,在 Controller 方法前后执行,适合登录校验/权限/日志。

执行顺序

请求处理链路
HTTP Request
    ↓
Filter.doFilter()          // CORS / 编码
    ↓
DispatcherServlet
    ↓
Interceptor.preHandle()    // 登录校验, return false 则中断
    ↓
Controller 方法            // 业务逻辑
    ↓
Interceptor.postHandle()   // 后置处理
    ↓
Interceptor.afterCompletion()  // 清理资源 (总是执行)
    ↓
Filter (退出)

登录拦截器实现

LoginInterceptor.java
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenService tokenService;

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
        String token = req.getHeader("Authorization");
        if (token == null || !tokenService.isValid(token)) {
            res.setStatus(401);
            return false;  // 中断请求
        }
        return true;  // 放行
    }
}

// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/api/**")
            .excludePathPatterns("/api/login", "/api/register");
    }
}

06 WebSocket 实时推送

制造业看板需要实时数据推送(产线状态、设备告警)。Spring Boot 内置 WebSocket 支持。

WebSocketConfig.java — 配置
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
            .setAllowedOriginPatterns("*")
            .withSockJS();  // 兼容不支持 WebSocket 的浏览器
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic", "/queue");
        registry.setApplicationDestinationPrefixes("/app");
    }
}
DashboardController.java — 推送产线数据
@Controller
public class DashboardController {

    @Autowired
    private SimpMessagingTemplate messaging;

    // 定时推送产线状态到所有订阅的客户端
    @Scheduled(fixedRate = 2000)  // 每2秒推送
    public void pushLineStatus() {
        LineStatus status = lineService.getCurrentStatus();
        messaging.convertAndSend("/topic/line-status", status);
    }

    // 推送设备告警 (定向推送给特定用户)
    public void pushAlert(String userId, AlertMessage alert) {
        messaging.convertAndSendToUser(userId, "/queue/alerts", alert);
    }
}
🔗 前端订阅

前端用 SockJS + STOMP.js 连接 /ws,订阅 /topic/line-status,收到推送后更新图表。

07 异常处理与日志

全局异常处理

GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    // 业务异常
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusiness(BusinessException e) {
        log.warn("业务异常: code={}, msg={}", e.getCode(), e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }

    // 参数校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
            .map(err -> err.getField() + ": " + err.getDefaultMessage())
            .collect(Collectors.joining("; "));
        return Result.fail(400, msg);
    }

    // 兜底: 未知异常
    @ExceptionHandler(Exception.class)
    public Result<Void> handleUnknown(Exception e) {
        log.error("系统异常", e);
        return Result.fail(500, "系统繁忙,请稍后重试");
    }
}

日志配置

application.yml — 日志配置
logging:
  level:
    root: INFO
    com.example: DEBUG  # 自己的包 DEBUG
    com.example.mapper: DEBUG  # MyBatis SQL 日志
  file:
    name: /var/log/myapp/app.log
  logback:
    rollingpolicy:
      max-file-size: 100MB
      max-history: 30
      total-size-cap: 3GB
⚠️ 日志最佳实践

① 永远用 SLF4J 门面 + Logback 实现,不要直接用 System.out.println
② 异常必须 log.error("描述", e) 带异常对象,不要只 log 消息;
③ 生产环境 NEVER 打印用户密码/身份证号等敏感信息;
④ 使用 @Slf4j 注解代替手动 LoggerFactory.getLogger

08 项目结构

标准 Spring Boot 项目采用分层架构:Controller → Service → Mapper → DB。

my-app/ ├── src/main/java/com/example/ │ ├── MyApp.java // 启动类 │ ├── config/ // 配置类 │ │ ├── WebConfig.java // MVC 配置 (拦截器/CORS) │ │ ├── RedisConfig.java // Redis 配置 │ │ └── ThreadPoolConfig.java // 线程池配置 │ ├── controller/ // 接口层: 接收 HTTP 请求 │ │ ├── ProductController.java │ │ └── OrderController.java │ ├── service/ // 业务层: 核心逻辑 │ │ ├── ProductService.java // 接口 │ │ └── impl/ │ │ └── ProductServiceImpl.java // 实现 │ ├── mapper/ // 数据层: 数据库访问 │ │ ├── ProductMapper.java │ │ └── OrderMapper.java │ ├── entity/ // 实体: 数据库映射 │ │ ├── Product.java │ │ └── Order.java │ ├── dto/ // 数据传输对象 │ │ ├── CreateProductDTO.java │ │ └── ProductVO.java // 返回视图对象 │ ├── common/ // 公共组件 │ │ ├── Result.java // 统一响应 │ │ ├── BusinessException.java │ │ └── GlobalExceptionHandler.java │ └── util/ // 工具类 │ └── DateUtils.java ├── src/main/resources/ │ ├── application.yml // 主配置 │ ├── application-dev.yml // 开发环境 │ ├── application-prod.yml // 生产环境 │ ├── mapper/ // MyBatis XML │ │ ├── ProductMapper.xml │ │ └── OrderMapper.xml │ └── db/migration/ // Flyway SQL 迁移 │ ├── V1__init.sql │ └── V2__add_stock_column.sql ├── src/test/java/ // 测试代码 └── pom.xml // Maven 配置
🔒 分层架构核心原则

Controller 只做参数接收和返回,不写业务逻辑。
Service 写业务逻辑,加 @Transactional
Mapper 只做数据库访问,不写业务判断。
Entity 对应数据库表,DTO 用于接口传输——不要混用。

09 面试考点 Top 8

Q1 Spring Boot 的自动配置原理是什么?

答案@EnableAutoConfiguration 通过 AutoConfigurationImportSelector 读取 META-INF/spring.factories(Boot 3.x 是 AutoConfiguration.imports),加载所有 AutoConfiguration 类。每个 AutoConfiguration 通过 @ConditionalOnClass@ConditionalOnProperty@ConditionalOnMissingBean 等条件注解判断是否生效。

Q2 @Autowired 和 @Resource 的区别?推荐用哪个?

答案:@Autowired 是 Spring 注解,默认按类型注入,多个时配合 @Qualifier。@Resource 是 JSR-250 标准,默认按名称注入。推荐 @Autowired + @Qualifier,或更好的方案:构造器注入(Lombok @RequiredArgsConstructor 自动生成)。

Q3 @Transactional 失效的场景有哪些?

答案:5 大场景——① 类内部 this 调用(不经过代理);② 非 public 方法;③ 异常被 catch 吞掉;④ rollbackFor 不匹配(默认只回滚 RuntimeException);⑤ 数据库引擎不支持事务(MyISAM)。解决方案:注入 self / AopContext.currentProxy() / 异常 re-throw / rollbackFor = Exception.class。

Q4 Spring Boot 中如何实现 CORS 跨域?

答案:推荐用 WebMvcConfigurer.addCorsMappings() 统一配置,或用 @CrossOrigin 注解在 Controller 上。生产环境建议在 Nginx/API Gateway 层处理 CORS。

Q5 HikariCP 为什么快?连接池大小怎么设?

答案:快因为——字节码优化、ConcurrentBag 无锁获取、FastList 无范围检查。大小公式:connections = ((core_count * 2) + effective_spindle_count)。实际 20-50 个通常最优,必须通过压测调优。

Q6 Spring Boot 如何实现优雅停机?

答案server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase=30s + K8s preStop hook + readinessProbe。收到 SIGTERM 后停止接收新请求,等待进行中请求完成,关闭 DB/Redis 连接,JVM 正常退出。

Q7 Filter 和 Interceptor 的区别?

答案:Filter 是 Servlet 规范,作用在 DispatcherServlet 前后,不能直接 @Autowired Spring Bean。Interceptor 是 Spring MVC 的,作用在 Controller 前后,天然支持 Spring Bean。Filter 适合 CORS/编码,Interceptor 适合登录/权限/日志。

Q8 如何排查 Java 内存泄漏 / OOM?

答案:jstat -gcutil 观察 GC 趋势 → jmap -histo:live 查看对象统计 → jmap -dump 或 Arthas heapdump 导出快照 → Eclipse MAT 分析 Leak Suspects。常见原因:static Map 无限增长、ThreadLocal 未 remove、连接未关闭。启动加 -XX:+HeapDumpOnOutOfMemoryError 自动 dump。

📚 学习路线建议

入门:Spring Initializr 创建项目 → 写第一个 Controller → 连接 MySQL
进阶:理解 IoC/AOP 原理 → 事务/缓存/异步 → Redis/MQ 集成
高级:JVM 调优 → 线程池调优 → 微服务 (Spring Cloud)
生产:Actuator 监控 → 日志告警 → K8s 部署