Java 后端开发
Spring Boot + MyBatis + MySQL — 制造业数据可视化系统的后端基石
01 Spring Boot 是什么
传统 Spring(手动挡):你手动配置 XML、手动管理事务、手动部署 WAR 包到 Tomcat。
Spring Boot(自动挡):自动配置、内嵌 Tomcat、一键启动。你只需要关注业务代码,框架帮你搞定基础设施。
核心特性
- 约定优于配置:合理默认值,零配置启动
- 起步依赖(Starter):spring-boot-starter-web 一个依赖拉入 MVC + Tomcat + Jackson
- 内嵌服务器:不需要装 Tomcat,java -jar 直接启动
- Actuator:生产级监控端点(健康检查/指标/审计)
- Profile:多环境配置(dev/test/prod)
创建第一个 Spring Boot 应用
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);
}
}
<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 方法。
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 返回视图名(HTML 页面),@RestController = @Controller + @ResponseBody,返回 JSON/XML 数据。前后端分离项目一律用 @RestController。
03 RESTful API 设计
好的 API 设计让前端不需要看文档就能猜到接口。资源用名词(/products),操作用 HTTP 方法(GET/POST/PUT/DELETE)。
| 操作 | HTTP | URL | 说明 |
|---|---|---|---|
| 查询列表 | 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 | ✓ 幂等 |
统一响应格式
@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();
}
}
// 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。
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
@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);
}
#{"{}"} 是预编译参数(?),防 SQL 注入,用于值。
${"{}"} 是字符串拼接,有注入风险,只用于表名/列名/排序字段等不能参数化的地方。
永远优先用 #{}。
05 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 (退出)
登录拦截器实现
@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 支持。
@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");
}
}
@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 异常处理与日志
全局异常处理
@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, "系统繁忙,请稍后重试");
}
}
日志配置
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。
Controller 只做参数接收和返回,不写业务逻辑。
Service 写业务逻辑,加 @Transactional。
Mapper 只做数据库访问,不写业务判断。
Entity 对应数据库表,DTO 用于接口传输——不要混用。