DIY项目-校遇
本文主要记录开发过程中遇到的问题。
1、配置问题
问题描述 | 根因分析 | 解决办法 |
---|---|---|
子模块无法识别父模块 | 子模块<parent>没声明或声明不正确 | 子模块的<parent>换成父模块的相应信息 |
构建报错:Invalid packaging for parent POM ... must be "pom" but is "jar" | 父模块 pom.xml 未声明 <packaging>pom</packaging> ,或被 Maven 当成 jar 项目 | 在父 |
报错依旧存在(父已写 packaging=pom ) | 父模块 没有 <modules> 声明,Maven 不识别子模块,仍尝试按 jar 构建 | 在父 pom.xml 中加入 <modules><module>子目录名</module></modules> |
子模块 pom.xml 在 IDEA 中显示 红色 | IDEA 未将该目录识别为 Maven 子模块;常见原因: | ① 补全 <modules> |
不知道 <modules> 里应该填什么 | 误以为是 artifactId 或 name ,实为子模块目录名 | <module>xiaoyu-server</module> 只需与磁盘文件夹名保持一致即可 |
2、满足序列化与反序列化的前置要求:
不是必须加 @Data
,但不加就要自己写全套样板代码(getter/setter/toString/equals/hashCode)。
对嵌套类同理:只要它需要被 Jackson 序列化/反序列化,就必须提供无参构造 + getter/setter;否则 Jackson 会抛异常。
场景 | 结果 |
---|---|
JSON → 对象(反序列化) | Jackson 通过 无参构造 创建对象,然后调用 setter 赋值。 |
对象 → JSON(序列化) | Jackson 通过 getter 读取字段值。 |
Spring 数据绑定 | 同 Jackson,依赖 setter;没有则绑定失败。 |
3、Mybatis-plus应用场景下自定义sql的分页查询
要点 | 说明 | 备注 |
---|---|---|
插件名称 | PaginationInnerInterceptor | MP 3.4+ 默认配置 |
启用方式 | Spring Boot 自动配置已默认注入,无需手动 | 多数据源时需为每个 SqlSessionFactory 单独注册 |
(自动进行分页查询)触发条件【本质是添加limit】 | Mapper 方法第一个参数必须是 Page<T> | 顺序错了就不会分页 |
拦截点 | Executor.query(...) | 利用 MyBatis 官方 Interceptor 链 |
执行流程 | 1. 自动 COUNT → 2. 方言改写 SQL → 3. 查询 → 4. 封装结果 | 对开发者透明 |
返回类型 | 同一个 Page<T> 对象(参数即返回值) | 里面包含 records 、total 、current 、size |
复杂 SQL 支持 | 支持 JOIN、UNION、子查询、WITH 等 | 只要通过 Mapper 接口调用 |
自定义 COUNT | 写同名 _COUNT XML/注解语句,插件优先使用 | 解决 COUNT 性能问题 |
内存分页 | 可强制开启(极少用) | interceptor.setOverflow(true) |
常见错误 | 手写 LIMIT / OFFSET 导致重复分页 | 不要自己拼分页关键字 |
多数据源 | 每个数据源注册自己的 PaginationInnerInterceptor 并指定 DbType | 例:DbType.MYSQL 、DbType.ORACLE |
与 MyBatis 原生区别 | 原生需手动写两条 SQL(COUNT + LIMIT);MP 自动完成 |
4、mybaits对嵌套类的封装
步骤 | 关键点 | 示例代码 / 值 | 备注 |
---|---|---|---|
1. SQL 别名 | 避免列名冲突 | u.id AS user_id | 多表同名列必做 |
2. resultMap 类型 | 外层 VO 全限定名 | type="com.xiaoyu.vo.BlacklistsVO" | 对应最外层实体 |
3. 主键标记 | 用 <id> 而非 <result> | <id column="id" property="id"/> | 主键必须写 id,否则缓存/懒加载异常 |
4. 普通列 | 用 <result> | <result column="created_at" property="createdAt"/> | 非主键一律 result |
5. 嵌套对象类型 | 静态内部类写法 | <association property="targetUser" javaType="com.xiaoyu.vo.BlacklistsVO$BlackUserInfo"> | 字节码分隔符是 $ |
6. association 内部主键 | 同样用 <id> | <id column="target_id" property="id"/> | 内部类主键也要 id 标签 |
7. association 内部普通列 | 用 <result> | <result column="avatar_url" property="avatarUrl"/> | 内部类非主键用 result |
8. 使用 | 查询引用 resultMap | <select id="getBlackList" resultMap="BlackListMap"> | 不再用 resultType |
判断是不是association 内部主键
步骤 | 要确认的问题 | 如何确认 | 对应标签 |
---|---|---|---|
① 看数据库 | 这张表真正的主键是哪一列? | SHOW KEYS FROM users WHERE Key_name = 'PRIMARY'; | 把查出来的列名写 <id> |
② 写 SQL | 查询语句里是否把主键列查出来? | SELECT u.id … | 若 u.id 是主键,则 <id column="id" …/> |
③ 内部类 | 内部类里出现的列需要 <id> 吗? | 只要这一列在 它所属的那张表 里是主键,就用 <id> | 例:target_id 是 blacklists 主键 → <id column="target_id" …/> |
④ 注解 | 要不要在 BlackUserInfo 上加 @TableId ? | 完全不用;MyBatis 只读 XML,内部类不会被当成实体 | 无 |
<resultMap id = "BlackListMap" type = "com.xiaoyu.vo.BlacklistsVO"><id property="id" column="id"/><result property="targetId" column="target_id"/><result property="targetId" column="target_id"/><!-- 嵌套对象 :targetUser--><association property="targetUser" javaType="com.xiaoyu.vo.BlacklistsVO$BlackUserInfo"><id property="id" column="target_user_id"/><result property="nickname" column="nickname"/><result property="avatarUrl" column="avatar_url"/></association></resultMap><select id="getBlackList" resultMap="BlackListMap">select b.id, b.target_id, b.created_at, u.nickname, u.avatar_url,u.id as target_user_idfrom blacklists bleft join users u on b.target_id = u.idwhere b.owner_id = #{userId}</select>
5、mybatis-plus对没有主键的表的处理 、 枚举类
景 | 解决方式 | 具体做法(代码级) | 读写示例 | 注意/坑 |
---|---|---|---|---|
表没有主键 | ① 改表(最省事) | 直接加 id 列 | 实体正常写 @TableId(type = IdType.AUTO) ,MP 全套方法直接可用 | 需要 DBA 权限;旧数据自增从 1 开始 |
② 不改表(手写 SQL) | 实体 无 @TableId | 插入:@Insert("INSERT INTO t_log(user_id,content) VALUES(#{userId},#{content})") int insertLog(LogPO po); | selectById /updateById 等 全失效;只能自己写 | |
枚举存/读库 | 统一做法(两种场景通用) | 1. 枚举加 code 并标注 @EnumValue | 写入:po.setStatus(Status.PENDING); // 库内自动存 0 friendshipMapper.insert(po); | 忘了 @EnumValue 会存成字符串导致 Data too long;库值与 code 必须一一对应 |
问题 | 怎么做 | 示例/一句话要点 | 常见坑 |
---|---|---|---|
表没有主键 | 实体类 不写 @TableId | @TableName("t_log") public class LogPO{ @TableField("user_id") private Long userId; ... } | 一旦写 @TableId 启动即报错 |
无主键还能用 MP 吗 | 只能自己写 SQL | @Mapper interface LogMapper{ @Select("SELECT * FROM t_log WHERE user_id=#{uid}") List<LogPO> listByUser(Long uid); } | selectById /updateById 等 全失效 |
插入/删除/更新 | 同上,手写方法 | @Insert("INSERT INTO t_log(...) VALUES (...)") int insertOne(LogPO po); | 无 |
枚举存数据库 | 数据库放 数字 | status tinyint | 用字符串会 报 Data too long |
枚举写法 | 给每个枚举加 code 并在 code 上打 @EnumValue | public enum Status{ @EnumValue private final int code; } | 打在 name() 上会被存字符串 |
实体字段 | 直接写 枚举类型 | private Status status; | 无需转换器 |
条件/插入/查询 | 传 枚举对象 即可 | .eq(FriendshipPO::getStatus, Status.PENDING) | 无 |
返回前端中文 | 再定义 desc 并用 @JsonValue | 与 MP 类型转换 无关 | 无 |
请求中参数转枚举类型
示例调用(GET 查询字符串)
前端只要传 字符串(大小写不敏感),Spring 会自动把 "PENDING"
/ "accepted"
转成对应的 FriendshipsPO.Status
枚举 , 举例:
GET /friendlist?page=1&pageSize=20&status=PENDING
SpringMVC 内部流程
"PENDING" → 找 FriendshipsPO.Status.valueOf("PENDING") → 得到 Status.PENDING
GET 参数传枚举 = 传字符串,Spring 自动 valueOf
转换;大小写不敏感,拼写错误即 400。
6、请求体参数的接收问题(请求体中变量很少)
① 前端传来的并不直接就是对象,因此需要@RequestBody来讲HTTP请求体反序列化为Java对象
② 即使请求体中的变量很少,也不能直接用变量名接收,前端传来的是一个“整体”
方式 | 示例 | 是否可行 | 说明 |
---|---|---|---|
单个 DTO | public void add(@RequestBody UserDTO dto) | ✅ 最常用 | 字段多、可校验 |
单个 Map | public void add(@RequestBody Map<String,Object> map) | ✅ 灵活 | 失去类型&校验 |
单个 String | public void add(@RequestBody String json) | ✅ 自己手动解析 | 极少用 |
多个变量 | public void add(@RequestBody Long userId, @RequestBody String name) | ❌ 启动即报错 | 只能有一个 @RequestBody |
混合接收 | public void add(@RequestBody UserDTO dto, @RequestParam Long timestamp) | ✅ | 请求体 + 查询/路径参数合法 |
7、什么时候用@Transaction
维度 | 必须加事务 | 不加事务 | 备注 |
---|---|---|---|
写≥1 次(INSERT/UPDATE/DELETE) | ✅ | ❌ | 包括先查后写、批量写 |
先写后读(读依赖写结果) | ✅ | ❌ | 如写主键后立即读自增 ID |
跨表写(多表先后更新) | ✅ | ❌ | 好友表+消息表、订单表+库存表 |
跨服务写(本地事务范畴) | ✅ | ❌ | 同一 JVM 内多 Mapper 调用 |
只读 | ❌ | ✅ | 纯查询、报表、导出 |
只读但调了写方法 | ✅ | ❌ | 被调方法已加事务,照样传播进来 |
批量操作(for 循环写) | ✅ | ❌ | 循环内任何一条失败需整体回滚 |
含有“状态机”判断再写 | ✅ | ❌ | 如先判断余额≥金额再扣款 |
8、