写在前面
作为一名技术人,我深知学习新框架时的迷茫与焦虑。尤其是当你已经熟悉了一套技术栈,想要转向另一个生态时,那种"无从下手"的感觉尤为强烈。
最近,我开始系统学习Spring Boot生态,并尝试将Spring AI集成到实际项目中。经过一段时间的摸索,我整理出了一份7天学习路线图,目标是让有编程基础的开发者能快速上手Spring Boot + Spring AI,独立完成一个完整的AI对话应用。
这份计划不是零基础教程,而是为有一定后端开发经验(不限于Java)的朋友量身定制的"快速转型指南"。如果你对Python、Node.js或其他后端语言已有了解,这份计划将帮助你在最短时间内建立Spring Boot的核心认知,并跟上AI应用开发的浪潮。
下面,我将这7天的学习中的第三天,消息表设计 + 级联删除 + 事务管理。
一、创建消息表
1.1 执行 SQL
sql
USE chat_db;
CREATE TABLE IF NOT EXISTS message (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '消息ID',
session_id BIGINT NOT NULL COMMENT '会话ID(逻辑外键,关联session表)',
role VARCHAR(20) NOT NULL COMMENT '角色:user(用户)/assistant(AI)',
content TEXT NOT NULL COMMENT '消息内容',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id),
INDEX idx_session_id (session_id),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消息表';
二、创建 Message 实体类
创建 entity/Message.java:
kotlin
package com.example.chatapi.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("message")
public class Message {
@TableId(type = IdType.AUTO)
private Long id;
private Long sessionId;
private String role;
private String content;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
}
三、创建 MessageMapper
创建 mapper/MessageMapper.java:
less
package com.example.chatapi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.chatapi.entity.Message;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface MessageMapper extends BaseMapper<Message> {
@Select("SELECT * FROM message WHERE session_id = #{sessionId} ORDER BY created_at ASC")
List<Message> selectBySessionId(@Param("sessionId") Long sessionId);
@Delete("DELETE FROM message WHERE session_id = #{sessionId}")
int deleteBySessionId(@Param("sessionId") Long sessionId);
}
四、创建统一响应类
创建 dto/ApiResponse.java:
typescript
package com.example.chatapi.dto;
import lombok.Data;
@Data
public class ApiResponse<T> {
private Integer code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("success");
response.setData(data);
return response;
}
public static <T> ApiResponse<T> error(Integer code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMessage(message);
return response;
}
}
创建 dto/MessageSaveRequest.java:
kotlin
package com.example.chatapi.dto;
import lombok.Data;
@Data
public class MessageSaveRequest {
private Long sessionId;
private String role;
private String content;
}
五、创建 MessageService
创建 service/MessageService.java:
java
package com.example.chatapi.service;
import com.example.chatapi.entity.Message;
import com.example.chatapi.mapper.MessageMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageService {
private final MessageMapper messageMapper;
public List<Message> getMessagesBySession(Long sessionId) {
log.info("查询会话 {} 的消息列表", sessionId);
return messageMapper.selectBySessionId(sessionId);
}
public boolean saveMessage(Message message) {
log.info("保存消息: sessionId={}, role={}", message.getSessionId(), message.getRole());
int rows = messageMapper.insert(message);
return rows > 0;
}
}
六、修改 SessionService(添加级联删除)
更新 service/SessionService.java:
java
package com.example.chatapi.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.chatapi.entity.Session;
import com.example.chatapi.mapper.MessageMapper;
import com.example.chatapi.mapper.SessionMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class SessionService {
private final SessionMapper sessionMapper;
private final MessageMapper messageMapper;
public List<Session> listSessions() {
LambdaQueryWrapper<Session> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(Session::getCreatedAt);
return sessionMapper.selectList(wrapper);
}
public Session createSession(String name) {
Session session = new Session();
session.setName(name);
session.setCreatedAt(LocalDateTime.now());
sessionMapper.insert(session);
log.info("创建会话成功: id={}, name={}", session.getId(), name);
return session;
}
public boolean updateSession(Long id, String name) {
Session session = new Session();
session.setId(id);
session.setName(name);
int rows = sessionMapper.updateById(session);
return rows > 0;
}
@Transactional(rollbackFor = Exception.class)
public boolean deleteSession(Long id) {
log.info("开始删除会话 {} 及其所有消息", id);
int deletedMessages = messageMapper.deleteBySessionId(id);
log.info("删除了 {} 条消息", deletedMessages);
int deletedSession = sessionMapper.deleteById(id);
return deletedSession > 0;
}
public boolean exists(Long id) {
return sessionMapper.selectCount(
new LambdaQueryWrapper<Session>().eq(Session::getId, id)
) > 0;
}
}
- rollbackFor = Exception.class:当发生任何异常时都回滚事务
- 如果不指定,默认只在 RuntimeException 和 Error 时回滚
异常情况(删除消息后出错)
markdown
开始事务
↓
删除消息(成功)✓
↓
删除会话(失败)✗ 抛出异常
↓
回滚事务 → 删除的消息恢复,数据回到原状
七、创建 MessageController
创建 controller/MessageController.java:
kotlin
package com.example.chatapi.controller;
import com.example.chatapi.dto.ApiResponse;
import com.example.chatapi.dto.MessageSaveRequest;
import com.example.chatapi.entity.Message;
import com.example.chatapi.service.MessageService;
import com.example.chatapi.service.SessionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/messages")
@RequiredArgsConstructor
public class MessageController {
private final MessageService messageService;
private final SessionService sessionService;
@GetMapping("/{sessionId}")
public ApiResponse<List<Message>> getMessages(@PathVariable Long sessionId) {
log.info("接收请求:获取会话 {} 的消息列表", sessionId);
if (!sessionService.exists(sessionId)) {
return ApiResponse.error(404, "会话不存在");
}
List<Message> messages = messageService.getMessagesBySession(sessionId);
return ApiResponse.success(messages);
}
@PostMapping
public ApiResponse<Message> saveMessage(@RequestBody MessageSaveRequest request) {
log.info("接收请求:保存消息 sessionId={}, role={}", request.getSessionId(), request.getRole());
if (!sessionService.exists(request.getSessionId())) {
return ApiResponse.error(404, "会话不存在");
}
if (!"user".equals(request.getRole()) && !"assistant".equals(request.getRole())) {
return ApiResponse.error(400, "角色只能是 user 或 assistant");
}
Message message = new Message();
message.setSessionId(request.getSessionId());
message.setRole(request.getRole());
message.setContent(request.getContent());
boolean success = messageService.saveMessage(message);
if (success) {
return ApiResponse.success(message);
} else {
return ApiResponse.error(500, "保存失败");
}
}
}
八、启动项目并测试
8.1 启动项目
运行 ChatApiApplication.java,控制台看到以下日志即成功:
8.2 Postman 测试
bash
POST http://localhost:8080/api/messages
Content-Type: application/json
{"sessionId": 1, "role": "user", "content": "什么是Spring Boot?"}
学习建议: 今天的重点是创建一个message表,和级联删除。不要只复制代码,动手敲一遍,再用 Postman 把所有接口测一遍。
欢迎关注我的公众号(onething365),最新的技术与你分享