21-MyBatis-Plus进阶与设计模式实战

学习目标

  • 掌握 MyBatis-Plus 全套功能(CRUD、条件构造器、分页、乐观锁)
  • 熟练使用代码生成器快速搭建项目骨架
  • 理解并掌握10+种高频设计模式
  • 能够识别 Spring/MyBatis 框架源码中的设计模式
  • 能够在项目中灵活运用设计模式解决实际问题
  • 能够回答大厂高频设计模式面试题

知识思维导图

MyBatis-Plus与设计模式

MyBatis-Plus

基础功能

BaseMapper

IService

ServiceImpl

条件构造器

QueryWrapper

LambdaQueryWrapper

UpdateWrapper

高级功能

分页插件

乐观锁

逻辑删除

自动填充

代码生成器

AutoGenerator

模板引擎

设计模式

创建型

单例模式

工厂模式

建造者模式

原型模式

结构型

代理模式

装饰器模式

适配器模式

门面模式

行为型

观察者模式

策略模式

模板方法

责任链模式

命令模式


一、MyBatis-Plus 快速入门

1.1 为什么使用 MyBatis-Plus?

MyBatis 虽然灵活,但写基础 CRUD 非常繁琐:

// 原生 MyBatis:每个实体都要写这些重复代码
// UserMapper.xml
<select id="selectById" resultType="User">
    SELECT * FROM user WHERE id = #{id}
</select>
<insert id="insert">
    INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})
</insert>
<update id="updateById">
    UPDATE user SET name=#{name}, age=#{age} WHERE id=#{id}
</update>
<delete id="deleteById">
    DELETE FROM user WHERE id = #{id}
</delete>

MyBatis-Plus 内置了所有基础 CRUD,无需编写 XML:

// MyBatis-Plus:继承 BaseMapper,自动拥有所有基础操作
@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 什么都不用写,BaseMapper 已经提供了所有基础方法
}

1.2 快速入门

依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
    username: root
    password: root123
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true   # 下划线转驼峰
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL
  global-config:
    db-config:
      id-type: auto          # 主键自增
      logic-delete-field: deleted   # 逻辑删除字段
      logic-delete-value: 1         # 删除值
      logic-not-delete-value: 0     # 未删除值

实体类:

@Data
@TableName("user")  // 对应数据库表名(默认类名转下划线)
public class User {

    @TableId(type = IdType.AUTO)  // 主键,自增
    private Long id;

    @TableField("user_name")  // 字段名与属性名不同时指定
    private String userName;

    private Integer age;

    private String email;

    @TableField(fill = FieldFill.INSERT)  // 插入时自动填充
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)  // 插入和更新时自动填充
    private LocalDateTime updateTime;

    @TableLogic  // 逻辑删除字段
    private Integer deleted;

    @Version  // 乐观锁版本号
    private Integer version;
}

Mapper:

@Mapper
public interface UserMapper extends BaseMapper<User> {
    // BaseMapper 已提供:
    // insert, deleteById, deleteByMap, delete, deleteBatchIds
    // updateById, update
    // selectById, selectBatchIds, selectByMap, selectOne
    // selectCount, selectList, selectMaps, selectObjs
    // selectPage, selectMapsPage
}

Service:

// Service 接口
public interface UserService extends IService<User> {
    // IService 提供了更丰富的方法:
    // save, saveBatch, saveOrUpdate
    // removeById, removeByMap, remove, removeByIds
    // updateById, update, updateBatchById
    // getById, getOne, getMap
    // count, list, listByIds, listByMap
    // page, listMaps, listObjs
}

// Service 实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {
    // ServiceImpl 已实现所有 IService 方法
    // 可以直接使用 baseMapper 调用 Mapper 方法
}

1.3 BaseMapper 常用方法

@SpringBootTest
public class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testCRUD() {
        // 1. 新增
        User user = new User();
        user.setUserName("张三");
        user.setAge(25);
        user.setEmail("zhangsan@example.com");
        userMapper.insert(user);
        System.out.println("新增后ID: " + user.getId()); // 自动回填ID

        // 2. 根据ID查询
        User found = userMapper.selectById(user.getId());
        System.out.println("查询结果: " + found);

        // 3. 根据ID更新
        User update = new User();
        update.setId(user.getId());
        update.setAge(26);
        userMapper.updateById(update);

        // 4. 根据ID删除(逻辑删除)
        userMapper.deleteById(user.getId());

        // 5. 批量查询
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));

        // 6. 查询总数
        Long count = userMapper.selectCount(null);
        System.out.println("总数: " + count);
    }
}

二、条件构造器

2.1 QueryWrapper

@Service
public class UserQueryService {

    @Autowired
    private UserMapper userMapper;

    /**
     * QueryWrapper 示例
     */
    public void queryExamples() {

        // 1. 等于(eq)
        QueryWrapper<User> wrapper1 = new QueryWrapper<>();
        wrapper1.eq("user_name", "张三");
        User user = userMapper.selectOne(wrapper1);

        // 2. 多条件查询
        QueryWrapper<User> wrapper2 = new QueryWrapper<>();
        wrapper2.eq("age", 25)
                .like("user_name", "张")    // LIKE '%张%'
                .between("age", 20, 30)     // BETWEEN 20 AND 30
                .isNotNull("email")          // email IS NOT NULL
                .orderByDesc("create_time"); // ORDER BY create_time DESC
        List<User> users = userMapper.selectList(wrapper2);

        // 3. OR 条件
        QueryWrapper<User> wrapper3 = new QueryWrapper<>();
        wrapper3.eq("user_name", "张三")
                .or()
                .eq("user_name", "李四");
        // WHERE user_name = '张三' OR user_name = '李四'

        // 4. 嵌套条件
        QueryWrapper<User> wrapper4 = new QueryWrapper<>();
        wrapper4.and(w -> w.eq("age", 25).eq("user_name", "张三"))
                .or(w -> w.eq("age", 30).eq("user_name", "李四"));
        // WHERE (age=25 AND user_name='张三') OR (age=30 AND user_name='李四')

        // 5. 指定查询字段
        QueryWrapper<User> wrapper5 = new QueryWrapper<>();
        wrapper5.select("id", "user_name", "age")  // 只查这三个字段
                .eq("age", 25);

        // 6. 子查询
        QueryWrapper<User> wrapper6 = new QueryWrapper<>();
        wrapper6.inSql("id", "SELECT user_id FROM order WHERE status = 1");
        // WHERE id IN (SELECT user_id FROM order WHERE status = 1)
    }
}

2.2 LambdaQueryWrapper(推荐)

// LambdaQueryWrapper 使用方法引用,避免字段名写错
@Service
public class UserLambdaService {

    @Autowired
    private UserMapper userMapper;

    public void lambdaQueryExamples() {

        // 1. 基础查询(使用方法引用,编译期检查)
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUserName, "张三")
               .ge(User::getAge, 18)           // age >= 18
               .le(User::getAge, 60)           // age <= 60
               .like(User::getEmail, "@gmail") // email LIKE '%@gmail%'
               .orderByDesc(User::getCreateTime);

        List<User> users = userMapper.selectList(wrapper);

        // 2. 条件判断(动态查询)
        String name = "张三";
        Integer minAge = null;  // null时不加此条件

        LambdaQueryWrapper<User> dynamicWrapper = new LambdaQueryWrapper<>();
        dynamicWrapper
            // 第一个参数为条件,false时不加此查询条件
            .eq(StringUtils.hasText(name), User::getUserName, name)
            .ge(minAge != null, User::getAge, minAge);

        // 3. 链式调用(Wrappers工具类)
        List<User> result = userMapper.selectList(
            Wrappers.<User>lambdaQuery()
                .eq(User::getAge, 25)
                .select(User::getId, User::getUserName)
        );
    }

    /**
     * LambdaUpdateWrapper 更新
     */
    public void lambdaUpdateExamples() {

        // 更新指定字段
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(User::getId, 1L)
                     .set(User::getAge, 26)
                     .set(User::getEmail, "new@example.com");

        userMapper.update(null, updateWrapper);

        // 链式更新
        userMapper.update(null,
            Wrappers.<User>lambdaUpdate()
                .eq(User::getId, 1L)
                .set(User::getAge, 26)
        );
    }
}

2.3 常用条件方法速查

等值比较:
  eq(column, val)      → column = val
  ne(column, val)      → column != val
  gt(column, val)      → column > val
  ge(column, val)      → column >= val
  lt(column, val)      → column < val
  le(column, val)      → column <= val

范围查询:
  between(column, val1, val2)  → column BETWEEN val1 AND val2
  notBetween(...)              → column NOT BETWEEN val1 AND val2
  in(column, list)             → column IN (...)
  notIn(column, list)          → column NOT IN (...)

模糊查询:
  like(column, val)      → column LIKE '%val%'
  notLike(column, val)   → column NOT LIKE '%val%'
  likeLeft(column, val)  → column LIKE '%val'
  likeRight(column, val) → column LIKE 'val%'

空值判断:
  isNull(column)    → column IS NULL
  isNotNull(column) → column IS NOT NULL

排序:
  orderByAsc(column)   → ORDER BY column ASC
  orderByDesc(column)  → ORDER BY column DESC

分组:
  groupBy(column)  → GROUP BY column
  having(sql)      → HAVING sql

三、分页插件

3.1 配置分页插件

@Configuration
public class MyBatisPlusConfig {

    /**
     * 分页插件(必须配置,否则分页不生效)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件(指定数据库类型)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

3.2 分页查询

@Service
public class UserPageService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserService userService;

    /**
     * Mapper 层分页
     */
    public IPage<User> pageByMapper(int pageNum, int pageSize, String keyword) {
        // 创建分页对象(页码从1开始)
        Page<User> page = new Page<>(pageNum, pageSize);

        // 构建查询条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.hasText(keyword), User::getUserName, keyword)
               .orderByDesc(User::getCreateTime);

        // 执行分页查询
        IPage<User> result = userMapper.selectPage(page, wrapper);

        System.out.println("总记录数: " + result.getTotal());
        System.out.println("总页数: " + result.getPages());
        System.out.println("当前页: " + result.getCurrent());
        System.out.println("每页大小: " + result.getSize());
        System.out.println("当前页数据: " + result.getRecords());

        return result;
    }

    /**
     * Service 层分页
     */
    public IPage<User> pageByService(int pageNum, int pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        return userService.page(page, Wrappers.<User>lambdaQuery()
            .orderByDesc(User::getCreateTime));
    }

    /**
     * 自定义 SQL 分页(复杂查询)
     */
    public IPage<UserVO> pageWithCustomSQL(int pageNum, int pageSize) {
        Page<UserVO> page = new Page<>(pageNum, pageSize);
        return userMapper.selectUserWithOrder(page);
    }
}

自定义 SQL 分页(Mapper.xml):

// UserMapper.java
public interface UserMapper extends BaseMapper<User> {
    // 自定义分页查询(Page参数必须放第一位)
    IPage<UserVO> selectUserWithOrder(Page<UserVO> page);
}
<!-- UserMapper.xml -->
<select id="selectUserWithOrder" resultType="UserVO">
    SELECT u.id, u.user_name, u.age,
           COUNT(o.id) as order_count,
           SUM(o.amount) as total_amount
    FROM user u
    LEFT JOIN `order` o ON u.id = o.user_id
    WHERE u.deleted = 0
    GROUP BY u.id
    ORDER BY total_amount DESC
</select>

3.3 分页结果封装

// 统一分页响应对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult<T> {

    private Long total;       // 总记录数
    private Long pages;       // 总页数
    private Long current;     // 当前页
    private Long size;        // 每页大小
    private List<T> records;  // 当前页数据

    /**
     * 从 IPage 转换
     */
    public static <T> PageResult<T> of(IPage<T> page) {
        PageResult<T> result = new PageResult<>();
        result.setTotal(page.getTotal());
        result.setPages(page.getPages());
        result.setCurrent(page.getCurrent());
        result.setSize(page.getSize());
        result.setRecords(page.getRecords());
        return result;
    }

    /**
     * 转换数据类型(如 Entity → VO)
     */
    public static <T, R> PageResult<R> of(IPage<T> page, Function<T, R> converter) {
        PageResult<R> result = new PageResult<>();
        result.setTotal(page.getTotal());
        result.setPages(page.getPages());
        result.setCurrent(page.getCurrent());
        result.setSize(page.getSize());
        result.setRecords(page.getRecords().stream()
            .map(converter)
            .collect(Collectors.toList()));
        return result;
    }
}

// 使用示例
@GetMapping("/users")
public Result<PageResult<UserVO>> listUsers(
        @RequestParam(defaultValue = "1") int page,
        @RequestParam(defaultValue = "10") int size) {

    IPage<User> userPage = userService.page(new Page<>(page, size));

    // Entity 转 VO
    PageResult<UserVO> result = PageResult.of(userPage, user -> {
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        return vo;
    });

    return Result.success(result);
}

四、乐观锁

4.1 乐观锁原理

乐观锁:假设不会发生并发冲突,只在更新时检查版本号

流程:
1. 查询数据,获取 version=1
2. 修改数据
3. 更新时:UPDATE ... SET version=2 WHERE id=? AND version=1
4. 如果 version 不匹配(被其他线程修改),更新失败,返回0行

适用场景:读多写少,冲突概率低

4.2 乐观锁实现

// 1. 实体类添加 @Version 注解
@Data
@TableName("product")
public class Product {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

    private Integer stock;

    private BigDecimal price;

    @Version  // 乐观锁版本号(必须是 Integer/Long/Date/Timestamp)
    private Integer version;
}

// 2. 配置乐观锁插件(已在 MyBatisPlusConfig 中配置)

// 3. 使用
@Service
public class ProductService {

    @Autowired
    private ProductMapper productMapper;

    /**
     * 扣减库存(乐观锁)
     */
    public boolean deductStock(Long productId, Integer quantity) {
        // 查询商品(获取当前version)
        Product product = productMapper.selectById(productId);
        if (product == null || product.getStock() < quantity) {
            return false;
        }

        // 扣减库存
        product.setStock(product.getStock() - quantity);

        // 更新时自动加上 WHERE version = ? 条件
        // 如果version不匹配,updateById返回0
        int rows = productMapper.updateById(product);

        if (rows == 0) {
            // 乐观锁冲突,重试
            throw new OptimisticLockException("库存更新冲突,请重试");
        }

        return true;
    }

    /**
     * 带重试的乐观锁更新
     */
    @Retryable(value = OptimisticLockException.class, maxAttempts = 3)
    public boolean deductStockWithRetry(Long productId, Integer quantity) {
        return deductStock(productId, quantity);
    }
}

五、逻辑删除

5.1 逻辑删除配置

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted    # 逻辑删除字段名
      logic-delete-value: 1          # 删除标记值
      logic-not-delete-value: 0      # 未删除标记值
@Data
@TableName("user")
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String userName;

    @TableLogic  // 标记为逻辑删除字段
    private Integer deleted;
}

5.2 逻辑删除效果

// 执行删除(实际执行 UPDATE user SET deleted=1 WHERE id=?)
userMapper.deleteById(1L);

// 查询时自动加上 WHERE deleted=0 条件
userMapper.selectById(1L);  // SELECT * FROM user WHERE id=1 AND deleted=0

// 如果需要查询已删除的数据,需要自定义SQL
@Select("SELECT * FROM user WHERE id = #{id}")  // 不走逻辑删除过滤
User selectByIdIgnoreDeleted(Long id);

六、自动填充

6.1 配置自动填充处理器

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
     * 插入时自动填充
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        // 填充创建时间
        this.strictInsertFill(metaObject, "createTime",
            LocalDateTime.class, LocalDateTime.now());
        // 填充更新时间
        this.strictInsertFill(metaObject, "updateTime",
            LocalDateTime.class, LocalDateTime.now());
        // 填充创建人(从 SecurityContext 获取当前用户)
        this.strictInsertFill(metaObject, "createBy",
            Long.class, getCurrentUserId());
    }

    /**
     * 更新时自动填充
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        // 更新时间
        this.strictUpdateFill(metaObject, "updateTime",
            LocalDateTime.class, LocalDateTime.now());
        // 更新人
        this.strictUpdateFill(metaObject, "updateBy",
            Long.class, getCurrentUserId());
    }

    private Long getCurrentUserId() {
        // 从 Spring Security 或 ThreadLocal 获取当前用户ID
        try {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth != null && auth.getPrincipal() instanceof UserDetails) {
                return ((CustomUserDetails) auth.getPrincipal()).getUserId();
            }
        } catch (Exception e) {
            // 忽略
        }
        return null;
    }
}

6.2 实体类配置

@Data
@TableName("order")
public class Order {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String orderNo;

    private BigDecimal amount;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createBy;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateBy;

    @TableLogic
    private Integer deleted;

    @Version
    private Integer version;
}

七、代码生成器

7.1 依赖配置

<!-- 代码生成器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3.1</version>
</dependency>
<!-- 模板引擎(选一个) -->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

7.2 代码生成器配置

public class CodeGenerator {

    public static void main(String[] args) {
        FastAutoGenerator.create(
            "jdbc:mysql://localhost:3306/shop",
            "root",
            "root123"
        )
        // 全局配置
        .globalConfig(builder -> builder
            .author("开发者")
            .outputDir(System.getProperty("user.dir") + "/src/main/java")
            .commentDate("yyyy-MM-dd")
            .disableOpenDir()  // 生成后不打开目录
        )
        // 包配置
        .packageConfig(builder -> builder
            .parent("com.example")
            .moduleName("shop")
            .entity("entity")
            .mapper("mapper")
            .service("service")
            .serviceImpl("service.impl")
            .controller("controller")
            .xml("mapper")
        )
        // 策略配置
        .strategyConfig(builder -> builder
            // 指定要生成的表
            .addInclude("user", "order", "product", "category")
            // 实体类配置
            .entityBuilder()
                .enableLombok()           // 使用Lombok
                .enableTableFieldAnnotation() // 字段注解
                .logicDeleteColumnName("deleted")  // 逻辑删除字段
                .versionColumnName("version")      // 乐观锁字段
                .addTableFills(
                    new Column("create_time", FieldFill.INSERT),
                    new Column("update_time", FieldFill.INSERT_UPDATE)
                )
            // Mapper配置
            .mapperBuilder()
                .enableMapperAnnotation()  // @Mapper注解
                .enableBaseResultMap()     // 生成BaseResultMap
                .enableBaseColumnList()    // 生成BaseColumnList
            // Service配置
            .serviceBuilder()
                .formatServiceFileName("%sService")
                .formatServiceImplFileName("%sServiceImpl")
            // Controller配置
            .controllerBuilder()
                .enableRestStyle()         // @RestController
                .enableHyphenStyle()       // 驼峰转连字符(/user-info)
        )
        .execute();
    }
}

7.3 生成的代码结构

src/main/java/com/example/shop/
├── controller/
│   ├── UserController.java
│   ├── OrderController.java
│   └── ProductController.java
├── service/
│   ├── UserService.java
│   ├── OrderService.java
│   └── ProductService.java
├── service/impl/
│   ├── UserServiceImpl.java
│   ├── OrderServiceImpl.java
│   └── ProductServiceImpl.java
├── mapper/
│   ├── UserMapper.java
│   ├── OrderMapper.java
│   └── ProductMapper.java
└── entity/
    ├── User.java
    ├── Order.java
    └── Product.java

src/main/resources/mapper/
├── UserMapper.xml
├── OrderMapper.xml
└── ProductMapper.xml

八、MyBatis-Plus 高级功能

8.1 多数据源

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.6.1</version>
</dependency>
spring:
  datasource:
    dynamic:
      primary: master  # 默认主库
      strict: false    # 非严格模式
      datasource:
        master:
          url: jdbc:mysql://master:3306/shop
          username: root
          password: root123
        slave1:
          url: jdbc:mysql://slave1:3306/shop
          username: root
          password: root123
        slave2:
          url: jdbc:mysql://slave2:3306/shop
          username: root
          password: root123
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    // 默认使用主库
    @DS("master")
    public void createUser(User user) {
        userMapper.insert(user);
    }

    // 读操作使用从库(负载均衡)
    @DS("slave1")
    public User findById(Long id) {
        return userMapper.selectById(id);
    }

    // 不加注解,使用默认数据源(master)
    public void updateUser(User user) {
        userMapper.updateById(user);
    }
}

8.2 SQL 注入器(自定义全局方法)

// 自定义方法:批量插入或更新
public class InsertOrUpdateBatch extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass,
                                                   Class<?> modelClass,
                                                   TableInfo tableInfo) {
        String sql = "<script>INSERT INTO %s %s VALUES %s ON DUPLICATE KEY UPDATE %s</script>";
        // 构建SQL...
        String sqlResult = String.format(sql, tableInfo.getTableName(),
            buildColumns(tableInfo), buildValues(tableInfo), buildUpdateSet(tableInfo));

        SqlSource sqlSource = languageDriver.createSqlSource(
            configuration, sqlResult, modelClass);

        return this.addInsertMappedStatement(mapperClass, modelClass,
            "insertOrUpdateBatch", sqlSource, new NoKeyGenerator(), null, null);
    }
}

// 注册自定义方法
@Component
public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        methodList.add(new InsertOrUpdateBatch());
        return methodList;
    }
}

8.3 性能分析插件(开发环境)

@Configuration
@Profile("dev")  // 只在开发环境启用
public class DevMybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor performanceInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加性能分析插件(超过指定时间打印警告)
        // 注意:3.x版本已移除,使用p6spy代替
        return interceptor;
    }
}
<!-- 使用 p6spy 打印完整SQL(含参数) -->
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>
# 使用p6spy数据源
spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://localhost:3306/shop

九、MyBatis-Plus 面试题

9.1 高频面试题

Q1:MyBatis-Plus 的 @TableField(exist=false) 有什么用?

@Data
@TableName("user")
public class User {
    private Long id;
    private String userName;

    // 数据库中没有此字段,不参与SQL操作
    @TableField(exist = false)
    private String roleNames;  // 角色名称列表(关联查询后手动赋值)

    @TableField(exist = false)
    private List<Order> orders;  // 订单列表(关联查询)
}

Q2:如何防止全表更新/删除?

// 配置防全表更新插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    // 防止全表更新删除
    interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
    return interceptor;
}

// 执行全表更新会抛出异常
userMapper.update(user, new UpdateWrapper<>());  // 抛出 MybatisPlusException

Q3:LambdaQueryWrapper 和 QueryWrapper 有什么区别?

QueryWrapper:
- 字段名用字符串表示("user_name")
- 容易写错字段名,编译期不检查
- 适合动态字段名的场景

LambdaQueryWrapper:
- 字段名用方法引用表示(User::getUserName)
- 编译期检查,重构时自动更新
- 推荐在日常开发中使用

// QueryWrapper(不推荐)
new QueryWrapper<User>().eq("user_name", "张三")

// LambdaQueryWrapper(推荐)
new LambdaQueryWrapper<User>().eq(User::getUserName, "张三")

Q4:MyBatis-Plus 的分页插件原理是什么?

原理:
1. 拦截 Executor 的 query 方法
2. 解析原始 SQL,判断是否需要分页
3. 先执行 COUNT(*) 查询获取总数
4. 再在原始 SQL 后追加 LIMIT offset, size
5. 返回分页结果

注意事项:
- 必须配置 PaginationInnerInterceptor
- Page 对象必须作为第一个参数传入
- 自定义 SQL 分页时,Page 参数必须放第一位

十、设计模式概述

10.1 为什么要学设计模式?

面试官:你了解设计模式吗?在项目中用过哪些?

错误回答:了解,用过单例模式...(说不出具体场景)

正确回答:
"在项目中我用过多种设计模式:
1. 策略模式:实现多种支付方式(微信/支付宝/银行卡)的统一处理
2. 观察者模式:订单状态变更时通知多个下游服务
3. 建造者模式:构建复杂的查询条件对象
4. 模板方法:统一数据导入流程,子类实现具体解析逻辑
5. 责任链模式:实现请求过滤器链(权限校验、参数校验、限流)"

10.2 设计模式分类

创建型模式(5种):关注对象的创建
  - 单例模式(Singleton)
  - 工厂方法模式(Factory Method)
  - 抽象工厂模式(Abstract Factory)
  - 建造者模式(Builder)
  - 原型模式(Prototype)

结构型模式(7种):关注类和对象的组合
  - 代理模式(Proxy)
  - 装饰器模式(Decorator)
  - 适配器模式(Adapter)
  - 门面模式(Facade)
  - 桥接模式(Bridge)
  - 组合模式(Composite)
  - 享元模式(Flyweight)

行为型模式(11种):关注对象间的通信
  - 观察者模式(Observer)
  - 策略模式(Strategy)
  - 模板方法模式(Template Method)
  - 责任链模式(Chain of Responsibility)
  - 命令模式(Command)
  - 迭代器模式(Iterator)
  - 中介者模式(Mediator)
  - 备忘录模式(Memento)
  - 状态模式(State)
  - 访问者模式(Visitor)
  - 解释器模式(Interpreter)

十一、创建型模式

11.1 单例模式(Singleton)

核心思想: 保证一个类只有一个实例,并提供全局访问点。

应用场景: 配置管理器、连接池、线程池、日志对象

饿汉式(线程安全,推荐)
public class ConfigManager {

    // 类加载时就创建实例(饿汉式)
    private static final ConfigManager INSTANCE = new ConfigManager();

    private Map<String, String> configs = new HashMap<>();

    // 私有构造方法,防止外部创建
    private ConfigManager() {
        // 加载配置
        configs.put("app.name", "MyApp");
        configs.put("app.version", "1.0.0");
    }

    // 全局访问点
    public static ConfigManager getInstance() {
        return INSTANCE;
    }

    public String get(String key) {
        return configs.get(key);
    }
}

// 使用
ConfigManager config = ConfigManager.getInstance();
String appName = config.get("app.name");
懒汉式(双重检查锁,线程安全)
public class DatabaseConnectionPool {

    // volatile 防止指令重排序
    private static volatile DatabaseConnectionPool instance;

    private List<Connection> pool = new ArrayList<>();

    private DatabaseConnectionPool() {
        // 初始化连接池
        for (int i = 0; i < 10; i++) {
            pool.add(createConnection());
        }
    }

    public static DatabaseConnectionPool getInstance() {
        if (instance == null) {                    // 第一次检查(不加锁,提高性能)
            synchronized (DatabaseConnectionPool.class) {
                if (instance == null) {            // 第二次检查(加锁后再检查)
                    instance = new DatabaseConnectionPool();
                }
            }
        }
        return instance;
    }

    private Connection createConnection() {
        // 创建数据库连接
        return null;
    }
}
枚举单例(最简洁,推荐)
public enum AppContext {

    INSTANCE;

    private final Map<String, Object> context = new ConcurrentHashMap<>();

    public void set(String key, Object value) {
        context.put(key, value);
    }

    public Object get(String key) {
        return context.get(key);
    }
}

// 使用
AppContext.INSTANCE.set("userId", 123L);
Long userId = (Long) AppContext.INSTANCE.get("userId");

11.2 工厂模式(Factory)

核心思想: 将对象的创建和使用分离,通过工厂方法创建对象。

应用场景: 支付方式选择、日志框架、数据库驱动

简单工厂
// 支付方式接口
public interface PaymentStrategy {
    PayResult pay(PayRequest request);
    String getType();
}

// 微信支付
@Component
public class WechatPayment implements PaymentStrategy {
    @Override
    public PayResult pay(PayRequest request) {
        // 调用微信支付API
        return PayResult.success("微信支付成功");
    }

    @Override
    public String getType() { return "WECHAT"; }
}

// 支付宝支付
@Component
public class AlipayPayment implements PaymentStrategy {
    @Override
    public PayResult pay(PayRequest request) {
        // 调用支付宝API
        return PayResult.success("支付宝支付成功");
    }

    @Override
    public String getType() { return "ALIPAY"; }
}

// 支付工厂(利用Spring自动注入所有实现)
@Component
public class PaymentFactory {

    private final Map<String, PaymentStrategy> strategyMap;

    // Spring 自动注入所有 PaymentStrategy 实现
    public PaymentFactory(List<PaymentStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(PaymentStrategy::getType, s -> s));
    }

    public PaymentStrategy getStrategy(String payType) {
        PaymentStrategy strategy = strategyMap.get(payType);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付方式: " + payType);
        }
        return strategy;
    }
}

// 使用
@Service
public class OrderPayService {

    @Autowired
    private PaymentFactory paymentFactory;

    public PayResult pay(String payType, PayRequest request) {
        PaymentStrategy strategy = paymentFactory.getStrategy(payType);
        return strategy.pay(request);
    }
}

11.3 建造者模式(Builder)

核心思想: 将复杂对象的构建过程分步骤进行,最终构建出完整对象。

应用场景: 构建复杂查询条件、HTTP请求构建、SQL构建

// 查询条件建造者
@Data
@Builder
public class UserQueryCondition {

    private String userName;
    private Integer minAge;
    private Integer maxAge;
    private String email;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private String orderBy;
    private Boolean orderAsc;
    private Integer pageNum;
    private Integer pageSize;

    // 转换为 LambdaQueryWrapper
    public LambdaQueryWrapper<User> toWrapper() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.hasText(userName), User::getUserName, userName)
               .ge(minAge != null, User::getAge, minAge)
               .le(maxAge != null, User::getAge, maxAge)
               .like(StringUtils.hasText(email), User::getEmail, email)
               .ge(startTime != null, User::getCreateTime, startTime)
               .le(endTime != null, User::getCreateTime, endTime);

        if (StringUtils.hasText(orderBy)) {
            if (Boolean.TRUE.equals(orderAsc)) {
                wrapper.orderByAsc(User::getCreateTime);
            } else {
                wrapper.orderByDesc(User::getCreateTime);
            }
        }

        return wrapper;
    }
}

// 使用 Lombok @Builder
UserQueryCondition condition = UserQueryCondition.builder()
    .userName("张")
    .minAge(18)
    .maxAge(35)
    .orderBy("createTime")
    .orderAsc(false)
    .pageNum(1)
    .pageSize(20)
    .build();

LambdaQueryWrapper<User> wrapper = condition.toWrapper();
// 自定义建造者(不使用Lombok)
public class HttpRequest {

    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final String body;
    private final int timeout;

    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = Collections.unmodifiableMap(builder.headers);
        this.body = builder.body;
        this.timeout = builder.timeout;
    }

    public static class Builder {
        private String url;
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private String body;
        private int timeout = 5000;

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder method(String method) {
            this.method = method;
            return this;
        }

        public Builder header(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public Builder body(String body) {
            this.body = body;
            return this;
        }

        public Builder timeout(int timeout) {
            this.timeout = timeout;
            return this;
        }

        public HttpRequest build() {
            if (url == null || url.isEmpty()) {
                throw new IllegalStateException("URL不能为空");
            }
            return new HttpRequest(this);
        }
    }
}

// 使用
HttpRequest request = new HttpRequest.Builder()
    .url("https://api.example.com/users")
    .method("POST")
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer token123")
    .body("{\"name\":\"张三\"}")
    .timeout(3000)
    .build();

11.4 原型模式(Prototype)

核心思想: 通过复制已有对象来创建新对象,避免重复初始化。

// 实现 Cloneable 接口
@Data
public class OrderTemplate implements Cloneable {

    private String templateName;
    private List<OrderItem> items;
    private BigDecimal discount;
    private String remark;

    /**
     * 浅拷贝(基本类型和String是值拷贝,引用类型共享)
     */
    @Override
    public OrderTemplate clone() {
        try {
            return (OrderTemplate) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 深拷贝(所有字段都是独立的)
     */
    public OrderTemplate deepClone() {
        OrderTemplate clone = new OrderTemplate();
        clone.setTemplateName(this.templateName);
        clone.setDiscount(this.discount);
        clone.setRemark(this.remark);
        // 深拷贝集合
        if (this.items != null) {
            clone.setItems(this.items.stream()
                .map(item -> {
                    OrderItem newItem = new OrderItem();
                    BeanUtils.copyProperties(item, newItem);
                    return newItem;
                })
                .collect(Collectors.toList()));
        }
        return clone;
    }
}

// 使用场景:基于模板快速创建订单
@Service
public class OrderTemplateService {

    private Map<String, OrderTemplate> templates = new HashMap<>();

    public void registerTemplate(OrderTemplate template) {
        templates.put(template.getTemplateName(), template);
    }

    /**
     * 基于模板创建订单(原型模式)
     */
    public Order createFromTemplate(String templateName, Long userId) {
        OrderTemplate template = templates.get(templateName);
        if (template == null) {
            throw new IllegalArgumentException("模板不存在: " + templateName);
        }

        // 深拷贝模板,避免修改影响原模板
        OrderTemplate copy = template.deepClone();

        // 基于拷贝创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setItems(copy.getItems());
        order.setDiscount(copy.getDiscount());
        return order;
    }
}

十二、结构型模式

12.1 代理模式(Proxy)

核心思想: 为目标对象提供代理,控制对目标对象的访问,可以在访问前后添加额外逻辑。

应用场景: Spring AOP、MyBatis Mapper、事务管理、权限控制

静态代理
// 目标接口
public interface UserService {
    User findById(Long id);
    void save(User user);
}

// 目标实现
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User findById(Long id) {
        // 查询数据库
        return new User();
    }

    @Override
    public void save(User user) {
        // 保存到数据库
    }
}

// 静态代理(添加日志功能)
public class UserServiceLogProxy implements UserService {

    private final UserService target;

    public UserServiceLogProxy(UserService target) {
        this.target = target;
    }

    @Override
    public User findById(Long id) {
        long start = System.currentTimeMillis();
        log.info("调用 findById, id={}", id);
        try {
            User result = target.findById(id);
            log.info("findById 成功, 耗时={}ms", System.currentTimeMillis() - start);
            return result;
        } catch (Exception e) {
            log.error("findById 失败", e);
            throw e;
        }
    }

    @Override
    public void save(User user) {
        log.info("调用 save, user={}", user);
        target.save(user);
    }
}
JDK 动态代理
// JDK动态代理(目标类必须实现接口)
public class LogInvocationHandler implements InvocationHandler {

    private final Object target;

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        log.info("调用方法: {}, 参数: {}", method.getName(), Arrays.toString(args));

        try {
            Object result = method.invoke(target, args);
            log.info("方法 {} 执行成功, 耗时={}ms",
                    method.getName(), System.currentTimeMillis() - start);
            return result;
        } catch (InvocationTargetException e) {
            log.error("方法 {} 执行失败", method.getName(), e.getCause());
            throw e.getCause();
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new LogInvocationHandler(target)
        );
    }
}

// 使用
UserService userService = new UserServiceImpl();
UserService proxy = LogInvocationHandler.createProxy(userService);
proxy.findById(1L);  // 自动记录日志
Spring AOP(最常用)
// 使用 Spring AOP 实现日志切面
@Aspect
@Component
@Slf4j
public class LogAspect {

    /**
     * 切点:所有 Controller 方法
     */
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerMethods() {}

    /**
     * 环绕通知:记录请求日志
     */
    @Around("controllerMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        log.info("请求开始: {}, 参数: {}", methodName, Arrays.toString(args));
        long start = System.currentTimeMillis();

        try {
            Object result = joinPoint.proceed();
            log.info("请求成功: {}, 耗时: {}ms", methodName,
                    System.currentTimeMillis() - start);
            return result;
        } catch (Exception e) {
            log.error("请求失败: {}", methodName, e);
            throw e;
        }
    }

    /**
     * 自定义注解切点:记录操作日志
     */
    @Around("@annotation(operationLog)")
    public Object logOperation(ProceedingJoinPoint joinPoint,
                                OperationLog operationLog) throws Throwable {
        // 记录操作日志到数据库
        OperationLogRecord record = new OperationLogRecord();
        record.setOperation(operationLog.value());
        record.setOperator(getCurrentUser());
        record.setOperateTime(LocalDateTime.now());

        try {
            Object result = joinPoint.proceed();
            record.setSuccess(true);
            return result;
        } catch (Exception e) {
            record.setSuccess(false);
            record.setErrorMsg(e.getMessage());
            throw e;
        } finally {
            operationLogService.save(record);
        }
    }
}

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
    String value() default "";
}

// 使用
@PostMapping("/users")
@OperationLog("创建用户")
public Result<User> createUser(@RequestBody UserCreateDTO dto) {
    return Result.success(userService.create(dto));
}

12.2 装饰器模式(Decorator)

核心思想: 动态地给对象添加额外功能,比继承更灵活。

应用场景: Java IO流、Spring Security过滤器链、缓存装饰

// 数据源装饰器(添加缓存功能)
public interface UserRepository {
    User findById(Long id);
    void save(User user);
}

// 基础实现(数据库)
@Repository
public class UserDatabaseRepository implements UserRepository {
    @Autowired
    private UserMapper userMapper;

    @Override
    public User findById(Long id) {
        return userMapper.selectById(id);
    }

    @Override
    public void save(User user) {
        userMapper.insertOrUpdate(user);
    }
}

// 缓存装饰器
public class UserCacheDecorator implements UserRepository {

    private final UserRepository delegate;  // 被装饰的对象
    private final RedisTemplate<String, User> redisTemplate;
    private static final long CACHE_TTL = 300;

    public UserCacheDecorator(UserRepository delegate,
                               RedisTemplate<String, User> redisTemplate) {
        this.delegate = delegate;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public User findById(Long id) {
        String key = "user:" + id;
        // 先查缓存
        User cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return cached;
        }
        // 缓存未命中,查数据库
        User user = delegate.findById(id);
        if (user != null) {
            redisTemplate.opsForValue().set(key, user, CACHE_TTL, TimeUnit.SECONDS);
        }
        return user;
    }

    @Override
    public void save(User user) {
        delegate.save(user);
        // 更新缓存
        String key = "user:" + user.getId();
        redisTemplate.opsForValue().set(key, user, CACHE_TTL, TimeUnit.SECONDS);
    }
}

// 配置(组合装饰器)
@Configuration
public class RepositoryConfig {

    @Bean
    public UserRepository userRepository(UserDatabaseRepository dbRepo,
                                          RedisTemplate<String, User> redisTemplate) {
        // 用缓存装饰器包装数据库实现
        return new UserCacheDecorator(dbRepo, redisTemplate);
    }
}

12.3 适配器模式(Adapter)

核心思想: 将不兼容的接口转换为兼容的接口,让原本不能一起工作的类可以协作。

// 场景:系统需要对接多个第三方短信服务商,但接口各不相同

// 统一短信接口
public interface SmsService {
    boolean send(String phone, String content);
}

// 阿里云短信SDK(第三方,无法修改)
public class AliyunSmsClient {
    public void sendSms(String mobile, String templateCode, Map<String, String> params) {
        // 阿里云短信发送逻辑
    }
}

// 腾讯云短信SDK(第三方,无法修改)
public class TencentSmsClient {
    public int sendMessage(String[] phoneNumbers, String templateId, String[] templateParams) {
        // 腾讯云短信发送逻辑
        return 0;
    }
}

// 阿里云适配器
@Component("aliyunSms")
public class AliyunSmsAdapter implements SmsService {

    private final AliyunSmsClient client = new AliyunSmsClient();

    @Override
    public boolean send(String phone, String content) {
        try {
            Map<String, String> params = new HashMap<>();
            params.put("content", content);
            client.sendSms(phone, "SMS_TEMPLATE_001", params);
            return true;
        } catch (Exception e) {
            log.error("阿里云短信发送失败", e);
            return false;
        }
    }
}

// 腾讯云适配器
@Component("tencentSms")
public class TencentSmsAdapter implements SmsService {

    private final TencentSmsClient client = new TencentSmsClient();

    @Override
    public boolean send(String phone, String content) {
        int result = client.sendMessage(
            new String[]{phone},
            "TENCENT_TEMPLATE_001",
            new String[]{content}
        );
        return result == 0;
    }
}

// 使用(通过配置切换短信服务商)
@Service
public class NotificationService {

    @Autowired
    @Qualifier("aliyunSms")  // 通过配置切换
    private SmsService smsService;

    public void sendVerifyCode(String phone, String code) {
        smsService.send(phone, "您的验证码是:" + code + ",5分钟内有效");
    }
}

12.4 门面模式(Facade)

核心思想: 为复杂子系统提供简单的统一接口。

// 场景:用户注册涉及多个子系统

// 子系统1:用户服务
@Service
public class UserDomainService {
    public User createUser(String username, String password) { return new User(); }
    public void sendWelcomeEmail(String email) { }
}

// 子系统2:积分服务
@Service
public class PointService {
    public void initUserPoints(Long userId) { }
}

// 子系统3:消息服务
@Service
public class MessageService {
    public void sendRegisterNotify(Long userId) { }
}

// 门面:统一注册流程
@Service
@Slf4j
public class UserRegistrationFacade {

    @Autowired
    private UserDomainService userDomainService;

    @Autowired
    private PointService pointService;

    @Autowired
    private MessageService messageService;

    /**
     * 用户注册门面方法
     * 调用方只需调用这一个方法,无需了解内部细节
     */
    @Transactional
    public User register(RegisterDTO dto) {
        log.info("开始用户注册流程, username={}", dto.getUsername());

        // 1. 创建用户
        User user = userDomainService.createUser(dto.getUsername(), dto.getPassword());

        // 2. 初始化积分
        pointService.initUserPoints(user.getId());

        // 3. 发送欢迎邮件
        userDomainService.sendWelcomeEmail(dto.getEmail());

        // 4. 发送注册通知
        messageService.sendRegisterNotify(user.getId());

        log.info("用户注册完成, userId={}", user.getId());
        return user;
    }
}

十三、行为型模式

13.1 观察者模式(Observer)

核心思想: 定义对象间的一对多依赖,当一个对象状态改变时,所有依赖它的对象都会收到通知。

应用场景: 事件驱动、消息通知、Spring ApplicationEvent

// 方案1:Spring ApplicationEvent(推荐)

// 定义事件
public class OrderCreatedEvent extends ApplicationEvent {

    private final Order order;

    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }

    public Order getOrder() { return order; }
}

// 发布事件
@Service
public class OrderService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Transactional
    public Order createOrder(OrderCreateDTO dto) {
        Order order = buildOrder(dto);
        orderMapper.insert(order);

        // 发布订单创建事件
        eventPublisher.publishEvent(new OrderCreatedEvent(this, order));

        return order;
    }
}

// 监听器1:发送短信
@Component
@Slf4j
public class OrderSmsListener {

    @EventListener
    @Async  // 异步处理
    public void onOrderCreated(OrderCreatedEvent event) {
        Order order = event.getOrder();
        log.info("发送订单短信, orderId={}", order.getId());
        // 发送短信逻辑
    }
}

// 监听器2:更新库存
@Component
@Slf4j
public class OrderStockListener {

    @EventListener
    @Async
    public void onOrderCreated(OrderCreatedEvent event) {
        Order order = event.getOrder();
        log.info("更新库存, orderId={}", order.getId());
        // 扣减库存逻辑
    }
}

// 监听器3:记录日志
@Component
public class OrderLogListener {

    @EventListener
    @Async
    public void onOrderCreated(OrderCreatedEvent event) {
        // 记录操作日志
    }
}
// 方案2:手动实现观察者模式
public interface OrderObserver {
    void onOrderCreated(Order order);
}

@Component
public class OrderEventManager {

    private final List<OrderObserver> observers = new CopyOnWriteArrayList<>();

    public void addObserver(OrderObserver observer) {
        observers.add(observer);
    }

    public void removeObserver(OrderObserver observer) {
        observers.remove(observer);
    }

    public void notifyOrderCreated(Order order) {
        observers.forEach(observer -> {
            try {
                observer.onOrderCreated(order);
            } catch (Exception e) {
                log.error("观察者处理失败: {}", observer.getClass().getSimpleName(), e);
            }
        });
    }
}

13.2 策略模式(Strategy)

核心思想: 定义一系列算法,将每个算法封装起来,使它们可以互相替换。

应用场景: 支付方式、排序算法、促销策略、数据导出格式

// 场景:电商促销策略

// 促销策略接口
public interface PromotionStrategy {
    BigDecimal calculate(BigDecimal originalPrice, Order order);
    String getStrategyCode();
}

// 满减策略
@Component
public class FullReductionStrategy implements PromotionStrategy {

    @Override
    public BigDecimal calculate(BigDecimal originalPrice, Order order) {
        // 满100减20
        if (originalPrice.compareTo(new BigDecimal("100")) >= 0) {
            return originalPrice.subtract(new BigDecimal("20"));
        }
        return originalPrice;
    }

    @Override
    public String getStrategyCode() { return "FULL_REDUCTION"; }
}

// 折扣策略
@Component
public class DiscountStrategy implements PromotionStrategy {

    @Override
    public BigDecimal calculate(BigDecimal originalPrice, Order order) {
        // 9折
        return originalPrice.multiply(new BigDecimal("0.9"))
                            .setScale(2, RoundingMode.HALF_UP);
    }

    @Override
    public String getStrategyCode() { return "DISCOUNT"; }
}

// 优惠券策略
@Component
public class CouponStrategy implements PromotionStrategy {

    @Override
    public BigDecimal calculate(BigDecimal originalPrice, Order order) {
        // 使用优惠券减免
        BigDecimal couponAmount = getCouponAmount(order.getCouponCode());
        return originalPrice.subtract(couponAmount).max(BigDecimal.ZERO);
    }

    @Override
    public String getStrategyCode() { return "COUPON"; }

    private BigDecimal getCouponAmount(String couponCode) {
        // 查询优惠券面额
        return new BigDecimal("50");
    }
}

// 策略上下文
@Component
public class PromotionContext {

    private final Map<String, PromotionStrategy> strategyMap;

    public PromotionContext(List<PromotionStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(PromotionStrategy::getStrategyCode, s -> s));
    }

    public BigDecimal applyPromotion(String strategyCode,
                                      BigDecimal price, Order order) {
        PromotionStrategy strategy = strategyMap.get(strategyCode);
        if (strategy == null) {
            return price;  // 无促销,原价
        }
        return strategy.calculate(price, order);
    }
}

// 使用
@Service
public class OrderPriceService {

    @Autowired
    private PromotionContext promotionContext;

    public BigDecimal calculateFinalPrice(Order order) {
        BigDecimal originalPrice = order.getTotalAmount();
        String promotionCode = order.getPromotionCode();
        return promotionContext.applyPromotion(promotionCode, originalPrice, order);
    }
}

13.3 模板方法模式(Template Method)

核心思想: 在父类中定义算法骨架,将某些步骤延迟到子类实现。

应用场景: 数据导入导出、报表生成、Spring JdbcTemplate

// 场景:数据导入流程(Excel/CSV/JSON格式)

// 抽象模板
public abstract class DataImportTemplate<T> {

    /**
     * 模板方法:定义导入流程(final防止子类覆盖)
     */
    public final ImportResult importData(InputStream inputStream) {
        ImportResult result = new ImportResult();

        try {
            // 1. 读取数据(子类实现)
            List<T> rawData = readData(inputStream);
            result.setTotalCount(rawData.size());

            // 2. 数据校验(子类可覆盖)
            List<T> validData = validateData(rawData, result);

            // 3. 数据转换(子类实现)
            List<?> entities = convertData(validData);

            // 4. 保存数据(通用逻辑)
            saveData(entities);
            result.setSuccessCount(entities.size());

            // 5. 后置处理(子类可覆盖,默认空实现)
            afterImport(result);

        } catch (Exception e) {
            result.setErrorMsg(e.getMessage());
        }

        return result;
    }

    // 抽象方法:子类必须实现
    protected abstract List<T> readData(InputStream inputStream) throws Exception;
    protected abstract List<?> convertData(List<T> rawData);

    // 钩子方法:子类可选择覆盖
    protected List<T> validateData(List<T> rawData, ImportResult result) {
        return rawData;  // 默认不校验
    }

    protected void afterImport(ImportResult result) {
        // 默认空实现
    }

    // 通用方法
    private void saveData(List<?> entities) {
        // 批量保存到数据库
    }
}

// Excel导入实现
@Component
public class ExcelUserImport extends DataImportTemplate<ExcelRow> {

    @Override
    protected List<ExcelRow> readData(InputStream inputStream) throws Exception {
        // 使用EasyExcel读取
        List<ExcelRow> rows = new ArrayList<>();
        EasyExcel.read(inputStream, ExcelRow.class,
            new PageReadListener<ExcelRow>(rows::addAll)).sheet().doRead();
        return rows;
    }

    @Override
    protected List<ExcelRow> validateData(List<ExcelRow> rawData, ImportResult result) {
        List<ExcelRow> valid = new ArrayList<>();
        for (ExcelRow row : rawData) {
            if (StringUtils.hasText(row.getUserName()) && row.getAge() != null) {
                valid.add(row);
            } else {
                result.addError("第" + row.getRowNum() + "行数据不完整");
            }
        }
        return valid;
    }

    @Override
    protected List<User> convertData(List<ExcelRow> rawData) {
        return rawData.stream().map(row -> {
            User user = new User();
            user.setUserName(row.getUserName());
            user.setAge(row.getAge());
            user.setEmail(row.getEmail());
            return user;
        }).collect(Collectors.toList());
    }

    @Override
    protected void afterImport(ImportResult result) {
        log.info("Excel导入完成: 总数={}, 成功={}, 失败={}",
                result.getTotalCount(), result.getSuccessCount(),
                result.getErrorCount());
    }
}

13.4 责任链模式(Chain of Responsibility)

核心思想: 将请求沿着处理者链传递,每个处理者决定是否处理请求或传递给下一个。

应用场景: 请求过滤器、审批流程、权限校验链

// 场景:订单创建前的校验链

// 处理器接口
public abstract class OrderValidator {

    protected OrderValidator next;

    public OrderValidator setNext(OrderValidator next) {
        this.next = next;
        return next;  // 返回next,支持链式调用
    }

    /**
     * 校验方法
     * @return null表示校验通过,非null表示校验失败的错误信息
     */
    public abstract String validate(OrderCreateDTO dto);

    /**
     * 执行校验并传递给下一个
     */
    protected String doValidate(OrderCreateDTO dto) {
        String error = validate(dto);
        if (error != null) {
            return error;  // 校验失败,不继续传递
        }
        if (next != null) {
            return next.doValidate(dto);  // 传递给下一个
        }
        return null;  // 所有校验通过
    }
}

// 用户状态校验
@Component
public class UserStatusValidator extends OrderValidator {

    @Autowired
    private UserService userService;

    @Override
    public String validate(OrderCreateDTO dto) {
        User user = userService.findById(dto.getUserId());
        if (user == null) {
            return "用户不存在";
        }
        if (user.getStatus() != UserStatus.ACTIVE) {
            return "用户账号已被禁用";
        }
        return null;
    }
}

// 库存校验
@Component
public class StockValidator extends OrderValidator {

    @Autowired
    private ProductService productService;

    @Override
    public String validate(OrderCreateDTO dto) {
        for (OrderItemDTO item : dto.getItems()) {
            Product product = productService.findById(item.getProductId());
            if (product == null) {
                return "商品不存在: " + item.getProductId();
            }
            if (product.getStock() < item.getQuantity()) {
                return "商品库存不足: " + product.getName();
            }
        }
        return null;
    }
}

// 金额校验
@Component
public class AmountValidator extends OrderValidator {

    @Override
    public String validate(OrderCreateDTO dto) {
        if (dto.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
            return "订单金额必须大于0";
        }
        if (dto.getTotalAmount().compareTo(new BigDecimal("100000")) > 0) {
            return "单笔订单金额不能超过10万";
        }
        return null;
    }
}

// 构建校验链
@Configuration
public class ValidatorChainConfig {

    @Bean
    public OrderValidator orderValidatorChain(
            UserStatusValidator userValidator,
            StockValidator stockValidator,
            AmountValidator amountValidator) {

        // 构建链:用户状态 → 库存 → 金额
        userValidator.setNext(stockValidator).setNext(amountValidator);
        return userValidator;
    }
}

// 使用
@Service
public class OrderCreateService {

    @Autowired
    private OrderValidator orderValidatorChain;

    public Order createOrder(OrderCreateDTO dto) {
        // 执行校验链
        String error = orderValidatorChain.doValidate(dto);
        if (error != null) {
            throw new BusinessException(error);
        }
        // 创建订单...
        return new Order();
    }
}

13.5 命令模式(Command)

核心思想: 将请求封装为对象,支持撤销、重做、队列等操作。

// 场景:购物车操作(支持撤销)

public interface CartCommand {
    void execute();
    void undo();
}

// 添加商品命令
public class AddItemCommand implements CartCommand {

    private final Cart cart;
    private final CartItem item;

    public AddItemCommand(Cart cart, CartItem item) {
        this.cart = cart;
        this.item = item;
    }

    @Override
    public void execute() {
        cart.addItem(item);
    }

    @Override
    public void undo() {
        cart.removeItem(item.getProductId());
    }
}

// 命令执行器(支持撤销)
@Component
public class CartCommandExecutor {

    private final Deque<CartCommand> history = new ArrayDeque<>();

    public void execute(CartCommand command) {
        command.execute();
        history.push(command);
    }

    public void undo() {
        if (!history.isEmpty()) {
            CartCommand command = history.pop();
            command.undo();
        }
    }
}

十四、框架源码中的设计模式

14.1 Spring 框架中的设计模式

Spring 框架大量使用设计模式,面试必考:

1. 单例模式
   - Spring Bean 默认是单例(scope=singleton)
   - ApplicationContext 本身是单例

2. 工厂模式
   - BeanFactory:Bean工厂,创建和管理Bean
   - FactoryBean:特殊Bean,用于创建复杂对象(如SqlSessionFactoryBean)

3. 代理模式
   - Spring AOP:JDK动态代理(接口)+ CGLIB代理(类)
   - @Transactional:事务代理

4. 观察者模式
   - ApplicationEvent + ApplicationListener
   - @EventListener 注解

5. 模板方法模式
   - JdbcTemplate、RestTemplate、RedisTemplate
   - AbstractApplicationContext.refresh()

6. 装饰器模式
   - BeanWrapper:包装Bean,提供属性访问
   - HttpServletRequestWrapper:包装请求

7. 责任链模式
   - Spring Security FilterChain
   - Spring MVC HandlerInterceptor链

8. 策略模式
   - Resource接口(ClassPathResource/FileSystemResource/UrlResource)
   - InstantiationStrategy(Bean实例化策略)

9. 建造者模式
   - BeanDefinitionBuilder
   - UriComponentsBuilder

10. 适配器模式
    - HandlerAdapter(适配不同类型的Handler)
    - MessageConverter(适配不同消息格式)

14.2 MyBatis 框架中的设计模式

1. 建造者模式
   - SqlSessionFactoryBuilder
   - XMLConfigBuilder、XMLMapperBuilder

2. 工厂模式
   - SqlSessionFactory:创建SqlSession
   - MapperProxyFactory:创建Mapper代理

3. 代理模式
   - MapperProxy:Mapper接口的JDK动态代理
   - 拦截器(Interceptor):插件机制

4. 模板方法模式
   - BaseExecutor:定义执行流程
   - SimpleExecutor/ReuseExecutor/BatchExecutor:具体实现

5. 装饰器模式
   - CachingExecutor:为Executor添加缓存功能

6. 责任链模式
   - InterceptorChain:插件拦截器链

7. 单例模式
   - Configuration:全局配置单例

8. 策略模式
   - TypeHandler:不同类型的处理策略
   - KeyGenerator:主键生成策略

14.3 代码示例:Spring AOP 代理原理

// Spring AOP 选择代理方式的逻辑
public class AopProxyFactory {

    public AopProxy createAopProxy(AdvisedSupport config) {
        // 如果目标类实现了接口,使用JDK动态代理
        if (config.isOptimize() || config.isProxyTargetClass()
                || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);  // JDK代理
            }
            return new ObjenesisCglibAopProxy(config);  // CGLIB代理
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
}

// MyBatis MapperProxy 原理
public class MapperProxy<T> implements InvocationHandler {

    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 如果是Object的方法(toString/hashCode等),直接调用
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        // 否则执行SQL
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
}

十五、设计模式高频面试题

15.1 面试题汇总

Q1:单例模式有哪几种实现方式?各有什么优缺点?

1. 饿汉式(推荐)
   优点:线程安全,实现简单
   缺点:类加载时就创建,可能浪费内存

2. 懒汉式(双重检查锁)
   优点:延迟加载,节省内存
   缺点:实现复杂,需要volatile

3. 静态内部类
   优点:延迟加载 + 线程安全,推荐
   缺点:无法传参

4. 枚举(最推荐)
   优点:线程安全,防止反序列化破坏单例
   缺点:不能延迟加载

// 静态内部类实现
public class Singleton {
    private Singleton() {}

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

Q2:工厂模式和抽象工厂模式有什么区别?

工厂方法模式:
- 一个工厂只生产一种产品
- 通过子类决定创建哪种产品
- 适合产品种类单一的场景

抽象工厂模式:
- 一个工厂生产一族相关产品
- 保证产品族的一致性
- 适合产品族的场景

示例:
工厂方法:SmsFactory → 创建短信服务(阿里云/腾讯云)
抽象工厂:CloudFactory → 创建一套云服务(短信+存储+推送)
  AliyunFactory → AliyunSms + AliyunOSS + AliyunPush
  TencentFactory → TencentSms + TencentCOS + TencentPush

Q3:代理模式和装饰器模式有什么区别?

相同点:
- 都是包装目标对象
- 都实现相同的接口

不同点:
代理模式:
- 控制对目标对象的访问
- 代理类通常自己创建目标对象
- 关注访问控制(权限、日志、事务)

装饰器模式:
- 动态添加功能
- 装饰器通过构造方法接收目标对象
- 关注功能增强(缓存、加密、压缩)

// 代理模式:代理自己创建目标
UserService proxy = new UserServiceProxy();  // 内部创建真实对象

// 装饰器模式:外部传入目标
UserRepository cached = new CacheDecorator(new DatabaseRepository());

Q4:策略模式和工厂模式有什么关系?

策略模式:关注算法的选择和切换
工厂模式:关注对象的创建

在实际项目中,两者经常配合使用:
- 工厂模式负责创建策略对象
- 策略模式负责执行具体算法

// 工厂 + 策略组合
@Component
public class PaymentFactory {
    private Map<String, PaymentStrategy> strategies;

    // 工厂:根据类型获取策略
    public PaymentStrategy getStrategy(String type) {
        return strategies.get(type);
    }
}

// 使用
PaymentStrategy strategy = factory.getStrategy("WECHAT");  // 工厂获取
strategy.pay(request);  // 策略执行

Q5:Spring 中用到了哪些设计模式?

必须能说出至少5个,并举例:

1. 单例模式:Bean默认单例
2. 工厂模式:BeanFactory创建Bean
3. 代理模式:AOP使用JDK/CGLIB代理
4. 观察者模式:ApplicationEvent事件机制
5. 模板方法:JdbcTemplate、RestTemplate
6. 装饰器模式:BeanWrapper
7. 责任链模式:Spring Security过滤器链
8. 策略模式:Resource接口多种实现

十六、综合实战:电商系统设计模式应用

16.1 支付系统(策略 + 工厂)

// 完整的支付系统设计

// 1. 支付策略接口
public interface PayStrategy {
    PayResult pay(PayContext context);
    RefundResult refund(RefundContext context);
    String getPayType();
}

// 2. 微信支付实现
@Component
public class WechatPayStrategy implements PayStrategy {

    @Autowired
    private WechatPayConfig config;

    @Override
    public PayResult pay(PayContext context) {
        // 构建微信支付请求
        WechatPayRequest request = WechatPayRequest.builder()
            .appId(config.getAppId())
            .mchId(config.getMchId())
            .outTradeNo(context.getOrderNo())
            .totalFee(context.getAmount().multiply(new BigDecimal("100")).intValue())
            .body(context.getDescription())
            .notifyUrl(config.getNotifyUrl())
            .build();

        // 调用微信支付API
        WechatPayResponse response = wechatPayClient.unifiedOrder(request);

        return PayResult.builder()
            .success(response.isSuccess())
            .payUrl(response.getCodeUrl())
            .tradeNo(response.getPrepayId())
            .build();
    }

    @Override
    public RefundResult refund(RefundContext context) {
        // 退款逻辑
        return RefundResult.success();
    }

    @Override
    public String getPayType() { return "WECHAT"; }
}

// 3. 支付工厂
@Component
public class PayStrategyFactory {

    private final Map<String, PayStrategy> strategyMap;

    public PayStrategyFactory(List<PayStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(PayStrategy::getPayType, s -> s));
    }

    public PayStrategy getStrategy(String payType) {
        PayStrategy strategy = strategyMap.get(payType.toUpperCase());
        if (strategy == null) {
            throw new BusinessException("不支持的支付方式: " + payType);
        }
        return strategy;
    }
}

// 4. 支付服务(门面模式)
@Service
@Slf4j
public class PaymentFacade {

    @Autowired
    private PayStrategyFactory strategyFactory;

    @Autowired
    private OrderService orderService;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    /**
     * 统一支付入口
     */
    @Transactional
    public PayResult pay(String orderNo, String payType) {
        // 1. 查询订单
        Order order = orderService.findByOrderNo(orderNo);
        validateOrder(order);

        // 2. 获取支付策略
        PayStrategy strategy = strategyFactory.getStrategy(payType);

        // 3. 执行支付
        PayContext context = buildPayContext(order, payType);
        PayResult result = strategy.pay(context);

        // 4. 更新订单状态
        if (result.isSuccess()) {
            orderService.updatePayStatus(orderNo, PayStatus.PAYING);
            // 发布支付事件(观察者模式)
            eventPublisher.publishEvent(new PayInitiatedEvent(this, order, result));
        }

        return result;
    }

    private void validateOrder(Order order) {
        if (order == null) throw new BusinessException("订单不存在");
        if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {
            throw new BusinessException("订单状态不允许支付");
        }
    }
}

16.2 审批流程(责任链 + 状态机)

// 费用报销审批流程

public abstract class ApprovalHandler {

    protected ApprovalHandler next;
    protected final BigDecimal maxAmount;  // 该级别可审批的最大金额

    public ApprovalHandler(BigDecimal maxAmount) {
        this.maxAmount = maxAmount;
    }

    public ApprovalHandler setNext(ApprovalHandler next) {
        this.next = next;
        return next;
    }

    public ApprovalResult approve(ExpenseRequest request) {
        if (request.getAmount().compareTo(maxAmount) <= 0) {
            // 在权限范围内,执行审批
            return doApprove(request);
        } else if (next != null) {
            // 超出权限,传递给上级
            return next.approve(request);
        } else {
            return ApprovalResult.reject("超出审批权限");
        }
    }

    protected abstract ApprovalResult doApprove(ExpenseRequest request);
}

// 组长审批(1000元以内)
@Component
public class TeamLeaderApproval extends ApprovalHandler {

    public TeamLeaderApproval() {
        super(new BigDecimal("1000"));
    }

    @Override
    protected ApprovalResult doApprove(ExpenseRequest request) {
        log.info("组长审批: {}, 金额: {}", request.getTitle(), request.getAmount());
        return ApprovalResult.approve("组长已审批");
    }
}

// 经理审批(5000元以内)
@Component
public class ManagerApproval extends ApprovalHandler {

    public ManagerApproval() {
        super(new BigDecimal("5000"));
    }

    @Override
    protected ApprovalResult doApprove(ExpenseRequest request) {
        log.info("经理审批: {}, 金额: {}", request.getTitle(), request.getAmount());
        return ApprovalResult.approve("经理已审批");
    }
}

// 总监审批(20000元以内)
@Component
public class DirectorApproval extends ApprovalHandler {

    public DirectorApproval() {
        super(new BigDecimal("20000"));
    }

    @Override
    protected ApprovalResult doApprove(ExpenseRequest request) {
        log.info("总监审批: {}, 金额: {}", request.getTitle(), request.getAmount());
        return ApprovalResult.approve("总监已审批");
    }
}

// 构建审批链
@Configuration
public class ApprovalChainConfig {

    @Bean
    public ApprovalHandler approvalChain(
            TeamLeaderApproval teamLeader,
            ManagerApproval manager,
            DirectorApproval director) {
        teamLeader.setNext(manager).setNext(director);
        return teamLeader;
    }
}

十七、设计模式速查与总结

17.1 设计模式速查表

┌──────────────┬──────────────────────────────┬──────────────────────────────┐
│ 模式名称     │ 核心思想                      │ 项目应用场景                 │
├──────────────┼──────────────────────────────┼──────────────────────────────┤
│ 单例模式     │ 全局唯一实例                  │ 配置管理、连接池、Spring Bean │
│ 工厂模式     │ 封装对象创建                  │ 支付方式、短信服务商选择      │
│ 建造者模式   │ 分步构建复杂对象              │ 查询条件、HTTP请求、SQL构建   │
│ 原型模式     │ 复制已有对象                  │ 订单模板、配置克隆            │
├──────────────┼──────────────────────────────┼──────────────────────────────┤
│ 代理模式     │ 控制对象访问                  │ AOP、事务、权限控制           │
│ 装饰器模式   │ 动态添加功能                  │ 缓存装饰、IO流、功能增强      │
│ 适配器模式   │ 接口转换                      │ 第三方SDK适配、格式转换       │
│ 门面模式     │ 简化复杂接口                  │ 用户注册、订单创建等复杂流程  │
├──────────────┼──────────────────────────────┼──────────────────────────────┤
│ 观察者模式   │ 一对多事件通知                │ 订单状态变更、Spring事件      │
│ 策略模式     │ 算法族可互换                  │ 促销策略、排序算法、导出格式  │
│ 模板方法     │ 定义算法骨架                  │ 数据导入、报表生成、JdbcTemplate│
│ 责任链模式   │ 请求沿链传递                  │ 审批流程、过滤器链、参数校验  │
│ 命令模式     │ 请求封装为对象                │ 购物车操作、撤销重做          │
└──────────────┴──────────────────────────────┴──────────────────────────────┘

17.2 设计原则(SOLID)

S - 单一职责原则(SRP)
    一个类只负责一件事
    示例:UserService只处理用户业务,不处理订单

O - 开闭原则(OCP)
    对扩展开放,对修改关闭
    示例:新增支付方式只需新增实现类,不修改工厂

L - 里氏替换原则(LSP)
    子类可以替换父类
    示例:所有PaymentStrategy实现都可以替换使用

I - 接口隔离原则(ISP)
    接口要小而专,不要大而全
    示例:读接口和写接口分离

D - 依赖倒置原则(DIP)
    依赖抽象,不依赖具体实现
    示例:Service依赖Repository接口,不依赖具体实现类

十八、MyBatis-Plus 与设计模式综合实战

18.1 通用 CRUD 接口设计

// 通用Controller基类(模板方法模式)
public abstract class BaseController<S extends IService<T>, T> {

    @Autowired
    protected S service;

    @GetMapping("/{id}")
    public Result<T> getById(@PathVariable Long id) {
        T entity = service.getById(id);
        if (entity == null) {
            return Result.fail(404, "数据不存在");
        }
        return Result.success(entity);
    }

    @GetMapping
    public Result<PageResult<T>> list(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        IPage<T> pageResult = service.page(new Page<>(page, size));
        return Result.success(PageResult.of(pageResult));
    }

    @PostMapping
    public Result<T> save(@RequestBody @Validated T entity) {
        // 子类可以覆盖此方法添加额外逻辑
        beforeSave(entity);
        service.save(entity);
        afterSave(entity);
        return Result.success(entity);
    }

    @PutMapping("/{id}")
    public Result<Void> update(@PathVariable Long id, @RequestBody T entity) {
        service.updateById(entity);
        return Result.success();
    }

    @DeleteMapping("/{id}")
    public Result<Void> delete(@PathVariable Long id) {
        service.removeById(id);
        return Result.success();
    }

    // 钩子方法(子类可覆盖)
    protected void beforeSave(T entity) {}
    protected void afterSave(T entity) {}
}

// 用户Controller继承基类
@RestController
@RequestMapping("/api/users")
public class UserController extends BaseController<UserService, User> {

    @Override
    protected void beforeSave(User user) {
        // 密码加密
        user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()));
    }

    @Override
    protected void afterSave(User user) {
        // 发送欢迎邮件
        emailService.sendWelcome(user.getEmail());
    }

    // 扩展特有接口
    @PostMapping("/login")
    public Result<String> login(@RequestBody LoginDTO dto) {
        return Result.success(userService.login(dto));
    }
}

18.2 动态数据权限(策略 + 代理)

// 数据权限策略接口
public interface DataPermissionStrategy {
    void applyPermission(LambdaQueryWrapper<?> wrapper, Long userId);
    String getRole();
}

// 管理员:查看所有数据
@Component
public class AdminPermissionStrategy implements DataPermissionStrategy {
    @Override
    public void applyPermission(LambdaQueryWrapper<?> wrapper, Long userId) {
        // 不添加任何限制
    }
    @Override
    public String getRole() { return "ADMIN"; }
}

// 普通用户:只查看自己的数据
@Component
public class UserPermissionStrategy implements DataPermissionStrategy {
    @Override
    public void applyPermission(LambdaQueryWrapper<?> wrapper, Long userId) {
        wrapper.eq("create_by", userId);
    }
    @Override
    public String getRole() { return "USER"; }
}

// 数据权限拦截器(MyBatis-Plus插件)
@Component
public class DataPermissionInterceptor implements InnerInterceptor {

    @Autowired
    private DataPermissionStrategyFactory strategyFactory;

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms,
                             Object parameter, RowBounds rowBounds,
                             ResultHandler resultHandler, BoundSql boundSql) {
        // 获取当前用户角色
        String role = SecurityContextHolder.getContext()
            .getAuthentication().getAuthorities().iterator().next().getAuthority();

        // 获取对应策略
        DataPermissionStrategy strategy = strategyFactory.getStrategy(role);

        // 动态修改SQL(添加数据权限条件)
        // 实际实现需要解析和修改SQL
    }
}

十九、练习题

基础练习

  1. 使用 MyBatis-Plus 实现用户管理 CRUD:

    • 创建 User 实体(含逻辑删除、乐观锁、自动填充)
    • 实现分页查询(支持按姓名模糊搜索)
    • 实现批量导入(Excel格式)
  2. 实现策略模式的促销系统:

    • 满减策略(满100减20)
    • 折扣策略(9折)
    • 优惠券策略
    • 支持动态切换策略

进阶练习

  1. 实现责任链模式的订单校验:

    • 用户状态校验
    • 库存校验
    • 金额校验
    • 黑名单校验
  2. 实现观察者模式的订单事件系统:

    • 订单创建事件
    • 订单支付事件
    • 订单取消事件
    • 多个监听器异步处理

综合练习

  1. 设计一个完整的电商下单系统,综合运用:
    • 建造者模式(构建订单对象)
    • 策略模式(促销计算)
    • 责任链模式(订单校验)
    • 观察者模式(下单后通知)
    • 门面模式(统一下单入口)

二十、学习检查清单

MyBatis-Plus 掌握程度

  • 能够配置 MyBatis-Plus 并完成基础 CRUD
  • 熟练使用 LambdaQueryWrapper 构建动态查询
  • 能够配置和使用分页插件
  • 能够实现乐观锁防止并发冲突
  • 能够配置逻辑删除和自动填充
  • 能够使用代码生成器快速搭建项目骨架
  • 能够回答 MyBatis-Plus 高频面试题

设计模式掌握程度

  • 能够说出并区分三大类设计模式
  • 能够实现单例模式(至少3种方式)
  • 能够在项目中应用工厂模式(支付/短信等)
  • 能够使用 Spring AOP 实现代理模式
  • 能够实现策略模式(促销/支付等)
  • 能够实现责任链模式(审批/校验等)
  • 能够实现观察者模式(Spring事件)
  • 能够识别 Spring/MyBatis 源码中的设计模式
  • 能够在面试中清晰描述设计模式的应用场景

二十一、MyBatis-Plus 高级查询技巧

21.1 关联查询

// 一对多查询:用户及其订单列表
@Data
public class UserWithOrders {
    private Long id;
    private String userName;
    private List<Order> orders;
}

// Mapper.xml 实现
// UserMapper.java
IPage<UserWithOrders> selectUserWithOrders(Page<UserWithOrders> page,
                                            @Param("keyword") String keyword);
<!-- UserMapper.xml -->
<resultMap id="UserWithOrdersMap" type="UserWithOrders">
    <id property="id" column="user_id"/>
    <result property="userName" column="user_name"/>
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="amount" column="amount"/>
        <result property="status" column="status"/>
    </collection>
</resultMap>

<select id="selectUserWithOrders" resultMap="UserWithOrdersMap">
    SELECT u.id as user_id, u.user_name,
           o.id as order_id, o.order_no, o.amount, o.status
    FROM user u
    LEFT JOIN `order` o ON u.id = o.user_id AND o.deleted = 0
    WHERE u.deleted = 0
    <if test="keyword != null and keyword != ''">
        AND u.user_name LIKE CONCAT('%', #{keyword}, '%')
    </if>
    ORDER BY u.id DESC
</select>

21.2 动态 SQL 技巧

<!-- 动态条件查询 -->
<select id="selectByCondition" resultType="User">
    SELECT * FROM user
    <where>
        <if test="userName != null and userName != ''">
            AND user_name LIKE CONCAT('%', #{userName}, '%')
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
        <if test="maxAge != null">
            AND age &lt;= #{maxAge}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
        AND deleted = 0
    </where>
    <choose>
        <when test="orderBy == 'age'">
            ORDER BY age ${orderDirection}
        </when>
        <when test="orderBy == 'createTime'">
            ORDER BY create_time ${orderDirection}
        </when>
        <otherwise>
            ORDER BY id DESC
        </otherwise>
    </choose>
</select>

<!-- 批量插入 -->
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user(user_name, age, email, create_time)
    VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.userName}, #{user.age}, #{user.email}, NOW())
    </foreach>
</insert>

<!-- 批量更新 -->
<update id="batchUpdate">
    <foreach collection="list" item="user" separator=";">
        UPDATE user
        <set>
            <if test="user.userName != null">user_name = #{user.userName},</if>
            <if test="user.age != null">age = #{user.age},</if>
            update_time = NOW()
        </set>
        WHERE id = #{user.id}
    </foreach>
</update>

21.3 自定义 TypeHandler

// 场景:数据库存储JSON字符串,Java对象自动转换

@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonListTypeHandler extends BaseTypeHandler<List<String>> {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
                                     List<String> parameter, JdbcType jdbcType)
            throws SQLException {
        try {
            ps.setString(i, MAPPER.writeValueAsString(parameter));
        } catch (JsonProcessingException e) {
            throw new SQLException("JSON序列化失败", e);
        }
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return parseJson(rs.getString(columnName));
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return parseJson(rs.getString(columnIndex));
    }

    @Override
    public List<String> getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return parseJson(cs.getString(columnIndex));
    }

    private List<String> parseJson(String json) {
        if (json == null || json.isEmpty()) return new ArrayList<>();
        try {
            return MAPPER.readValue(json, new TypeReference<List<String>>() {});
        } catch (JsonProcessingException e) {
            return new ArrayList<>();
        }
    }
}

// 实体类使用
@Data
@TableName("product")
public class Product {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

    // 数据库存储:["手机","数码","华为"]
    // Java对象:List<String>
    @TableField(typeHandler = JsonListTypeHandler.class)
    private List<String> tags;
}

二十二、更多设计模式实战

22.1 状态模式(State)

核心思想: 允许对象在内部状态改变时改变其行为。

应用场景: 订单状态机、工作流引擎

// 订单状态接口
public interface OrderState {
    void pay(Order order);
    void ship(Order order);
    void complete(Order order);
    void cancel(Order order);
    String getStateName();
}

// 待支付状态
public class PendingPaymentState implements OrderState {

    @Override
    public void pay(Order order) {
        order.setState(new PaidState());
        order.setStatus(OrderStatus.PAID);
        log.info("订单支付成功: {}", order.getOrderNo());
    }

    @Override
    public void ship(Order order) {
        throw new BusinessException("待支付订单不能发货");
    }

    @Override
    public void complete(Order order) {
        throw new BusinessException("待支付订单不能完成");
    }

    @Override
    public void cancel(Order order) {
        order.setState(new CancelledState());
        order.setStatus(OrderStatus.CANCELLED);
        log.info("订单取消: {}", order.getOrderNo());
    }

    @Override
    public String getStateName() { return "待支付"; }
}

// 已支付状态
public class PaidState implements OrderState {

    @Override
    public void pay(Order order) {
        throw new BusinessException("订单已支付");
    }

    @Override
    public void ship(Order order) {
        order.setState(new ShippedState());
        order.setStatus(OrderStatus.SHIPPED);
        log.info("订单发货: {}", order.getOrderNo());
    }

    @Override
    public void complete(Order order) {
        throw new BusinessException("已支付订单需先发货");
    }

    @Override
    public void cancel(Order order) {
        // 已支付取消需要退款
        order.setState(new RefundingState());
        order.setStatus(OrderStatus.REFUNDING);
    }

    @Override
    public String getStateName() { return "已支付"; }
}

// 订单实体(包含状态)
@Data
public class Order {
    private Long id;
    private String orderNo;
    private OrderStatus status;
    private transient OrderState state;  // 不持久化状态对象

    // 根据status初始化state
    public void initState() {
        switch (this.status) {
            case PENDING_PAYMENT: this.state = new PendingPaymentState(); break;
            case PAID: this.state = new PaidState(); break;
            case SHIPPED: this.state = new ShippedState(); break;
            default: this.state = new CancelledState();
        }
    }

    // 委托给状态对象处理
    public void pay() { state.pay(this); }
    public void ship() { state.ship(this); }
    public void complete() { state.complete(this); }
    public void cancel() { state.cancel(this); }
}

22.2 迭代器模式(Iterator)

核心思想: 提供统一的方式遍历集合,不暴露集合内部结构。

// 场景:分批处理大量数据(避免OOM)

public class BatchDataIterator<T> implements Iterator<List<T>> {

    private final Function<Integer, List<T>> dataLoader;
    private final int batchSize;
    private int currentPage = 1;
    private List<T> currentBatch;
    private boolean hasMore = true;

    public BatchDataIterator(Function<Integer, List<T>> dataLoader, int batchSize) {
        this.dataLoader = dataLoader;
        this.batchSize = batchSize;
        loadNextBatch();
    }

    private void loadNextBatch() {
        currentBatch = dataLoader.apply(currentPage++);
        hasMore = currentBatch != null && currentBatch.size() == batchSize;
    }

    @Override
    public boolean hasNext() {
        return currentBatch != null && !currentBatch.isEmpty();
    }

    @Override
    public List<T> next() {
        List<T> batch = currentBatch;
        if (hasMore) {
            loadNextBatch();
        } else {
            currentBatch = null;
        }
        return batch;
    }
}

// 使用:分批处理100万用户数据
@Service
public class UserBatchService {

    @Autowired
    private UserMapper userMapper;

    public void processAllUsers() {
        BatchDataIterator<User> iterator = new BatchDataIterator<>(
            page -> userMapper.selectPage(new Page<>(page, 1000), null).getRecords(),
            1000
        );

        while (iterator.hasNext()) {
            List<User> batch = iterator.next();
            processBatch(batch);
            log.info("已处理 {} 条用户数据", batch.size());
        }
    }

    private void processBatch(List<User> users) {
        // 批量处理逻辑
    }
}

22.3 享元模式(Flyweight)

核心思想: 共享细粒度对象,减少内存使用。

// 场景:字典数据缓存(大量重复使用的枚举值)

@Component
public class DictFlyweightFactory {

    // 享元池
    private final Map<String, DictItem> pool = new ConcurrentHashMap<>();

    @Autowired
    private DictMapper dictMapper;

    @PostConstruct
    public void init() {
        // 启动时加载所有字典数据到享元池
        List<DictItem> items = dictMapper.selectAll();
        items.forEach(item -> pool.put(item.getCode(), item));
        log.info("字典数据加载完成,共 {} 条", items.size());
    }

    /**
     * 获取字典项(从享元池获取,不重复创建)
     */
    public DictItem getDictItem(String code) {
        return pool.get(code);
    }

    /**
     * 获取字典项名称
     */
    public String getDictName(String code) {
        DictItem item = pool.get(code);
        return item != null ? item.getName() : code;
    }
}

// 使用
@Service
public class OrderService {

    @Autowired
    private DictFlyweightFactory dictFactory;

    public OrderVO convertToVO(Order order) {
        OrderVO vo = new OrderVO();
        BeanUtils.copyProperties(order, vo);
        // 状态码转名称(使用享元池,不重复查库)
        vo.setStatusName(dictFactory.getDictName("ORDER_STATUS_" + order.getStatus()));
        return vo;
    }
}

二十三、大厂面试设计模式真题

23.1 字节跳动面试题

Q:如何设计一个可扩展的消息推送系统?

答:使用策略模式 + 工厂模式 + 观察者模式

1. 推送渠道策略(策略模式):
   interface PushStrategy {
       void push(PushMessage message);
       String getChannel();
   }
   - AppPushStrategy(App推送)
   - SmsPushStrategy(短信推送)
   - EmailPushStrategy(邮件推送)
   - WechatPushStrategy(微信推送)

2. 策略工厂(工厂模式):
   PushStrategyFactory 根据渠道类型返回对应策略

3. 事件驱动(观察者模式):
   业务事件 → 推送服务监听 → 根据用户偏好选择渠道 → 执行推送

4. 扩展性:
   新增推送渠道只需新增Strategy实现类,
   注册到工厂即可,无需修改现有代码(开闭原则)

Q:如何设计一个高性能的限流组件?

答:使用策略模式 + 装饰器模式

1. 限流策略(策略模式):
   interface RateLimitStrategy {
       boolean tryAcquire(String key);
   }
   - FixedWindowStrategy(固定窗口)
   - SlidingWindowStrategy(滑动窗口)
   - TokenBucketStrategy(令牌桶)
   - LeakyBucketStrategy(漏桶)

2. 限流装饰器(装饰器模式):
   @Around("@annotation(RateLimit)")
   public Object rateLimitAround(ProceedingJoinPoint pjp) {
       RateLimit annotation = getAnnotation(pjp);
       RateLimitStrategy strategy = factory.getStrategy(annotation.type());
       if (!strategy.tryAcquire(buildKey(pjp))) {
           throw new RateLimitException("请求过于频繁");
       }
       return pjp.proceed();
   }

23.2 阿里巴巴面试题

Q:说说你对开闭原则的理解,并举例说明

答:
开闭原则(OCP):对扩展开放,对修改关闭。
即:添加新功能时,应该通过新增代码实现,而不是修改已有代码。

反例(违反开闭原则):
public BigDecimal calculateDiscount(Order order, String type) {
    if ("FULL_REDUCTION".equals(type)) {
        // 满减逻辑
    } else if ("DISCOUNT".equals(type)) {
        // 折扣逻辑
    }
    // 每次新增促销类型都要修改这个方法 ✗
}

正例(遵循开闭原则):
interface DiscountStrategy {
    BigDecimal calculate(Order order);
}
// 新增促销类型只需新增实现类,不修改已有代码 ✓
class NewYearDiscountStrategy implements DiscountStrategy { ... }

二十四、总结

本文档涵盖了 MyBatis-Plus 和设计模式的完整知识体系:

MyBatis-Plus 部分:

  • 快速入门(BaseMapper、IService、ServiceImpl)
  • 条件构造器(QueryWrapper、LambdaQueryWrapper)
  • 分页插件(配置与使用)
  • 乐观锁(@Version)
  • 逻辑删除(@TableLogic)
  • 自动填充(MetaObjectHandler)
  • 代码生成器(FastAutoGenerator)
  • 高级功能(多数据源、TypeHandler)

设计模式部分(13种高频模式):

  • 创建型:单例、工厂、建造者、原型
  • 结构型:代理、装饰器、适配器、门面、享元
  • 行为型:观察者、策略、模板方法、责任链、命令、状态、迭代器

框架中的设计模式:

  • Spring:单例、工厂、代理、观察者、模板方法、装饰器、责任链
  • MyBatis:建造者、工厂、代理、模板方法、装饰器

掌握这些内容,能够在大厂面试中自信地回答设计模式相关问题,并在实际项目中灵活运用。


二十五、MyBatis-Plus 实战项目:商品管理系统

25.1 数据库设计

-- 商品分类表
CREATE TABLE category (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(64) NOT NULL COMMENT '分类名称',
    parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父分类ID,0为顶级',
    sort INT NOT NULL DEFAULT 0 COMMENT '排序',
    icon VARCHAR(256) COMMENT '图标URL',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '1启用 0禁用',
    deleted TINYINT NOT NULL DEFAULT 0,
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_parent_id (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类';

-- 商品表
CREATE TABLE product (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(128) NOT NULL COMMENT '商品名称',
    category_id BIGINT NOT NULL COMMENT '分类ID',
    brand VARCHAR(64) COMMENT '品牌',
    price DECIMAL(10,2) NOT NULL COMMENT '售价',
    original_price DECIMAL(10,2) COMMENT '原价',
    stock INT NOT NULL DEFAULT 0 COMMENT '库存',
    sales INT NOT NULL DEFAULT 0 COMMENT '销量',
    description TEXT COMMENT '商品描述',
    images JSON COMMENT '商品图片列表',
    tags JSON COMMENT '标签列表',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '1上架 0下架',
    version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
    deleted TINYINT NOT NULL DEFAULT 0,
    create_by BIGINT COMMENT '创建人',
    update_by BIGINT COMMENT '更新人',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_category_id (category_id),
    INDEX idx_brand (brand),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品';

-- 商品SKU表
CREATE TABLE product_sku (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    product_id BIGINT NOT NULL COMMENT '商品ID',
    sku_code VARCHAR(64) NOT NULL UNIQUE COMMENT 'SKU编码',
    specs JSON NOT NULL COMMENT '规格属性,如{"颜色":"黑色","内存":"256G"}',
    price DECIMAL(10,2) NOT NULL COMMENT 'SKU价格',
    stock INT NOT NULL DEFAULT 0 COMMENT 'SKU库存',
    version INT NOT NULL DEFAULT 0,
    deleted TINYINT NOT NULL DEFAULT 0,
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_product_id (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SKU';

25.2 实体类

@Data
@TableName("product")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

    private Long categoryId;

    private String brand;

    private BigDecimal price;

    private BigDecimal originalPrice;

    private Integer stock;

    private Integer sales;

    private String description;

    @TableField(typeHandler = JsonListTypeHandler.class)
    private List<String> images;

    @TableField(typeHandler = JsonListTypeHandler.class)
    private List<String> tags;

    private Integer status;

    @Version
    private Integer version;

    @TableLogic
    private Integer deleted;

    @TableField(fill = FieldFill.INSERT)
    private Long createBy;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateBy;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

25.3 Service 实现

@Service
@Slf4j
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product>
        implements ProductService {

    @Autowired
    private ProductSkuMapper skuMapper;

    @Autowired
    private CategoryService categoryService;

    /**
     * 创建商品(含SKU)
     */
    @Transactional
    @Override
    public Product createProduct(ProductCreateDTO dto) {
        // 1. 校验分类
        Category category = categoryService.getById(dto.getCategoryId());
        if (category == null || category.getStatus() == 0) {
            throw new BusinessException("商品分类不存在或已禁用");
        }

        // 2. 创建商品
        Product product = Product.builder()
            .name(dto.getName())
            .categoryId(dto.getCategoryId())
            .brand(dto.getBrand())
            .price(dto.getPrice())
            .originalPrice(dto.getOriginalPrice())
            .stock(dto.getSkus().stream().mapToInt(ProductSkuDTO::getStock).sum())
            .description(dto.getDescription())
            .images(dto.getImages())
            .tags(dto.getTags())
            .status(0)  // 默认下架
            .build();

        save(product);

        // 3. 创建SKU
        List<ProductSku> skus = dto.getSkus().stream().map(skuDto -> {
            ProductSku sku = new ProductSku();
            sku.setProductId(product.getId());
            sku.setSkuCode(generateSkuCode(product.getId(), skuDto));
            sku.setSpecs(skuDto.getSpecs());
            sku.setPrice(skuDto.getPrice());
            sku.setStock(skuDto.getStock());
            return sku;
        }).collect(Collectors.toList());

        skuMapper.batchInsert(skus);

        log.info("商品创建成功, productId={}, skuCount={}", product.getId(), skus.size());
        return product;
    }

    /**
     * 商品上架
     */
    @Override
    public void onSale(Long productId) {
        Product product = getById(productId);
        if (product == null) throw new BusinessException("商品不存在");
        if (product.getStatus() == 1) throw new BusinessException("商品已上架");
        if (product.getStock() <= 0) throw new BusinessException("库存为0,不能上架");

        // 使用乐观锁更新
        boolean success = lambdaUpdate()
            .eq(Product::getId, productId)
            .eq(Product::getVersion, product.getVersion())
            .set(Product::getStatus, 1)
            .update();

        if (!success) {
            throw new BusinessException("操作冲突,请重试");
        }
    }

    /**
     * 扣减库存(乐观锁)
     */
    @Override
    public boolean deductStock(Long productId, Long skuId, Integer quantity) {
        // 先扣SKU库存
        int skuRows = skuMapper.deductStock(skuId, quantity);
        if (skuRows == 0) {
            log.warn("SKU库存不足, skuId={}, quantity={}", skuId, quantity);
            return false;
        }

        // 再扣商品总库存
        int productRows = baseMapper.deductStock(productId, quantity);
        if (productRows == 0) {
            log.warn("商品库存不足, productId={}, quantity={}", productId, quantity);
            // 回滚SKU库存
            skuMapper.increaseStock(skuId, quantity);
            return false;
        }

        return true;
    }

    /**
     * 分页查询商品列表
     */
    @Override
    public IPage<ProductVO> pageProducts(ProductQueryDTO query) {
        Page<Product> page = new Page<>(query.getPage(), query.getSize());

        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<Product>()
            .like(StringUtils.hasText(query.getName()), Product::getName, query.getName())
            .eq(query.getCategoryId() != null, Product::getCategoryId, query.getCategoryId())
            .eq(StringUtils.hasText(query.getBrand()), Product::getBrand, query.getBrand())
            .ge(query.getMinPrice() != null, Product::getPrice, query.getMinPrice())
            .le(query.getMaxPrice() != null, Product::getPrice, query.getMaxPrice())
            .eq(query.getStatus() != null, Product::getStatus, query.getStatus())
            .orderByDesc(Product::getCreateTime);

        IPage<Product> productPage = page(page, wrapper);

        // 转换为VO
        return productPage.convert(this::convertToVO);
    }

    private ProductVO convertToVO(Product product) {
        ProductVO vo = new ProductVO();
        BeanUtils.copyProperties(product, vo);
        // 获取分类名称
        Category category = categoryService.getById(product.getCategoryId());
        if (category != null) {
            vo.setCategoryName(category.getName());
        }
        return vo;
    }

    private String generateSkuCode(Long productId, ProductSkuDTO skuDto) {
        return "SKU" + productId + "_" + System.currentTimeMillis();
    }
}

25.4 Controller 层

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

    @Autowired
    private ProductService productService;

    /**
     * 创建商品
     */
    @PostMapping
    @OperationLog("创建商品")
    public Result<Product> create(@RequestBody @Validated ProductCreateDTO dto) {
        Product product = productService.createProduct(dto);
        return Result.success(product);
    }

    /**
     * 商品列表(分页)
     */
    @GetMapping
    public Result<PageResult<ProductVO>> list(ProductQueryDTO query) {
        IPage<ProductVO> page = productService.pageProducts(query);
        return Result.success(PageResult.of(page));
    }

    /**
     * 商品详情
     */
    @GetMapping("/{id}")
    public Result<ProductVO> detail(@PathVariable Long id) {
        Product product = productService.getById(id);
        if (product == null) {
            return Result.fail(404, "商品不存在");
        }
        return Result.success(productService.convertToVO(product));
    }

    /**
     * 商品上架
     */
    @PutMapping("/{id}/on-sale")
    @OperationLog("商品上架")
    public Result<Void> onSale(@PathVariable Long id) {
        productService.onSale(id);
        return Result.success();
    }

    /**
     * 商品下架
     */
    @PutMapping("/{id}/off-sale")
    @OperationLog("商品下架")
    public Result<Void> offSale(@PathVariable Long id) {
        productService.offSale(id);
        return Result.success();
    }

    /**
     * 删除商品(逻辑删除)
     */
    @DeleteMapping("/{id}")
    @OperationLog("删除商品")
    public Result<Void> delete(@PathVariable Long id) {
        productService.removeById(id);
        return Result.success();
    }

    /**
     * 批量删除
     */
    @DeleteMapping("/batch")
    public Result<Void> batchDelete(@RequestBody List<Long> ids) {
        productService.removeByIds(ids);
        return Result.success();
    }
}

二十六、设计模式综合应用:订单系统重构

26.1 重构前(反模式)

// 反例:一个方法做了太多事情,违反单一职责
@Service
public class OrderServiceBad {

    public Order createOrder(OrderCreateDTO dto) {
        // 1. 校验用户
        User user = userMapper.selectById(dto.getUserId());
        if (user == null) throw new RuntimeException("用户不存在");
        if (user.getStatus() != 1) throw new RuntimeException("用户已禁用");

        // 2. 校验商品
        for (OrderItemDTO item : dto.getItems()) {
            Product product = productMapper.selectById(item.getProductId());
            if (product == null) throw new RuntimeException("商品不存在");
            if (product.getStock() < item.getQuantity()) throw new RuntimeException("库存不足");
        }

        // 3. 计算价格
        BigDecimal total = BigDecimal.ZERO;
        if ("FULL_REDUCTION".equals(dto.getPromotionType())) {
            // 满减逻辑
        } else if ("DISCOUNT".equals(dto.getPromotionType())) {
            // 折扣逻辑
        }

        // 4. 创建订单
        Order order = new Order();
        // ...

        // 5. 扣减库存
        // ...

        // 6. 发送短信
        // ...

        // 7. 发送邮件
        // ...

        return order;
    }
}

26.2 重构后(应用设计模式)

// 正例:职责清晰,可扩展

// 1. 责任链:订单校验
@Component
public class OrderValidationChain {

    @Autowired
    private UserStatusValidator userValidator;

    @Autowired
    private StockValidator stockValidator;

    @Autowired
    private AmountValidator amountValidator;

    @PostConstruct
    public void init() {
        userValidator.setNext(stockValidator).setNext(amountValidator);
    }

    public void validate(OrderCreateDTO dto) {
        String error = userValidator.doValidate(dto);
        if (error != null) throw new BusinessException(error);
    }
}

// 2. 策略模式:价格计算
@Component
public class PriceCalculator {

    @Autowired
    private PromotionContext promotionContext;

    public BigDecimal calculate(OrderCreateDTO dto) {
        BigDecimal total = dto.getItems().stream()
            .map(item -> item.getPrice().multiply(new BigDecimal(item.getQuantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        return promotionContext.applyPromotion(dto.getPromotionCode(), total, null);
    }
}

// 3. 门面模式:统一下单入口
@Service
@Slf4j
public class OrderFacade {

    @Autowired
    private OrderValidationChain validationChain;

    @Autowired
    private PriceCalculator priceCalculator;

    @Autowired
    private OrderDomainService orderDomainService;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Transactional
    public Order createOrder(OrderCreateDTO dto) {
        // 1. 校验(责任链)
        validationChain.validate(dto);

        // 2. 计算价格(策略)
        BigDecimal finalPrice = priceCalculator.calculate(dto);

        // 3. 创建订单(领域服务)
        Order order = orderDomainService.create(dto, finalPrice);

        // 4. 发布事件(观察者)
        eventPublisher.publishEvent(new OrderCreatedEvent(this, order));

        log.info("订单创建成功, orderId={}", order.getId());
        return order;
    }
}

二十七、速查手册

27.1 MyBatis-Plus 常用注解速查

实体类注解:
@TableName("table_name")    → 指定表名
@TableId(type=IdType.AUTO)  → 主键,自增
@TableField("column_name")  → 指定列名
@TableField(exist=false)    → 非数据库字段
@TableField(fill=FieldFill.INSERT) → 插入时自动填充
@TableLogic                 → 逻辑删除字段
@Version                    → 乐观锁版本号

条件构造器:
eq/ne/gt/ge/lt/le           → 比较运算
between/notBetween          → 范围
like/notLike/likeLeft/likeRight → 模糊
in/notIn/inSql              → IN查询
isNull/isNotNull            → 空值
and/or                      → 逻辑连接
orderByAsc/orderByDesc      → 排序
groupBy/having              → 分组
select                      → 指定查询字段

27.2 设计模式选择指南

遇到以下场景,选择对应模式:

需要全局唯一对象?
  → 单例模式

需要根据条件创建不同对象?
  → 工厂模式

需要构建复杂对象(多个可选参数)?
  → 建造者模式

需要在不修改原类的情况下添加功能?
  → 装饰器模式(运行时添加)
  → 代理模式(控制访问)

需要统一处理多种算法/行为?
  → 策略模式

需要定义算法骨架,子类实现细节?
  → 模板方法模式

需要一对多通知?
  → 观察者模式

需要请求依次经过多个处理者?
  → 责任链模式

需要将不兼容接口统一?
  → 适配器模式

需要简化复杂子系统?
  → 门面模式

对象状态变化影响行为?
  → 状态模式

27.3 面试高频问题速查

MyBatis-Plus 面试题:
Q: MP和MyBatis的关系?
A: MP是MyBatis的增强工具,在MyBatis基础上只做增强不做改变

Q: 如何防止全表更新删除?
A: 配置 BlockAttackInnerInterceptor 插件

Q: @TableField(exist=false)的作用?
A: 标记该字段不对应数据库列,不参与SQL操作

Q: 乐观锁的原理?
A: 更新时加上 WHERE version=? 条件,更新成功则version+1

设计模式面试题:
Q: 单例模式如何防止反射破坏?
A: 使用枚举实现,枚举天然防止反射和序列化破坏

Q: 代理模式和装饰器模式的区别?
A: 代理控制访问,装饰器增强功能;代理自创目标,装饰器外部传入

Q: Spring用了哪些设计模式?
A: 单例、工厂、代理(AOP)、观察者(事件)、模板方法、装饰器、责任链

Q: 如何在项目中应用策略模式?
A: 定义策略接口,多个实现类,工厂根据类型返回策略,业务代码面向接口编程
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐