MyBatis-Plus 联合主键表生成两个 @TableId 的坑

5 阅读3分钟

背景

做博客系统时,用 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)

处理方式:

  1. 生成后手动改实体,把所有 @TableId 改成 @TableField;
  2. 或者干脆不用生成器,手写实体(4 行代码,很快);
  3. 或者在建表时加一个自增 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;

最后这种方案最省事——用自增主键代替联合主键,唯一性靠唯一索引保证。

总结

  1. MyBatis-Plus 不支持联合主键,生成器会给所有主键列都加 @TableId;
  2. 生成后要手动检查,把 @TableId 改成 @TableField;
  3. "没有主键"只会触发警告,不影响 save / lambdaQuery / lambdaUpdate;
  4. 更省事的方案:关联表也加一个自增 id,联合字段加唯一索引。

建议:新项目里所有表都加自增 id 主键,别用联合主键——省心。

参考