MyBatis-Plus进阶与设计模式实战
21-MyBatis-Plus进阶与设计模式实战
学习目标
- 掌握 MyBatis-Plus 全套功能(CRUD、条件构造器、分页、乐观锁)
- 熟练使用代码生成器快速搭建项目骨架
- 理解并掌握10+种高频设计模式
- 能够识别 Spring/MyBatis 框架源码中的设计模式
- 能够在项目中灵活运用设计模式解决实际问题
- 能够回答大厂高频设计模式面试题
知识思维导图
一、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
}
}
十九、练习题
基础练习
-
使用 MyBatis-Plus 实现用户管理 CRUD:
- 创建 User 实体(含逻辑删除、乐观锁、自动填充)
- 实现分页查询(支持按姓名模糊搜索)
- 实现批量导入(Excel格式)
-
实现策略模式的促销系统:
- 满减策略(满100减20)
- 折扣策略(9折)
- 优惠券策略
- 支持动态切换策略
进阶练习
-
实现责任链模式的订单校验:
- 用户状态校验
- 库存校验
- 金额校验
- 黑名单校验
-
实现观察者模式的订单事件系统:
- 订单创建事件
- 订单支付事件
- 订单取消事件
- 多个监听器异步处理
综合练习
- 设计一个完整的电商下单系统,综合运用:
- 建造者模式(构建订单对象)
- 策略模式(促销计算)
- 责任链模式(订单校验)
- 观察者模式(下单后通知)
- 门面模式(统一下单入口)
二十、学习检查清单
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 <= #{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: 定义策略接口,多个实现类,工厂根据类型返回策略,业务代码面向接口编程
更多推荐




所有评论(0)