背景
做博客系统时,用 MyBatis-Plus 代码生成器生成 article_tag(文章-标签关联表)的实体。
结果应用启动直接报错:
com.baomidou.mybatisplus.core.exceptions.MybatisPlusException:
@TableId can't more than one in Class: "com.blog.entity.ArticleTag".
翻遍代码,我明明一个 @TableId 都没手动加——都是生成器自动生成的。
现象
建表 SQL:
CREATE TABLE `article_tag` (
`article_id` BIGINT NOT NULL COMMENT '文章ID',
`tag_id` BIGINT NOT NULL COMMENT '标签ID',
PRIMARY KEY (`article_id`, `tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章标签关联表';
联合主键(article_id + tag_id)。
生成器生成的实体:
@TableName("article_tag")
public class ArticleTag {
@TableId("article_id")
private Long articleId;
@TableId("tag_id")
private Long tagId;
}
两个 @TableId——MP 只允许一个主键,直接抛异常。
原因
MyBatis-Plus 不支持联合主键。
生成器的逻辑是"扫描数据库表的主键列,把主键列标上 @TableId"。
遇到联合主键时,information_schema 会返回两行主键列,生成器不会去重,于是给两个字段都标了 @TableId。
结果:MP 初始化 TableInfo 时发现有两个 @TableId,抛 can't more than one 异常。
解决
把 @TableId 改成 @TableField,去掉主键标识:
package com.blog.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* 文章标签关联表(联合主键,MP 不支持联合主键,故不用 @TableId)
*/
@Getter
@Setter
@TableName("article_tag")
public class ArticleTag implements Serializable {
private static final long serialVersionUID = 1L;
@TableField("article_id")
private Long articleId;
@TableField("tag_id")
private Long tagId;
}
改完启动正常。
那"没有主键"会不会有问题?
会有一个警告,但不影响功能:
WARN: Can not find table primary key in Class: "com.blog.entity.ArticleTag".
WARN: Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
含义:MP 提示你不能用 getById / removeById 这类"按主键"的方法。
这正好符合预期——article_tag 是联合主键,本来就不该用单主键方法。
正确用法是按条件删:
// 删某篇文章的所有标签关联
articleTagService.lambdaUpdate()
.eq(ArticleTag::getArticleId, articleId)
.remove();
// 删某篇文章的某个标签
articleTagService.lambdaUpdate()
.eq(ArticleTag::getArticleId, articleId)
.eq(ArticleTag::getTagId, tagId)
.remove();
完全够用。
举一反三
所有"联合主键表"都会踩这个坑:
| 表 | 联合主键 |
|---|---|
article_tag | (article_id, tag_id) |
user_role | (user_id, role_id) |
order_item | (order_id, product_id) |
friend | (user_id, friend_id) |
处理方式:
- 生成后手动改实体,把所有
@TableId改成@TableField; - 或者干脆不用生成器,手写实体(4 行代码,很快);
- 或者在建表时加一个自增
id作为主键,联合字段加唯一索引——这样就没坑了:
CREATE TABLE `article_tag` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`article_id` BIGINT NOT NULL,
`tag_id` BIGINT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_article_tag` (`article_id`, `tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
最后这种方案最省事——用自增主键代替联合主键,唯一性靠唯一索引保证。
总结
- MyBatis-Plus 不支持联合主键,生成器会给所有主键列都加
@TableId; - 生成后要手动检查,把
@TableId改成@TableField; - "没有主键"只会触发警告,不影响
save/lambdaQuery/lambdaUpdate; - 更省事的方案:关联表也加一个自增
id,联合字段加唯一索引。
建议:新项目里所有表都加自增 id 主键,别用联合主键——省心。