3、核心注解:@TableName、@TableId、@TableField、@TableLogic详解

好,咱们直接进入正题。MyBatis-Plus 之所以能让你少写那么多代码,核心秘密就在这几个注解上。说白了,它们就是 MP 和数据库表之间的「翻译官」。你想想看,没有这些注解,MP 怎么知道你的 User 类对应哪张表?哪个字段是主键?

我个人习惯,拿到一个新项目,第一件事就是先把实体类的注解配好。这一步做好了,后面写代码就跟开了挂一样顺畅。

3.1 @TableName:实体类与表的映射

这个注解最简单,也最常用。它的作用就是告诉 MP:我这个 Java 类,对应数据库里的哪张表。

核心作用:建立实体类与数据库表的映射关系。

@TableName("t_user")
public class User {
    private Long id;
    private String name;
    // ...
}

嗯,这里要注意几个细节。如果你不写 @TableName,MP 会默认用类名作为表名。比如 User 类就对应 user 表。但实际项目中,表名往往有前缀,比如 t_user、sys_user 之类的。这时候就必须显式指定。

我的小技巧:如果你们团队的表命名规范统一,比如都是 t_ 开头,可以在全局配置里设置 tablePrefix,这样就不用每个实体类都写 @TableName 了。

// 全局配置
mybatis-plus:
  global-config:
    db-config:
      table-prefix: t_

我在项目中遇到过一个问题:有个同事没加 @TableName,结果 MP 把 User 类映射到了 user 表,但实际表名是 sys_user。查了半天才发现是注解漏了。所以,我建议你养成习惯,每个实体类都显式加上 @TableName,哪怕表名和类名一样。

3.2 @TableId:主键的指定

主键是每张表的灵魂。@TableId 就是用来告诉 MP,哪个字段是主键,以及主键的生成策略是什么。

@TableName("t_user")
public class User {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    private String name;
    // ...
}

这里有两个属性需要你关注:

属性 说明 常用值
value 指定数据库表中的主键字段名 "id"、"user_id" 等
type 主键生成策略 IdType.AUTO、IdType.ASSIGN_ID 等

关于主键策略,我重点说几个常用的:

  • IdType.AUTO:数据库自增。适合 MySQL 的 auto_increment,简单粗暴。
  • IdType.ASSIGN_ID:使用雪花算法生成 ID。适合分布式场景,我现在的项目都在用这个。
  • IdType.INPUT:自己输入。适合有业务含义的主键,比如订单号。

避坑指南:我曾经在一个老项目里看到有人用 IdType.NONE,结果插入数据时主键一直为空,报错报得莫名其妙。后来才发现,NONE 表示「不设置」,需要你自己处理。所以,千万别偷懒,一定要显式指定策略。

另外,如果你的实体类里主键字段名和数据库字段名一致,比如都是 id,那 value 属性可以省略。但我不建议这么做,显式写出来更清晰,也方便后来人维护。

3.3 @TableField:非主键字段的映射

这个注解最灵活,也最容易出问题。它用来处理那些「不按套路出牌」的字段映射。

@TableName("t_user")
public class User {
    @TableId
    private Long id;
    
    @TableField("user_name")
    private String name;
    
    @TableField(select = false)
    private String password;
    
    @TableField(exist = false)
    private String remark;
    
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
}

你看,@TableField 能干的事情很多:

属性 说明 使用场景
value 指定数据库字段名 Java 字段名和数据库字段名不一致时
exist 是否为数据库字段 实体类中有额外属性,但数据库没有对应字段
select 是否参与查询 密码、敏感信息等不想被查出来
fill 自动填充策略 创建时间、更新时间等

嗯,这里我要重点说一下 exist 属性。我在项目中经常看到有人把一些计算字段、临时变量放在实体类里,但又没加 @TableField(exist = false)。结果 MP 执行 SQL 时,就会报「字段不存在」的错误。你想想看,多冤啊。

我的习惯:只要实体类里有非数据库字段,我第一时间就加上 @TableField(exist = false)。这已经成了我的肌肉记忆了。

还有一个 select 属性,我特别喜欢。比如用户表里的 password 字段,正常情况下查询列表时不应该返回。设置 select = false 后,MP 生成的查询 SQL 会自动排除这个字段,省得你每次都要手动处理。

3.4 @TableLogic:逻辑删除的开关

逻辑删除,说白了就是「假删除」。数据还在数据库里,只是标记为已删除。这个注解就是用来配置这个标记字段的。

@TableName("t_user")
public class User {
    @TableId
    private Long id;
    
    private String name;
    
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;
}

这里有两个关键属性:

  • value:未删除时的值,默认是 0。
  • delval:已删除时的值,默认是 1。

配置好之后,MP 会自动帮你做两件事:

  1. 查询时自动加上 WHERE deleted = 0 条件。
  2. 删除时自动执行 UPDATE 语句,把 deleted 字段设为 1。

我曾经踩过的坑:有一次我忘了配置 @TableLogic,结果调用 deleteById 方法时,数据直接被物理删除了。还好有备份,不然就凉了。所以,我建议你只要涉及删除操作,优先考虑逻辑删除。除非你有明确的物理删除需求。

另外,逻辑删除的字段类型我建议用 Integer 或 tinyint,别用 String。用 String 的话,value 和 delval 要写成 "0" 和 "1",容易搞混。

3.5 注解的综合运用

好了,四个注解都讲完了。咱们来个完整的例子,看看它们怎么配合使用:

@TableName("t_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    @TableField("user_name")
    private String name;
    
    @TableField(select = false)
    private String password;
    
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    
    @TableField(fill = FieldFill.UPDATE)
    private Date updateTime;
    
    @TableLogic
    private Integer deleted;
    
    @TableField(exist = false)
    private String extraInfo;
}

你看,一个实体类配好这些注解,后面写 CRUD 基本就不用操心了。插入时自动填充创建时间,更新时自动填充更新时间,查询时自动过滤已删除的数据,密码字段默认不返回。多省心。

总结一下:

  • @TableName:告诉 MP 用哪张表。
  • @TableId:告诉 MP 主键是谁,怎么生成。
  • @TableField:处理字段映射、排除、自动填充等细节。
  • @TableLogic:开启逻辑删除,保护数据安全。

说实话,这几个注解我每天都在用。你只要把它们搞懂了,MyBatis-Plus 就算入门了。下一章咱们聊聊条件构造器,那才是真正让你写 SQL 写到爽的东西。