
前言这一篇进入后端开发的核心Spring Boot 整合 MyBatis以及三层架构。Spring Boot 是目前 Java 后端开发的主流框架它把 Spring 的复杂配置简化到了极致。三层架构则是后端项目的标准组织方式Controller 接请求、Service 写业务、Mapper 操作数据库一、HTTP 协议状态码1.1 什么是 HTTP 状态码HTTP 状态码是服务器对请求的响应结果标识用三位数字表示。1.2 常见状态码状态码含义说明200OK请求成功201Created创建成功如新增204No Content成功但无返回内容如删除301Moved Permanently永久重定向302Found临时重定向304Not Modified资源未修改走缓存400Bad Request请求参数错误401Unauthorized未认证未登录403Forbidden已认证但无权限404Not Found资源不存在405Method Not Allowed请求方法不允许500Internal Server Error服务器内部错误502Bad Gateway网关错误503Service Unavailable服务不可用1.3 状态码分类范围含义1xx信息性状态码2xx成功3xx重定向4xx客户端错误5xx服务器错误1.4 重要提醒200 只表示请求成功不表示业务成功。业务成功与否要看响应体里的业务状态码。json{ code: 200, message: 成功, data: { ... } }二、SpringBoot 工程搭建及 yml 配置2.1 创建 Spring Boot 项目用 IDEA 的 Spring Initializr 创建或者用 start.spring.ioProjectMavenLanguageJavaSpring Boot3.xDependenciesSpring Web、MyBatis Framework、MySQL Driver、Lombok2.2 项目结构textsrc/main/java/com/example/demo/ ├── DemoApplication.java # 启动类 ├── controller/ # 控制层 ├── service/ # 业务层 │ └── impl/ # 业务实现 ├── mapper/ # 持久层接口 ├── entity/ # 实体类 ├── dto/ # 数据传输对象 └── common/ # 公共类Result、异常等 src/main/resources/ ├── application.yml # 配置文件 └── mapper/ # MyBatis XML2.3 启动类javaSpringBootApplication MapperScan(com.example.demo.mapper) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }注意启动类必须放在最外层包这样 Spring Boot 才能扫描到所有子包。2.4 application.yml 配置yamlserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl三、Controller 创建及请求路径3.1 创建 ControllerjavaRestController RequestMapping(/users) public class UserController { GetMapping(/{id}) public ResultUser getById(PathVariable Integer id) { // ... } PostMapping public ResultVoid add(RequestBody User user) { // ... } }3.2 常用注解注解说明RestController等于ControllerResponseBody返回 JSONRequestMapping类级路径前缀GetMappingGET 请求查询PostMappingPOST 请求新增PutMappingPUT 请求修改DeleteMappingDELETE 请求删除3.3 请求路径拼接最终路径 类级路径 方法级路径javaRestController RequestMapping(/users) // 类级 public class UserController { GetMapping(/{id}) // 方法级 public ResultUser getById(PathVariable Integer id) { } PostMapping // 方法级为空 public ResultVoid add(RequestBody User user) { } }方法最终路径请求方法getById/users/{id}GETadd/usersPOST3.4 四件套对应 CRUD注解请求方法对应操作示例路径GetMappingGET查询/users、/users/1PostMappingPOST新增/usersPutMappingPUT修改/users/1DeleteMappingDELETE删除/users/1四、常见参数绑定方式4.1 五种常见方式方式注解适用场景路径参数PathVariableRESTful 风格如/users/1查询参数RequestParamURL 参数如?name张三请求体RequestBodyJSON 请求体POST/PUT表单参数无注解表单提交请求头RequestHeader获取请求头4.2 路径参数javaGetMapping(/{id}) public ResultUser getById(PathVariable Integer id) { }请求GET /users/14.3 查询参数javaGetMapping public ResultListUser list( RequestParam(required false) String name, RequestParam(defaultValue 1) Integer page) { }请求GET /users?name张三page14.4 请求体javaPostMapping public ResultVoid add(RequestBody User user) { }请求POST /usersBody 是 JSON4.5 表单参数javaPostMapping(/login) public ResultString login(String username, String password) { }请求POST /loginContent-Type 是application/x-www-form-urlencoded五、响应类型封装5.1 为什么要封装统一返回格式前端好处理json{ code: 200, message: 成功, data: { ... } }5.2 封装 Result 类javaData public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(成功); result.setData(data); return result; } public static T ResultT success() { return success(null); } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } public static T ResultT error(String message) { return error(500, message); } }5.3 使用javaGetMapping(/{id}) public ResultUser getById(PathVariable Integer id) { User user userService.getById(id); return Result.success(user); } PostMapping public ResultVoid add(RequestBody User user) { userService.add(user); return Result.success(); }5.4 业务状态码code含义200成功400参数错误401未登录403无权限404资源不存在500服务器错误六、Apifox 安装及测试6.1 什么是 ApifoxApifox 是 API 文档和测试工具集 API 设计、调试、Mock、测试于一体。6.2 安装去 apifox.com 下载安装。6.3 测试接口步骤新建项目新建接口填写请求方法、URL、参数点击发送查看响应结果6.4 测试 POST 接口配置项值MethodPOSTURLhttp://localhost:8080/usersBody 类型rawBody 格式JSONBody 内容{name: 张三, age: 18}6.5 常见错误错误原因404路径不对检查类级 方法级405请求方法不对GET/POST 写错400参数格式不对415Content-Type 不对JSON 要选raw JSON500服务器内部错误看日志七、SpringBoot 整合 MyBatis 步骤7.1 四步走步骤内容1引入依赖2配置 yml3启动类加MapperScan4写 Mapper 接口和 XML7.2 引入依赖xmldependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency7.3 配置 ymlyamlspring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?serverTimezoneAsia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true7.4 启动类加 MapperScanjavaSpringBootApplication MapperScan(com.example.demo.mapper) public class DemoApplication { }7.5 写 MapperjavaMapper public interface UserMapper { User selectById(Integer id); ListUser selectAll(); int insert(User user); }xmlmapper namespacecom.example.demo.mapper.UserMapper select idselectById resultTypeUser SELECT * FROM user WHERE id #{id} /select /mapper八、三层架构基本结构各层意义8.1 什么是三层架构三层架构就是把后端代码分成三层Controller、Service、Mapper。text请求 → Controller → Service → Mapper → 数据库 响应 ← Controller ← Service ← Mapper ← 数据库8.2 各层职责层职责说明Controller接收请求、返回响应不写业务逻辑Service业务逻辑核心业务在这里Mapper数据库操作只负责 CRUD8.3 代码示例java// Controller接收请求 RestController RequestMapping(/users) public class UserController { Autowired private UserService userService; GetMapping(/{id}) public ResultUser getById(PathVariable Integer id) { return Result.success(userService.getById(id)); } }java// Service 接口 public interface UserService { User getById(Integer id); } // Service 实现业务逻辑 Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override public User getById(Integer id) { return userMapper.selectById(id); } }java// Mapper数据库操作 Mapper public interface UserMapper { User selectById(Integer id); }8.4 为什么要分层原因说明职责单一每层只做自己的事解耦改一层不影响其他层可复用Service 可以被多个 Controller 调用易维护出问题定位到具体层九、三层架构Service 层为什么需要接口9.1 问题Service 层为什么要定义接口UserService再写实现类UserServiceImpl直接写实现类不行吗9.2 答案为了解耦Controller 依赖接口不依赖实现类。java// Controller 依赖接口 Autowired private UserService userService; // 接口 // 不是这样 Autowired private UserServiceImpl userService; // 实现类9.3 好处好处说明解耦Controller 不关心具体实现可替换换实现类不用改 Controller易测试可以 Mock 接口符合规范面向接口编程9.4 举例java// 接口 public interface UserService { User getById(Integer id); } // 实现类 A Service public class UserServiceImplA implements UserService { public User getById(Integer id) { // 从数据库查 } } // 实现类 B Service public class UserServiceImplB implements UserService { public User getById(Integer id) { // 从缓存查 } }Controller 只依赖UserService换实现类不用改 Controller。十、什么是 IoC10.1 什么是 IoCIoCInversion of Control控制反转是一种设计思想对象的创建权和管理权交给 Spring 容器而不是自己new。10.2 传统方式 vs IoC传统方式javapublic class UserController { private UserService userService new UserServiceImpl(); // 自己 new }IoC 方式javaRestController public class UserController { Autowired private UserService userService; // Spring 注入 }10.3 IoC 的好处好处说明解耦不用自己 new不用关心创建细节统一管理所有 Bean 由 Spring 容器管理方便替换换实现类只需改配置十一、什么是 DI11.1 什么是 DIDIDependency Injection依赖注入是 IoC 的具体实现方式Spring 容器把依赖的对象注入到需要的地方。11.2 三种注入方式方式写法推荐度字段注入Autowired加在字段上⭐⭐构造器注入构造方法参数⭐⭐⭐⭐⭐Setter 注入Autowired加在 setter 上⭐11.3 代码示例java// 字段注入 RestController public class UserController { Autowired private UserService userService; } // 构造器注入推荐 RestController public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } }11.4 IoC vs DI对比项IoCDI是什么设计思想具体实现关注点控制权转移依赖如何注入关系DI 是 IoC 的实现方式十二、后端接口开发流程12.1 标准流程text1. 分析需求 → 确定接口 2. 设计数据库表 3. 写 Entity 4. 写 Mapper接口 XML 5. 写 Service接口 实现 6. 写 Controller 7. Apifox 测试12.2 以根据 ID 查用户为例1. 分析需求GET /users/{id}返回用户信息2. 数据库表sqlCREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), age INT );3. EntityjavaData public class User { private Integer id; private String name; private Integer age; }4. MapperjavaMapper public interface UserMapper { User selectById(Integer id); }xmlselect idselectById resultTypeUser SELECT * FROM user WHERE id #{id} /select5. Servicejavapublic interface UserService { User getById(Integer id); } Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override public User getById(Integer id) { return userMapper.selectById(id); } }6. ControllerjavaRestController RequestMapping(/users) public class UserController { Autowired private UserService userService; GetMapping(/{id}) public ResultUser getById(PathVariable Integer id) { return Result.success(userService.getById(id)); } }7. 测试Apifox 请求GET /users/1十三、多条件搜索 分页的接口实现方案13.1 需求GET /users?name张三age18page1size1013.2 查询参数封装javaData public class UserQuery { private String name; private Integer age; private Integer page 1; private Integer size 10; }13.3 ControllerjavaGetMapping public ResultPageResultUser list(UserQuery query) { return Result.success(userService.page(query)); }13.4 ServicejavaOverride public PageResultUser page(UserQuery query) { PageHelper.startPage(query.getPage(), query.getSize()); ListUser list userMapper.selectByCondition(query); PageInfoUser pageInfo new PageInfo(list); PageResultUser result new PageResult(); result.setList(list); result.setTotal(pageInfo.getTotal()); return result; }13.5 Mapper XMLxmlselect idselectByCondition resultTypeUser SELECT * FROM user where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testage ! null AND age #{age} /if /where /select13.6 分页结果封装javaData public class PageResultT { private ListT list; private Long total; }十四、使用 Transactional 处理事务14.1 什么是事务事务是一组操作要么全部成功要么全部失败。14.2 Transactional 用法javaService public class OrderServiceImpl implements OrderService { Transactional Override public void createOrder(Order order) { orderMapper.insert(order); // 插入订单 orderItemMapper.insertBatch(order); // 插入订单明细 stockMapper.reduce(order); // 扣减库存 // 任何一步失败全部回滚 } }14.3 关键属性属性说明默认值rollbackFor哪些异常回滚RuntimeExceptionpropagation传播行为REQUIREDisolation隔离级别DEFAULTreadOnly是否只读false14.4 常见坑java// ❌ 坑 1异常被 catch 了不会回滚 Transactional public void test() { try { orderMapper.insert(order); int i 1 / 0; } catch (Exception e) { e.printStackTrace(); // 异常被吃掉事务不回滚 } } // ✅ 解决抛出异常或手动回滚 Transactional public void test() { try { orderMapper.insert(order); int i 1 / 0; } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }java// ❌ 坑 2同类内部调用事务失效 Service public class OrderService { public void methodA() { methodB(); // 内部调用Transactional 失效 } Transactional public void methodB() { } }十五、全局异常处理方案15.1 为什么需要全局异常处理如果每个接口都 try-catch代码冗余且难维护。全局异常处理可以统一捕获异常统一返回格式。15.2 实现方式javaRestControllerAdvice public class GlobalExceptionHandler { // 处理业务异常 ExceptionHandler(BusinessException.class) public ResultVoid handleBusinessException(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } // 处理参数校验异常 ExceptionHandler(MethodArgumentNotValidException.class) public ResultVoid handleValidException(MethodArgumentNotValidException e) { String message e.getBindingResult().getFieldError().getDefaultMessage(); return Result.error(400, message); } // 处理其他异常 ExceptionHandler(Exception.class) public ResultVoid handleException(Exception e) { log.error(系统异常, e); return Result.error(500, 系统繁忙请稍后再试); } }15.3 自定义业务异常javaData 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); } }15.4 使用javaService public class UserServiceImpl implements UserService { Override public User getById(Integer id) { User user userMapper.selectById(id); if (user null) { throw new BusinessException(404, 用户不存在); } return user; } }十六、文件上传16.1 ControllerjavaPostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(文件不能为空); } String url fileService.upload(file); return Result.success(url); }16.2 ServicejavaService public class FileServiceImpl implements FileService { Value(${file.upload-path}) private String uploadPath; Override public String upload(MultipartFile file) { String originalName file.getOriginalFilename(); String ext originalName.substring(originalName.lastIndexOf(.)); String newName UUID.randomUUID() ext; File dest new File(uploadPath newName); try { file.transferTo(dest); } catch (IOException e) { throw new BusinessException(文件上传失败); } return /upload/ newName; } }16.3 yml 配置yamlspring: servlet: multipart: max-file-size: 10MB max-request-size: 100MB file: upload-path: D:/upload/十七、一个请求的完整流程链路17.1 完整链路text前端请求 ↓ Nginx反向代理 ↓ Spring BootTomcat ↓ DispatcherServlet ↓ 拦截器Interceptor ↓ Controller ↓ Service ↓ Mapper ↓ 数据库 ↓ 返回结果17.2 各环节职责环节职责Nginx反向代理、负载均衡、静态资源Tomcat接收 HTTP 请求DispatcherServlet分发请求到 ControllerInterceptor登录校验、权限校验Controller接收参数、调用 ServiceService业务逻辑、事务Mapper数据库操作数据库存储数据十八、Axios 的常用 API18.1 什么是 AxiosAxios 是一个基于 Promise 的 HTTP 客户端用于前端发请求。18.2 常用方法方法说明axios.get(url, config)GET 请求axios.post(url, data, config)POST 请求axios.put(url, data, config)PUT 请求axios.delete(url, config)DELETE 请求18.3 代码示例javascript// GET参数用 params axios.get(/users, { params: { page: 1, size: 10 } }) // POST数据用 data axios.post(/users, { name: 张三, age: 18 }) // PUT axios.put(/users/1, { name: 李四 }) // DELETE一般用路径传参 axios.delete(/users/1)18.4 参数位置请求方法参数位置GETparamsPOST/PUTdataDELETE路径十九、Axios 的封装19.1 为什么要封装统一配置 baseURL、超时、请求头、拦截器避免每个请求都写一遍。19.2 封装 request.jsjavascriptimport axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器 request.interceptors.request.use( config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }, error Promise.reject(error) ) // 响应拦截器 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } return res.data }, error { ElMessage.error(error.message || 请求失败) return Promise.reject(error) } ) export default request19.3 封装 api 模块javascript// api/user.js import request from /utils/request export function getUserList(params) { return request.get(/users, { params }) } export function addUser(data) { return request.post(/users, data) }19.4 在组件中使用vuescript setup import { getUserList } from /api/user async function fetchList() { const list await getUserList({ page: 1, size: 10 }) console.log(list) } /script二十、同源策略跨域请求20.1 什么是同源策略同源 协议 域名 端口三者完全相同。地址是否同源原因http://localhost:8080vshttp://localhost:8080✅完全相同http://localhost:8080vshttp://localhost:5173❌端口不同http://localhost:8080vshttps://localhost:8080❌协议不同http://localhost:8080vshttp://127.0.0.1:8080❌域名不同20.2 什么是跨域浏览器出于安全考虑限制跨域请求。前端localhost:5173请求后端localhost:8080端口不同就是跨域。20.3 解决跨域后端配置javaConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }20.4 解决跨域前端代理javascript// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })二十一、常见的错误调试方案21.1 常见错误与排查错误可能原因排查方法404路径不对检查类级 方法级路径405请求方法不对检查 GET/POST/PUT/DELETE400参数格式不对检查参数类型、必填项415Content-Type 不对JSON 请求选raw JSON500服务器内部错误看控制台日志、断点调试401未登录检查 token 是否携带403无权限检查权限配置跨域错误同源策略后端配 CORS 或前端配代理数据库连接失败配置错误检查 url、username、passwordSQL 语法错误SQL 写错看 MyBatis 日志输出的 SQL21.2 调试步骤看控制台日志异常堆栈会指出具体位置看 MyBatis 日志确认 SQL 和参数是否正确断点调试在关键位置打断点看变量值Apifox 单独测试排除前端问题看浏览器 Network确认请求和响应