开发者

Spring MVC的三层架构使用及解析

目录
  • 一、Spring MVC 三层架构概述
    • 1.1 三层架构的核心定义
    • 1.2 三层架构的调用流程
  • 二、各层详细拆解
    • 2.1 表现层(Controller 层):用户交互的 "入口"
      • 2.1.1 核心组件与注解详解
      • 2.1.2 示例:一个完整的 Controller 实现
      • 2.1.3 最佳实践与注意事项
    • 2.2 业务逻辑层(Service 层):系统的 "大脑"
      • 2.2.1 核心组件与注解
      • 2.2.2 示例:Service 接口与实现类
      • 2.2.3 开发注意事项
    • 2.3 持久层(Mapper/DAO 层):数据的 "搬运工"
      • 2.3.1 核心组件与配置
      • 2.3.2 示例详解
      • 2.3.3 高级特性与最佳实践
      • 2.3.4 常见问题解决方案
  • 三、基于三层架构搭建 Spring MVC 项目
    • 3.1 项目结构(Maven)
      • 各层职责说明
    • 3.2 核心依赖(pom.XML)
      • 3.3 核心配置(application.properties)
        • 3.4 配置类(SpringMvcConfig.Java)
          • 3.5 功能测试:验证三层架构流程
            • 3.5.1 数据库表准备(mysql)
            • 3.5.2 接口测试(Postman)
        • 四、三层架构常见问题与解决方案
          • 4.1 表现层常见问题
            • 问题 1:Controller 无法接收请求(404 错误)
            • 问题 2:参数绑定失败(400 错误)
          • 4.2 业务逻辑层常见问题
            • 问题 1:事务不生效(数据提交后未回滚)
            • 问题 2:Service 层循环依赖(BeanCreationException)
          • 4.3 持久层常见问题
            • 问题 1:Mapper 接口与 XML 不匹配(BindingException)
            • 问题 2:SQL 执行报错(SQLSyntaxErrorException)
        • 五、三层架构优化建议
          • 5.1 代码复用与解耦
            • 统一异常处理
            • 通用Service/DAO层设计
          • 5.2 性能优化
            • 缓存设计
            • 批量操作优化
          • 5.3 扩展性优化
            • 接口版本控制
            • 多数据源支持
            • 依赖注入优化
        • 总结

          一、Spring MVC 三层架构概述

          在传统的 Java Web 开发中(如 Servlet+JSP),代码往往混杂在一起:数据处理、页面交互、业务逻辑全部写在 Servlet 中,导致项目维护困难、扩展性差。

          这种开发模式存在以下典型问题:

          • 单个Servlet文件可能包含数百行代码,混合处理业务逻辑和视图渲染
          • 数据库操作直接嵌入业务逻辑中,难以更换数据源
          • 代码复用率低,相似功能需要重复实现
          • 单元测试困难,各功能模块耦合度过高

          Spring MVC 的三层架构正是为解决这些问题而生,通过职责拆分,将系统分为三个核心层级,每层专注于特定功能,既降低了耦合度,又提升了代码的可复用性和可维护性。

          这种分层架构模式借鉴了企业级应用开发的成熟经验,并针对Web应用场景进行了优化。

          1.1 三层架构的核心定义

          Spring MVC 的三层架构并非独立存在,而是相互协作、自上而下的调用关系,具体包括:

          表现层(Presentation Layer)
          • 直接与用户交互,负责接收请求、返回响应

          主要功能包括:

          • 接收HTTP请求参数并进行基本校验
          • 调用Service层处理业务逻辑
          • 返回响应(页面渲染或jsON数据)
          • 核心组件:Spring MVC的Controller

          典型实现示例:

          @RestController
          @RequestMapping("/users")
          public class UserController {
              @Autowired
              private UserService userService;
              
              @GetMapping("/{id}")
              public User getUser(@PathVariable Long id) {
                  return userService.getUserById(id);
              }
          }
          
          业务逻辑层(Business Logic Layer,简称Service层)
          • 处理核心业务逻辑的中枢

          主要职责包括:

          • 复杂业务规则的实现
          • 数据有效性校验
          • 事务管理(通过@Transactional注解)
          • 异常处理
          • 组合多个数据操作完成业务功能

          典型实现示例:

          @Service
          public class UserServiceImpl implements UserService {
              @Autowired
              private UserMapper userMapper;
              
              @Transactional
              @Override
              public User createUser(UserDto userDto) {
                  // 业务校验
                  if(userMapper.existsByUsername(userDto.getUsername())) {
                      throw new BusinessException("用户名已存在");
                  }
                  
                  // 数据转换
                  User user = new User();
                  BeanUtils.copyProperties(userDto, user);
                  
                  // 持久化操作
                  userMapper.insert(user);
                  return user;
              }
          }
          
          持久层(Persistence Layer)
          • 专注于数据持久化操作

          主要特点:

          • 只关心"如何访问数据",不关心业务逻辑
          • 提供标准的CRUD操作方法
          • 支持多种持久化技术(JDBC、MyBATis、JPA等)

          核心组件:

          • DAO(Data Access Object)或Repository
          • MyBatis的Mapper接口

          典型实现示例:

          @Mapper
          public interface UserMapper {
              @Select("SELECT * FROM users WHERE id = #{id}")
              User selectById(Long id);
              
              @Insert("INSERT INTO users(username, password) VALUES(#{username}, #{password})")
              void insert(User user);
          }
          

          1.2 三层架构的调用流程

          请求接收阶段

          • 用户通过浏览器访问/users/123
          • HTTP请求被Spring MVC的前端控制器DispatcherServlet拦截
          • DispatcherServlet查找所有已注册的HandlerMapping

          请求路由阶段

          • HandlerMapping根据URL路径/users/123匹配到UserController的pythongetUser方法
          • 将路径变量123解析为方法参数id

          业务处理阶段

          • Controller调用UserService的getUserById方法

          Service层可能执行以下操作:

          • 参数校验(如ID有效性)
          • 业务规则判断(如权限检查)
          • 调用持久层获取数据

          数据访问阶段

          • Service调用UserMapper的selectById方法
          • MyBatis执行SQL:SELECT * FROM users WHERE id = 123
          • 将查询结果映射为User对象返回

          响应返回阶段

          • 查询结果沿调用链返回:Mapper → Service → Controller
          • Controller将User对象转换为JSON格式
          • DispatcherServlet将响应写入HttpServletResponse

          视图渲染阶段(可选)

          如果返回的是视图(如JSP):

          • DispatcherServlet将ModelAndView交给ViewResolver
          • ViewResolver解析视图名称,定位具体的JSP文件
          • 视图引擎渲染JSP,生成html响应

          三层架构交互时序图示例

          Client → DispatcherServlet → Controller → Service → Mapper → DB
                                             ↑                |
                                             |                ↓
          Client ← DispatcherServlet ← Controller ← Service ← Mapper

          这种分层架构使得系统各部分的职责更加清晰,便于团队协作开发、单元测试和后期维护。例如:

          • 前端开发人员只需要关注Controller层的接口定义
          • 业务分析师可以基于Service层的代码理解业务规则
          • DBA可以优化Mapper层的SQL语句而不影响业务逻辑

          二、各层详细拆解

          2.1 表现层(Controller 层):用户交互的 "入口"

          表现层是 Spring MVC 框架中与用户直接交互的层级,作为系统的"门面",负责处理 HTTP 请求和响应。它的核心组件是 Controller 类,主要职责包括:

          1. 接收客户端请求(解析请求参数、请求头等)
          2. 进行基础参数校验
          3. 调用 Service 层处理业务逻辑
          4. 组装响应数据并返回给客户端
          5. 处理异常情况并返回友好错误信息

          2.1.1 核心组件与注解详解

          @Controller

          • 作用:标识一个类作为 Spring MVC 的控制器
          • 实现原理:Spring 会在启动时扫描带有该注解的类,并将其注册为 Spring Bean
          • 配套机制:配合组件扫描注解 @ComponentScan 使用

          示例:

          @Controller
          public class HomeController {
              // 控制器方法...
          }
          

          @RequestMapping

          核心功能:

          • 路径映射:将 HTTP 请求映射到控制器方法
          • 请求方法限定:通过 method 属性指定处理的 HTTP 方法
          • 参数匹配:通过 params 属性匹配特定请求参数
          • 头部匹配:通过 headers 属性匹配特定请求头

          高级用法:

          • 通配符支持:如 "/user/*" 可匹配 "/user/123" 等路径
          • 类级别与方法级别组合:类级别定义基础路径,方法级别定义具体路径
          • 媒体类型限定:通过 produces/consumes 指定处理的内容类型

          示例:

          @RequestMapping(value = "/products", method = RequestMethod.GET)
          public String listProducts() {...}
          

          @GetMapping/@PostMapping

          优点:

          • 代码更简洁:相比 @RequestMapping(method = RequestMethod.GET) 更易读
          • 语义更明确:直接表明处理的 HTTP 方法
          • 支持继承:可以组合使用 @RequestMapping 的类级别注解

          示例对比:

          // 传统方式
          @RequestMapping(value = "/user", method = RequestMethod.GET)
          
          // 简化方式
          @GetMapping("/user")
          

          @RequestParam

          主要参数:

          • name/value:指定绑定的请求参数名称
          • required:是否为必须参数(默认 true)
          • defaultValue:参数默认值

          使用场景:

          • 处理查询参数:如 ?page=1&size=10
          • 处理表单数据:如 application/x-www-form-urlencoded

          示例:

          @GetMapping("/search")
          public String search(@RequestParam(name = "keyword", required = false, defaultValue = "") String keyword) {...}
          

          @PathVariable

          特点:

          • 用于 RESTful 风格的 URL 参数获取
          • 支持正则表达式匹配路径变量
          • 可配合 @RequestMapping 的通配符使用

          示例:

          @GetMapping("/users/{userId}/orders/{orderId}")
          public String getOrder(@PathVariable Long userId, @PathVariable String orderId) {...}
          

          @ResponseBody

          工作机制:

          • 通过 HttpMessageConverter 将返回值转换为指定格式
          • 常用转换器:MappingJackson2HttpMessageConverter(JSON)
          • 可自定义转换器处理特殊格式

          典型应用:

          • 前后端分离架构中的 API 接口
          • AJAX 请求响应
          • 移动端接口开发

          示例:

          @ResponseBody
          @GetMapping("/api/user/{id}")
          public User getUser(@PathVariable Long id) {...}
          

          @RestController

          组合优势:

          • 减少样板代码:无需在每个方法上添加 @ResponseBody
          • 语义更清晰:明确表示该类是纯 API 控制器
          • 自动配置:默认启用 JSON 序列化

          实现原理:

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Controller
          @ResponseBody
          public @interface RestController {...}
          

          2.1.2 示例:一个完整的 Controller 实现

          package com.example.demo.controller;
          
          import org.springframework.stereotype.Controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.annotation.*;
          import org.springframework.validation.annotation.Validated;
          import javax.validation.constraints.Min;
          import java.util.List;
          
          @Controller
          @RequestMapping("/user")
          @Validated  // 启用方法参数验证
          public class UserController {
              
              private final UserService userService;
              
              // 推荐使用构造器注入
              public UserController(UserService userService) {
                  this.userService = userService;
              }
          
              /**
               * 用户列表页面
               * @param page 页码(从1开始)
               * @param size 每页条数
               * @param model 视图模型
               * @return 视图名称
               */
              @GetMapping("/list")
              public String listUsers(
                      @RequestParam(defaultValue = "1") @Min(1) int page,
                      @RequestParam(defaultValue = "10") @Min(1) int size,
                      Model model) {
                  
                  PageInfo<User> pageInfo = userService.getUsersByPage(page, size);
                  model.addAttribute("pageInfo", pageInfo);
                  return "user/list";
              }
          
              /**
               * 获取用户详情(REST API)
               * @param userId 用户ID
               * @return 统一响应结果
               */
              @GetMapping("/{userId}")
              @ResponseBody
              public Result<User> getUserDetail(@PathVariable @Min(1) Long userId) {
                  User user = userService.getUserById(userId);
                  return Result.success(user);
              }
          
              /**
               * 创建新用户
               * @param userDTO 用户数据
               * @return 创建结果
               */
              @PostMapping
              @ResponseBody
              public Result<Long> createUser(@RequestBody @Valid UserDTO userDTO) {
                  Long userId = userService.createUser(userDTO);
                  return Result.success(userId);
              }
          }
          

          2.1.3 最佳实践与注意事项

          职责分离原则

          Controller 应保持"瘦身",仅处理:

          • 请求/响应转换
          • 基础参数验证
          • 异常捕获

          所有业务逻辑应委托给 Service 层

          参数校验建议

          使用 JSR-380 规范注解:

          • @NotNull/@NotEmpty/@NotBlank
          • @Size(min=, max=)
          • @Pattern(regexp=)
          • @Min/@Max

          分组校验:通过 groups 属性实现不同场景的校验规则

          自定义校验:实现 ConstraintValidator 接口

          统一响应格式

          推荐结构:

          public class Result<T> {
              private int code;    // 状态码
              private String msg;  // 消息
              private T data;      // 数据
              private long timestamp = System.currentTimeMillis();
              // 构造方法、静态工厂方法等...
          }
          

          使用示例:

          @GetMapping("/{id}")
          public Result<User> getUser(@PathVariable Long id) {
              User user = userService.getUserById(id);
              return Result.success(user);
          }
          

          异常处理

          推荐使用 @ControllerAdvice 统一处理异常

          示例:

          @ControllerAdvice
          public class GlobalExceptionHandler {
              
              @ExceptionHandler(BusinessException.class)
              @ResponseBody
              public Result<?> handleBusinessException(BusinessException e) {
                  return Result.fail(e.getErrorCode(), e.getMessage());
              }
          }
          

          性能考虑

          避免在 Controller 中进行:

          • 复杂计算
          • 数据库操作
          • 耗时 I/O 操作

          使用异步处理:@Async、DeferredResult 等

          安全建议

          • 对敏感参数进行过滤
          • 重要操作添加权限校验
          • 防止 CSRF 攻击
          • 输入参数进行 XSS 防护

          测试建议

          • 使用 MockMvc 进行单元测试

          测试用例应覆盖:

          • 正常流程
          • 参数校验失败情况
          • 异常情况处理
          • 边界条件

          2.2 业务逻辑层(Service 层):系统的 "大脑"

          Service 层是整个系统的核心业务处理中枢,负责实现业务规则、处理事务、协调多个持久层操作,是表现层与持久层之间的 "桥梁"。

          它相当于应用系统的"大脑",负责处理复杂的业务逻辑,确保业务流程的正确性和数据一致性。

          2.2.1 核心组件与注解

          @Service 注解
          • 作用:标记一个类为 Service 层组件,Spring 会自动扫描并注册为 Bean,供 Controller 层注入
          • 使用场景:所有业务逻辑处理类都应该使用此注解

          示例

          @Service
          public class OrderServiceImpl implements OrderService {...}
          
          @Transactional 注解
          • 作用:声明事务管理,可用于类或方法上,指定事务的各种属性

          常用参数

          • propagation:事务传播行为(如REQUIRED, REQUIRES_NEW)
          • isolation:事务隔离级别(如DEFAULT, READ_COMMITTED)
          • rollbackFor:指定哪些异常需要回滚
          • timeout:事务超时时间

          示例

          @Transactional(propagation = Propagation.REQUIRED, 
                       isolation = Isolation.DEFAULT,
                       timeout = 30,
                       rollbackFor = Exception.class)
          
          接口与实现类设计

          优势

          • 面向接口编程,便于后续扩展
          • 方便进行单元测试(可mock接口)
          • 实现多态特性

          典型结构

          ├── service
          │   ├── UserService.java          // 接口
          │   └── impl
          │       └── UserServiceImpl.java  // 实现类

          2.2.2 示例:Service 接口与实现类

          1. Service接口设计
          /**
           * 用户服务接口
           * 定义业务契约,不包含具体实现
           */
          public interface UserService {
          
              /**
               * 查询所有用户
               * @return 用户列表(可能为空列表)
               */
              List<User> getAllUsers();
          
              /**
               * 根据ID查询用户
               * @param userId 用户ID
               * @return 用户实体
               * @throws BusinessException 当ID无效时抛出
               */
              User getUserById(Integer userId) throws BusinessException;
          
              /**
               * 新增用户
               * @param user 用户实体
               * @return 操作是否成功
               * @throws BusinessException 当用户名已存在等业务异常时抛出
               */
              boolean addUser(User user) throws BusinessException;
              
              /**
               * 批量导入用户
               * @param users 用户列表
               * @return 成功导入的数量
               */
              @Transactional
              int batchImportUsers(List<User> users);
          }
          
          2. Service实现类详解
          import org.springframework.stereotype.Service;
          import org.springframework.transaction.annotation.Transactional;
          import javax.annotation.Resource;
          
          /**
           * 用户服务实现类
           * 实现具体的业务逻辑
           */
          @Service // 标记为Service组件
          public class UserServiceImpl implements UserService {
          
              // 使用@Resource或@Autowired注入持久层组件
              @Resource
              private UserMapper userMapper;
              
              @Resource
              private RoleService roleService;
              
              @Resource
              private LogService logService;
          
              // 简单查询操作通常不需要事务
              @Override
              public List<User> getAllUsers() {
                  // 直接调用Mapper层方法获取数据
                  // 可添加缓存逻辑提升性能
                  return userMapper.selectAll();
              }
          
              @Override
              public User getUserById(Integer userId) throws BusinessException {
                  // 业务校验
                  if (userId == null || userId <= 0) {
                      throw new BusinessException(ErrorCode.INVALID_USER_ID, "用户ID无效");
                  }
                  
                  // 查询用户
                  User user = userMapper.selectById(userId);
                  
                  // 业务处理:如果用户不存在
                  if (user == null) {
                      throw new BusinessException(ErrorCode.USER_NOT_FOUND, "用户不存在");
                  }
                  
                  return user;
              }
          
              // 需要事务管理的业务方法
              @Override
              @Transactional(rollbackFor = Exception.class)
              public boolean addUser(User user) throws BusinessException {
                  // 1. 参数校验
                  if (user == null || StringUtils.isEmpty(user.getUsername())) {
                      throw new BusinessException(ErrorCode.INVALID_PARAM, "用户信息不完整");
                  }
                  
                  // 2. 业务校验(用户名唯一性检查)
                  User existingUser = userMapper.selectByUsername(user.getUsername());
                  if (existingUser != null) {
                      throw new BusinessException(ErrorCode.USERNAME_EXISTS, "用户名已存在");
                  }
                  
                  // 3. 设置默认值等业务处理
                  user.setCreateTime(new Date());
                  user.setStatus(1); // 默认激活状态
                  
                  // 4. 调用Mapper层保存数据
                  int rows = userMapper.insert(user);
                  
                  // 5. 关联操作(如分配默认角色)
                  roleService.assignDefaultRole(user.getId());
                  
                  // 6. 记录操作日志(异步处理)
                  logService.asyncRecordLog("USER_ADD", "新增用户:" + user.getUsername());
                  
                  return rows > 0;
              }
              
              @Override
              @Transactional
              public int batchImportUsers(List<User> users) {
                  int successCount = 0;
                  for (User user : users) {
                      try {
                          if (addUser(user)) {
                              successCount++;
                          }
                      } catch (BusinessException e) {
                          // 记录导入失败的用户
                          log.warn("导入用户失败: {}", user.getUsername(), e);
                      }
                  }
                  return successCount;
              }
          }
          

          2.2.3 开发注意事项

          职责划分原则

          • Service层应专注于业务逻辑处理
          • 所有数据库操作必须通过Mapper/DAO层完成
          • 避免在Service层直接编写SQL语句

          事务管理最佳实践

          • 事务注解应加在Service层而非Controller层
          • 默认情况下只对RuntimeException回滚,建议明确指定rollbackFor
          • 只读操作使用@Transactional(readOnly = true)提升性能
          • 避免在同一个类中自调用事务方法(因代理机制会失效)

          异常处理规范

          // 自定义业务异常示例
          public class BusinessException extends RuntimeException {
              private String errorCode;
              
              public BusinessException(String errorCode, String message) {
                  super(message);
                  this.errorCode = errorCode;
              }
              
              // getter方法...
          }
          
          • 使用自定义业务异常替代RuntimeException
          • 不同业务错误定义不同的错误码
          • 在Controller层统一处理业务异常

          性能优化建议

          • 复杂查询考虑添加缓存
          • 批量操作使用批量处理方法
          • 耗时操作考虑异步处理

          测试考量

          • Service层应易于单元测试
          • 使用Mock对象隔离依赖
          • 测试应覆盖各种业务场景和异常分支

          典型业务场景处理

          • 分布式事务:对于跨服务调用,考虑使用Seata等分布式事务解决方案
          • 幂等性处理:对于支付等关键业务,需要实现幂等性控制
          • 业务流水号:重要业务操作应生成唯一业务流水号便于追踪

          2.3 持久层(Mapper/DAO 层):数据的 "搬运工"

          持久层作为应用程序与数据库之间的桥梁,专注于数据的持久化操作。其主要职责包括:

          1. 执行基本的 CRUD 操作(Create/Read/Update/Delete)
          2. 处理数据库事务
          3. 实现数据缓存(可选)
          4. 进行数据校验(基础级别)

          与业务层不同,持久层不关心业务逻辑,只关注"数据如何存储和获取"。在现代 Java Web 开发中,MyBatis 已成为最流行的持久层框架之一,相比传统的 JDBC 和 DAO 模式,它具有以下优势:

          • 简化了数据库操作
          • 提供自动的对象关系映射(ORM)
          • 支持动态SQL
          • 具有更好的性能

          2.3.1 核心组件与配置

          1. Mapper 接口

          Mapper 接口是 MyBatis 的核心概念,它替代了传统的 DAO 接口。与 DAO 不同:

          • 不需要编写实现类
          • MyBatis 通过动态代理技术自动生成实现
          • 方法签名直接对应 SQL 操作
          // 典型 Mapper 接口定义
          @Mapper
          public interface UserMapper {
              // 查询方法
              User selectById(Long id);
              
              // 插入方法
              int insert(User user);
              
              // 更新方法
              int update(User user);
              
              // 删除方法
              int deleteById(Long id);
          }
          
          2. 关键注解
          • @Mapper:标记接口为 MyBatis Mapper 接口
          • @MapperScan:在 Spring Boot 启动类或配置类上使用,指定 Mapper 接口的扫描路径
          • @Param:用于多参数方法,指定参数名称
          3. XML 映射文件

          XML 文件是 SQL 语句的主要存放位置,通常包含:

          • 结果映射定义(<resultMap>)
          • SQL 查询语句(<select>)
          • 插入语句(<insert>)
          • 更新语句(<update>)
          • 删除语句(<delete>)

          2.3.2 示例详解

          1. Mapper 接口增强版
          @Mapper
          public interface UserMapper {
              // 基础CRUD操作
              List<User> selectAll();
              User selectById(@Param("id") Long id);
              int insert(User user);
              int update(User user);
              int deleteById(@Param("id") Long id);
              
              // 分页查询
              List<User> selectByPage(@Param("offset") int offset, 
                                     @Param("pageSize") int pageSize);
              
              // 条件查询
              List<User> selectByCondition(@Param("condition") UserQueryCondition condition);
              
              // 批量操作
              int batchInsert(@Param("users") List<User> users);
              int batchUpdate(@Param("users") List<User> users);
          }
          
          2. 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="UserDetailResultMap" type="User">
                  <id column="id" property="id"/>
                  <result column="username" property="username"/>
                  <result column="email" property="email"/>
                  <result column="create_time" property="createTime" 
                          jdbcType="TIMESTAMP"/>
                  <!-- 关联映射 -->
                  <association property="department" javaType="Department">
                      <id column="dept_id" property="id"/>
                      <result column="dept_name" property="name"/>
                  </association>
              </resultMap>
          
              <!-- 动态SQL查询 -->
              <select id="selectByCondition" resultMap="UserDetailResultMap">
                  SELECT u.*, d.id as dept_id, d.name as dept_name
                  FROM user u
                  LEFT JOIN department d ON u.dept_id = d.id
                  <where>
                      <if test="condition.username != null">
                          AND u.username LIKE CONCAT('%', #{condition.username}, '%')
                      </if>
                      <if test="condition.email != null">
                          AND u.email = #{condition.email}
                      </if>
                      <if test="condition.createTimeStart != null">
                          AND u.create_time >= #{condition.createTimeStart}
                      </if>
                  </where>
                  ORDER BY u.create_time DESC
              </select>
          
              <!-- 批量插入 -->
              <insert id="batchInsert">
                  INSERT INTO user (username, email, create_time)
                  VALUES
                  <foreach collection="users" item="user" separator=",">
                      (#{user.username}, #{user.email}, #{user.createTime})
                  </foreach>
              </insert>
          </mapper>
          

          2.3.3 高级特性与最佳实践

          动态SQL

          • 使用<if>, <choose>, <when>, <otherwise>标签实现条件判断
          • <where>标签自动处理WHERE子句
          • <set>标签用于UPDATE语句

          关联查询

          • 一对一:<association>
          • 一对多:<collection>
          • 延迟加载:配置lazyLoadingEnabled=true

          性能优化

          • 使用二级缓存(需谨慎)
          • 批量操作代替单条操作
          • 合理使用延迟加载

          事务管理

          • 在Service层使用@Transactional注解
          • 配置适当的事务传播行为

          分页实现

          • 使用PageHelper插件
          • 手动实现分页(limit offset)

          2.3.4 常见问题解决方案

          参数映射问题

          • 简单类型参数:直接使用#{param}
          • 对象参数:使用#{propertyName}
          • Map参数:使用#{key}
          • 多参数:必须使用@Param注解

          结果映射问题

          • 字段名与属性名不一致时使用<resultMap>
          • 复杂类型使用嵌套映射
          • 使用<constructor>进行构造函数映射

          SQL注入防护

          • 永远使用#{}而不是${}进行参数绑定
          • 对用户输入进行严格校验

          性能监控

          • 配置SQL日志输出
          • 使用MyBatis-Plus的性能分析插件
          • 监控慢SQL

          通过以上规范和最佳实践,可以构建出高效、可维护的持久层,为应用程序提供可靠的数据访问支持。

          三、基于三层架构搭建 Spring MVC 项目

          3.1 项目结构(Maven)

          一个标准的 Spring MVC 三层架构项目结构如下(以 IntelliJ IDEA 为例):

          src/
          ├── main/
          │   ├── java/
          │   │   └── com/
          │   │       └── example/
          │   │           ├── controller/       # 表现层(Controller)
          │   │           │   └── UserController.java
          │   │           ├── service/          # 业务逻辑层(Service)
          │   │           │   ├── UserService.java      # 接口
          │   │           │   └── impl/
          │   │           │       └── UserServiceImpl.java  # 实现类
          │   │           ├── mapper/           # 持久层(Mapper)
          │   │           │   └── UserMapper.java
          │   │           ├── entity/           # 实体类(对应数据库表)
          │   │           │   └── User.java
          │   │           ├── exception/        # 自定义异常
          │   │           │   └── BusinessException.java
          │   │           ├── config/           # Spring配置类
          │   │           │   └── SpringMvcConfig.java
          │   │           └── util/             # 工具类
          │   │               └── Result.java   # 统一响应封装
          │   ├── resources/
          │   │   ├── mapper/                   # Mapper XML文件
          │   │   │   └── UserMapper.xml
          │   │   ├── application.properties    # 全局配置(数据库、MyBatis等)
          │   │   └── static/                   # 静态资源(css、JS、图片)
          │   └── webapp/                       # Web资源
          │       ├── WEB-INF/
          │       │   ├── views/                # 视图页面(JSP/HTML)
          │       │   │   └── userList.jsp
          │       │   └── web.xml               # Web配置(可选,Spring Boot可省略)
          └── pom.xml                           # Maven依赖

          各层职责说明

          • 表现层(Controller):接收HTTP请求,参数校验,调用Service并返回响应
          • 业务逻辑层(Service):处理业务逻辑,事务控制,调用Mapper层
          • 持久层(Mapper):数据库操作,与MyBatis框架交互
          • 实体层(Entity):定义数据模型,与数据库表映射
          • 配置层(Config):Spring相关配置,如MVC配置、组件扫描等

          3.2 核心依赖(pom.xml)

          <!-- Spring MVC核心依赖 -->
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-webmvc</artifactId>
              <version>5.3.28</version>
          </dependency>
          
          <!-- MyBatis依赖 -->
          <dependency>
              <groupId>org.mybatis</groupId>
              <artifactId>mybatis</artifactId>
              <version>3.5.13</version>
          </dependency>
          
          <!-- MyBatis整合Spring -->
          <dependency>
              <groupId>org.mybatis</groupId>
              <artifactId>mybatis-spring</artifactId>
              <version>2.1.2</version>
          </dependency>
          
          <!-- MySQL驱动 -->
          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>8.0.33</version>
              <scope>runtime</scope>
          </dependency>
          
          <!-- 数据库连接池(HikariCP) -->
          <dependency>
              <groupId>com.zaxxer</groupId>
              <artifactId>HikariCP</artifactId>
              <version>5.0.1</version>
          </dependency>
          
          <!-- JSP依赖(若使用JSP视图) -->
          <dependency>
              <groupId>javax.servlet.jsp</groupId>
              <artifactId>jsp-api</artifactId>
              <version>2.2</version>
              <scope>provided</scope>
          </dependency>
          
          <!-- lombok(简化实体类代码) -->
          <dependency>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
              <version>1.18.28</version>
              <optional>true</optional>
          </dependency>
          
          <!-- JSON处理(Jackson) -->
          <dependency>
              <groupId>com.fasterxml.jackson.core</groupId>
              <artifactId>jackson-databind</artifactId>
              <version>2.14.2</version>
          </dependency>
          

          3.3 核心配置(application.properties)

          # 数据库配置(HikariCP)
          spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
          spring.datasource.url=jdbc:mysql://localhost:3306/spring_mvc_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
          spring.datasource.username=root
          spring.datasource.password=123456
          
          # HikariCP连接池配置
          spring.datasource.hikari.maximum-pool-size=10
          spring.datasource.hikari.minimum-idle=5
          spring.datasource.hikari.idle-timeout=300000
          
          # MyBatis配置
          # Mapper XML文件路径
          mybatis.mapper-locations=classpath:mapper/*.xml
          # 实体类别名扫描包(简化XML中的type配置)
          mybatis.type-aliases-package=com.example.entity
          # 开启MyBatis日志(便于调试SQL)
          mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
          
          # Spring MVC视图解析器配置(JSP)
          spring.mvc.view.prefix=/WEB-INF/views/
          spring.mvc.view.suffix=.jsp
          
          # 静态资源访问配置(CSS/JS/图片)
          spring.mvc.static-path-pattern=/static/**
          

          3.4 配置类(SpringMvcConfig.java)

          import org.mybatis.spring.annotation.MapperScan;
          import org.springframework.context.annotation.ComponentScan;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.web.servlet.config.annotation.EnableWebMvc;
          import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
          import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
          import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
          
          @Configuration // 标记为配置类
          @EnableWebMvc // 开启Spring MVC功能
          @ComponentScan("com.example") // 扫描组件(Controller/Service等)
          @MapperScan("com.example.mapper") // 扫描MyBatis Mapper接口
          public class SpringMvcConfig implements WebMvcConfigurer {
          
              // 配置视图解析器(JSP)
              @Override
              public void configureViewResolvers(ViewResolverRegistry registry) {
                  registry.jsp("/WEB-INF/views/", ".jsp");
              }
          
              // 配置静态资源访问(避免静态资源被DispatcherServlet拦截)
              @Override
              public void addResourceHandlers(ResourceHandlerRegistry registry) {
                  registry.addResourceHandler("/static/**")
                          .addResourceLocations("classpath:/static/");
              }
              
              // 配置JSON消息转换器(自动注册Jackson)
              // Spring会自动注册MappingJackson2HttpMessageConverter
          }
          

          3.5 功能测试:验证三层架构流程

          3.5.1 数据库表准备(MySQL)

          CREATE DATABASE IF NOT EXISTS spring_mvc_db;
          USE spring_mvc_db;
          
          CREATE TABLE IF NOT EXISTS `user` (
              `user_id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
              `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
              `nickname` VARCHAR(50) DEFAULT '' COMMENT '昵称',
              `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户表';
          
          -- 初始化测试数据
          INSERT INTO `user`(username, nickname) VALUES
          ('admin', '管理员'),
          ('user1', '普通用户1'),
          ('user2', '普通用户2');
          

          3.5.2 接口测试(Postman)

          1. 查询所有用户(GET)
          • 请求地址http://localhost:8080/spring-mvc-demo/user/list
          • 请求方式:GET

          预期结果

          • 返回userList.jsp页面
          • 页面中展示数据库中的用户列表
          • 页面包含用户ID、用户名、昵称和创建时间信息
          2. 根据ID查询用户(GET)
          • 请求地址http://localhost:8080/spring-mvc-demo/user/1
          • 请求方式:GET
          • 请求头Accept: application/json

          预期结果

          {
            "code": 200,
            "msg": "success",
            "data": {
              "userId": 1,
              "username": "admin",
              "nickname": "管理员",
              "createTime": "2024-05-20 10:30:00"
            }
          }
          
          3. 新增用户(POST)
          • 请求地址http://localhost:8080/spring-mvc-demo/user/add
          • 请求方式:POST
          • 请求头Content-Type: application/json

          请求体

          {
            "username": "newuser",
            "nickname": "新用户"
          }
          

          预期结果

          {
            "code": 200,
            "msg": "添加成功",
            "data": null
          }
          

          四、三层架构常见问题与解决方案

          4.1 表现层常见问题

          问题 1:Controller 无法接收请求(404 错误)

          可能原因分析:

          请求路径映射问题

          • 前端发送的请求路径与后端@RequestMapping注解定义的路径不匹配
          • 常见错误包括:大小写不一致(如/userInfo vs /userinfo)、缺少或多余斜杠(如/api/user vs /api/user/
          • 特殊字符编码问题(如空格应编码为%20

          Controller 配置问题

          • 类未添加@Controller@RestController注解

          类未被Spring组件扫描到,可能原因:

          • @ComponentScan配置的包路径不正确
          • Controller类所在的包不在主启动类的同级或子级目录下
          • 使用了错误的扫描注解(如@ServletComponentScan

          DispatcherServlet 配置问题

          • web.xmlurl-pattern配置为/*会拦截所有请求,包括静态资源和JSP
          • 未正确配置静态资源处理器
          • 缺少必要的Servlet映射配置

          解决方案及实施步骤:

          路径核对

          • 使用Postman等工具直接测试Controller接口
          • 在Controller方法中添加日志输出,确认请求是否到达
          • 检查是否有@PathVariable参数但请求未提供

          注解检查

          @RestController  // 或 @Controller
          @RequestMapping("/api/users")
          public class UserController {
              // 确保方法上有@RequestMapping或其派生注解
              @GetMapping("/{id}")
              public User getUser(@PathVariable Long id) {
                  // ...
              }
          }
          

          DispatcherServlet 配置

          推荐配置:

          <servlet-mapping>
              <servlet-name>dispatcher</servlet-name>
              <url-pattern>/</url-pattern>  <!-- 而不是 /* -->
          </servlet-mapping>
          

          Spring Boot中配置静态资源:

          @Override
          public void addResourceHandlers(ResourceHandlerRegistry registry) {
              registry.addResourceHandler("/static/**")
                     .addResourceLocations("classpath:/static/");
          }
          

          问题 2:参数绑定失败(400 错误)

          可能原因深度分析:

          类型不匹配

          • 前端传递字符串"123",但后端使用Integer接收
          • 集合类型参数未正确格式化(如List<String>需要?names=aa&names=bb

          日期格式化

          常见日期格式冲突:

          • 前端:"2024-05-20"
          • 后端期望:"2024/05/20"或时间戳

          时区问题(如UTC与本地时区差异)

          命名不一致

          • 驼峰命名与下划线命名转换问题
          • 嵌套对象属性访问(如user.address.city

          完整解决方案:

          基础类型处理

          @GetMapping("/detail")
          public Result detail(@RequestParam("user_id") Integer userId) {
              // 明确指定参数名
          }
          

          日期处理最佳实践

          @PostMapping("/schedule")
          public Result createSchedule(
                  @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
                  @RequestBody @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") Date endTime) {
              // 分别处理URL参数和JSON体中的日期
          }
          

          复杂对象绑定

          // 实体类
          public class User {
              @JsonProperty("user_id")  // 处理JSON字段名
              private Long userId;
              
              @RequestParam("user_name")  // 处理URL参数名
              private String userName;
          }
          
          // Controller
          @PostMapping("/update")
          public Result updateUser(@Valid User user) {
              // 支持混合绑定方式
          }
          

          补充技巧

          全局日期格式配置(application.yml):

          spring:
            jackson:
              date-format: yyyy-MM-dd HH:mm:ss
              time-zone: GMT+8
          

          自定义参数解析器:实现HandlerMethodArgumentResolver处理特殊参数类型

          4.2 业务逻辑层常见问题

          问题 1:事务不生效(数据提交后未回滚)

          详细原因分析:

          注解位置问题

          • @Transactional注解在private/protected方法上无效
          • 注解被同类中的非事务方法调用

          异常处理问题

          • 捕获了异常但未重新抛出
          • 抛出的异常类型不是RuntimeException
          • 自定义异常未继承RuntimeException

          代理机制问题

          • 使用this.method()调用导致绕过Spring代理
          • 特殊场景:异步方法、synchronized方法

          完整解决方案:

          正确的事务配置

          @Service
          public class OrderService {
              @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
              public void createOrder(OrderDTO dto) throws BusinessException {
                  // 业务代码
              }
          }
          

          解决自调用问题方案

          方案1:重构代码结构

          方案2:通过AopContext获取代理对象

          ((OrderService) AopContext.currentProxy()).internalMethod();
          

          方案3:使用@Autowired注入自身(需配合@Lazy

          事务调试技巧

          开启事务日志:

          logging.level.org.springframework.transaction.interceptor=DEBUG
          logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
          

          问题 2:Service 层循环依赖(BeanCreationException)

          典型场景分析:

          直接循环依赖

          @Service
          class AService { @Autowired BService b; }
          
          @Service
          class BService { @Autowired AService a; }
          

          间接循环依赖: A → B → C → A

          构造器注入导致的不可解循环

          @Service
          class AService {
              private final BService b;
              public AService(BService b) { this.b = b; }
          }
          

          进阶解决方案:

          架构层面重构

          • 提取公共逻辑到新的CommonService
          • 使用门面模式封装相关服务

          技术解决方案

          // 方案1:使用setter注入 + @Lazy
          @Service
          class AService {
              private BService b;
              
              @Autowired
              public void setB(@Lazy BService b) { this.b = b; }
          }
          
          // 方案2:使用ApplicationContext
          @Service
          class BService implements ApplicationContextAware {
              private ApplicationContext context;
              
              public void someMethod() {
                  AService a = context.getBean(AService.class);
              }
          }
          

          Spring Boot 2.6+ 处理

          配置允许循环引用(不推荐):

          spring.main.allow-circular-references=true
          

          4.3 持久层常见问题

          问题 1:Mapper 接口与 XML 不匹配(BindingException)

          完整排查清单:

          路径匹配问题

          • 检查XML文件是否在resources目录的对应包路径下
          • Maven项目注意src/main/resourcessrc/main/java的目录结构一致性

          ID匹配问题

          • 方法重载导致混淆
          • 泛型方法特殊处理

          配置问题

          • MyBatis配置文件中<mappers>配置错误
          • Spring Boot中mybatis.mapper-locations配置不完整

          详细解决方案:

          项目结构规范

          src/main/java
            └─com/example/mapper
                  UserMapper.java
          src/main/resources
            └─com/example/mapper
                  UserMapper.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">
              <select id="selectById" resultType="com.example.entity.User">
                  SELECT * FROM user WHERE id = #{id}
              </select>
          </mapper>
          

          Spring Boot配置

          # 确保扫描到Mapper接口
          mybatis.mapper-locations=classpath*:mapper/**/*.xml
          # 开启MyBatis日志
          logging.level.com.example.mapper=DEBUG
          

          问题 2:SQL 执行报错(SQLSyntaxErrorException)

          深度排查指南:

          SQL语法问题

          • 数据库方言差异(MySQL vs oracle)
          • 保留关键字冲突(如使用order作为表名)
          • 分页语法差异

          参数处理问题

          • #{}${}混用导致的语法错误
          • 参数类型不匹配(如字符串参数未加引号)

          数据库连接问题

          • 连接池配置不当
          • 数据库版本不兼容
          • SSL连接配置错误

          专业解决方案:

          SQL调试技巧

          # 打印完整执行的SQL(包括参数)
          mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
          

          参数处理规范

          <!-- 正确用法 -->
          <select id="findUsers" resultType="User">
              SELECT * FROM user 
              WHERE username = #{name} 
              AND create_time > #{date,jdbcType=TIMESTAMP}
          </select>
          
          <!-- 动态表名用法(需确保安全) -->
          <select id="selectByTable" resultType="map">
              SELECT * FROM ${tableName} 
              WHERE id = #{id}
          </select>
          

          数据库连接配置

          # 完整连接配置示例
          spring.datasource.url=jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=Asia/Shanghai
          spring.datasource.username=root
          spring.datasource.password=123456
          spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
          

          高级技巧

          使用MyBatis-Plus的SQL注入器防止SQL错误

          配置SQL执行超时时间:

          <select id="complexQuery" timeout="30">
              <!-- 复杂查询 -->
          </select>
          

          五、三层架构优化建议

          5.1 代码复用与解耦

          统一异常处理

          使用Spring提供的@ControllerAdvice+@ExceptionHandler实现全局异常处理机制,可以避免在各个Controller中重复编写try-catch块。这种集中式异常处理方式具有以下优势:

          1. 减少重复代码,提高代码整洁度
          2. 统一异常响应格式,便于前端处理
          3. 可灵活分类处理不同类型的异常
          @ControllerAdvice
          public class GlobalExceptionHandler {
              /**
               * 处理业务异常
               * @param e 业务异常对象
               * @return 统一响应结果
               */
              @ExceptionHandler(BusinessException.class)
              @ResponseBody
              public Result<?> handleBusinessException(BusinessException e) {
                  log.error("业务异常:{}", e.getMessage(), e);
                  return Result.fail(e.getCode(), e.getMessage());
              }
          
              /**
               * 处理系统异常
               * @param e 异常对象
               * @return 统一响应结果
               */
              @ExceptionHandler(Exception.class)
              @ResponseBody
              public Result<?> handleException(Exception e) {
                  log.error("系统异常:", e);
                  return Result.pythonfail(500, "系统繁忙,请稍后再试");
              }
          }
          

          通用Service/DAO层设计

          通过抽取通用CRUD操作方法到基类中,可以大幅减少重复代码。这种设计模式特别适合具有大量相似CRUD操作的系统:

          1.定义通用Mapper接口

          public interface BaseMapper<T> {
              int insert(T entity);  // 插入单条记录
              int deleteById(@Param("id") Serializable id);  // 根据主键删除
              int updateById(@Param("entity") T entity);  // 根据主键更新
              T selectById(@Param("id") Serializable id);  // 根据主键查询
              List<T> selectList(@Param("entity") T entity);  // 条件查询列表
              Page<T> selectPage(Page<T> page, @Param("entity") T entity);  // 分页查询
          }
          

          2.业务Mapper继承通用接口

          public interface UserMapper extends BaseMapper<User> {
              // 自定义方法
              @Select("SELECT * FROM user WHERE username = #{username}")
              User selectByUsername(@Param("username") String username);
              
              // 复杂查询示例
              @Select("SELECT u.* FROM user u JOIN department d ON u.dept_id = d.id WHERE d.name = #{deptName}")
              List<User> selectByDepartmentName(@Param("deptName") String deptName);
          }
          

          3.通用Service实现

          public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements BaseService<T> {
              @Autowired
              protected M baseMapper;
          
              @Override
              public boolean save(T entity) {
                  return baseMapper.insert(entity) > 0;
              }
          
              @Override
              public boolean updateById(T entity) {
                  return baseMapper.updateById(entity) > 0;
              }
              
              // 其他通用方法实现...
          }
          

          5.2 性能优化

          缓存设计

          在Service层合理使用缓存可以显著提升系统性能,特别是对于读多写少的场景:

          缓存使用场景:

          • 高频访问的配置数据
          • 用户基础信息
          • 商品详情等静态数据
          • 计算结果缓存

          缓存实现示例:

          @Service
          public class UserServiceImpl implements UserService {
              @Resource
              private RedisTemplate<String, User> redisTemplate;
              @Resource
              private UserMapper userMapper;
              
              // 缓存key前缀
              private static final String USER_CACHE_PREFIX = "user:id:";
              // 缓存过期时间(小时)
              private static final long CACHE_EXPIRE_HOURS = 1;
          
              @Overrihttp://www.devze.comde
              @Transactional(readOnly = true)
              public User getUserById(Integer userId) {
          编程客栈        String key = USER_CACHE_PREFIX + userId;
                  // 1. 先查缓存
                  User user = redisTemplate.opsForValue().get(key);
                  if (user != null) {
                      return user;
                  }
                  
                  // 2. 缓存未命中,查数据库
                  user = userMapper.selectById(userId);
                  if (user != null) {
                      // 3. 设置缓存
                      redisTemplate.opsForValue().set(
                          key, 
                          user, 
                          CACHE_EXPIRE_HOURS, 
                          TimeUnit.HOURS
                      );
                      
                      // 4. 异步更新用户访问记录
                      CompletableFuture.runAsync(() -> 
                          updateUserAccessTime(userId)
                      );
                  }
                  return user;
              }
              
              // 缓存一致性处理
              @Override
              @CacheEvict(key = "#user.id", condition = "#user.id != null")
              public boolean updateUser(User user) {
                  return userMapper.updateById(user) > 0;
              }
          }
          

          批量操作优化

          使用批量操作可以大幅减少数据库交互次数,提高性能:

          1.MyBatis批量插入示例:

          <!-- 批量插入用户 -->
          <insert id="batchInsert" parameterType="java.util.List">
              INSERT INTO user (username, password, nickname, create_time)
              VALUES
              <foreach collection="list" item="item" index="index" separator=",">
                  (#{item.username}, #{item.password}, #{item.nickname}, 
                  <choose>
                      <when test="item.createTime != null">#{item.createTime}</when>
                      <otherwise>NOW()</otherwise>
                  </choose>)
              </foreach>
          </insert>
          

          2.批量更新示例:

          @Transactional
          public int batchUpdateUserStatus(List<Integer> userIds, Integer status) {
              return userMapper.batchUpdateStatus(userIds, status);
          }
          
          <!-- XML映射 -->
          <update id="batchUpdateStatus">
              UPDATE user SET status = #{status} WHERE id IN
              <foreach collection="userIds" item="id" open="(" separator="," close=")">
                  #{id}
              </foreach>
          </update>
          

          5.3 扩展性优化

          接口版本控制

          良好的版本控制策略可以保证系统平滑升级:

          1.URL路径版本控制:

          @RestController
          @RequestMapping("/api/v1/user")
          public class UserControllerV1 {
              @GetMapping("/list")
              public Result<List<User>> listUsers() {
                  // V1版本实现
              }
          }
          
          @RestController
          @RequestMapping("/api/v2/user")
          public class UserControllerV2 {
              @GetMapping("/list")
              public Result<PageInfo<User>> listUsers() {
                  // V2版本实现,返回分页数据
              }
          }
          

          2.请求头版本控制:

          @GetMapping("/user/list")
          public Result<?> listUsers(@RequestHeader("X-API-Version") String version) {
              if ("2.0".equals(version)) {
                  // 新版本逻辑
              } else {
                  // 默认版本逻辑
              }
          }
          

          多数据源支持

          使用Spring的AbstractRoutingDataSource实现动态数据源切换:

          1.配置多数据源:

          @Configuration
          public class DataSourceConfig {
              @Bean
              @Primary
              @ConfigurationProperties(prefix = "spring.datasource.master")
              public DataSource masterDataSource() {
                  return DataSourceBuilder.create().build();
              }
          
              @Bean
              @ConfigurationProperties(prefix = "spring.datasource.slave")
              public DataSource slaveDataSource() {
                  return DataSourceBuilder.create().build();
              }
          
              @Bean
              public DataSource dynamicDataSource() {
                  Map<Object, Object> targetDataSources = new HashMap<>();
                  targetDataSources.put("master", masterDataSource());
                  targetDataSources.put("slave", slaveDataSource());
                  
                  DynamicDataSource dynamicDataSource = new DynamicDataSource();
                  dynamicDataSource.setTargetDataSources(targetDataSources);
                  dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
                  return dynamicDataSource;
              }
          }
          

          2.动态数据源路由:

          public class DynamicDataSource extends AbstractRoutingDataSource {
              @Override
              protected Object determineCurrentLookupKey() {
                  return DataSourceContextHolder.getDataSourceType();
              }
          }
          
          public class DataSourceContextHolder {
              private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
          
              public static void setDataSourceType(String dataSourceType) {
                  contextHolder.set(dataSourceType);
              }
          
              public static String getDataSourceType() {
                  return contextHolder.get();
              }
          
              public static void clearDataSourceType() {
                  contextHolder.remove();
              }
          }
          

          依赖注入优化

          使用构造器注入可以避免循环依赖问题,提高代码可测试性:

          1.推荐做法:

          @Service
          jspublic class UserServiceImpl implements UserService {
              private final UserMapper userMapper;
              private final RoleService roleService;
          
              @Autowired
              public UserServiceImpl(UserMapper userMapper, RoleService roleService) {
                  this.userMapper = userMapper;
                  this.roleService = roleService;
              }
              
              // 业务方法...
          }
          

          2.使用Lombok简化代码:

          @Service
          @RequiredArgsConstructor
          public class UserServiceImpl implements UserService {
              private final UserMapper userMapper;
              private final RoleService roleService;
              
              // 自动生成构造器,无需手动编写
          }
          

          3.循环依赖解决方案:

          // 使用@Lazy注解解决循环依赖
          @Service
          public class OrderServiceImpl implements OrderService {
              private final UserService userService;
              
              public OrderServiceImpl(@Lazy UserService userService) {
                  this.userService = userService;
              }
          }
          

          总结

          以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

          0

          上一篇:

          下一篇:

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          最新开发

          开发排行榜