同事问我@TableField注解怎么用?我慌神了...💨💨

1,061 阅读11分钟

🏆本文收录于「滚雪球学Spring Boot」专栏,专业攻坚指数级提升持续更新中,up!up!up!!

📜 前言

  嘿,家人们,跟大家分享一下我的早报,一早我刚挤地铁匆忙来到工作岗位,我那新来的同事就从工位挪了过来,那时候经理还没来,她直接就贴我耳旁悄咪咪的问我:“@TableField注解怎么用?”,我一开始还以为自己百米冲刺导致幻听了,可当她拿我笔记本写给我看的时候,我慌了👻...既然同事有难,那我们就不能袖手旁观,与其任由同事坐等被裁,不如主动出击,授人予渔!帮她度过试用期。择日不如撞日,那今天我们就来聊聊它——@TableField ,顺便给大家也温习一遍,会的同学可以粗略过一遍,不会的同学特别是我那同事!认真看好好学,别走神,因为我只教这一遍。

  不可否认,它确实是一位能在实体类和数据库表字段之间“牵线搭桥”的得力助手,特别适合处理数据库和代码不完全对应的场景。😊你曾是否也遇到过数据库字段和实体类字段名称不匹配,或者希望实体类不映射某些数据库字段的情况?@TableField 就是你的不二之选!那么,本期的正文内容即将开始💪。

📚 目录

  1. 🧐 什么是@TableField
  2. 🎩 @TableField 注解基本用法
  3. ✍️ 实战演示:@TableField的多种应用场景
  4. 🔄 动态选择字段:@TableField(select = false)
  5. 📝 复杂场景中的 @TableField 配置
  6. 🛠️ @TableField@TableId 的区别
  7. 💡 最佳实践与注意事项
  8. 📈 总结

🧐 什么是@TableField

  @TableField 是MyBatisPlus中的一个注解,用来解决实体类字段和数据库表字段之间的映射问题。其实,说白了,它就是一个专门帮助我们处理“字段小麻烦”的注解。当数据库字段名和实体类属性名不一致时,或者有字段我们不想映射到数据库时,@TableField 就可以派上用场了!✨不信你试试看。

  比如,有些数据库字段命名习惯使用下划线风格,而在Java中通常驼峰命名法,这时候如果没有 @TableField 来进行适配,会写很多不必要的代码。接下来下面就来具体感受一下它的妙处所在吧!

🎩 @TableField 注解基本用法

  在 @TableField 注解中,最常用的属性就是value。它用于指定数据库中与实体类字段对应的列名。举个简单的例子,如果数据库中有一个名为user_name的字段,而在实体类中我们想叫它userName,这时候就可以用 @TableField 来完成映射:

@TableField("user_name")
private String userName;

  这样一来,MyBatisPlus在查询时会自动映射user_nameuserName,避免手动转换的麻烦,是不是很方便,以前我们可能要手写映射关系,现在一个注解直接搞定。

✍️ 实战演示:@TableField的多种应用场景

  既然了解了这个注解是干啥用的,接下来我们通过几个实际的代码示例,展示一下 @TableField 的多种灵活用法,让大家切身感受它的妙用之处。

1. 基本映射:数据库字段名与实体类字段名不一致

  假设有一个这样的场景,数据库中有如下表结构:

CREATE TABLE user (
    id BIGINT(20) NOT NULL AUTO_INCREMENT,
    user_name VARCHAR(30) NOT NULL,
    age INT(11),
    email VARCHAR(50),
    PRIMARY KEY (id)
);

  在实体类中,我们希望将 user_name 映射为 userName,那我们便可以直接使用该注解进行字段名映射:

public class User {
    private Long id;
    
    @TableField("user_name")
    private String userName;

    private Integer age;
    private String email;
}

  这样一来,我们就不需要在每次查询和保存时手动转换字段名,MyBatisPlus自己就能自动处理这些映射关系,助你解放双手。

2. 忽略字段:不映射数据库中的某些字段

  这个场景真是太常见了,有木有??我们不希望实体类中的某些字段映射到数据库,比如计算出的临时属性或客户端特有的字段。这时,我们就可以通过 @TableField(exist = false) 将这些字段标记为“非数据库字段”,这样既可直接以实体类返回且在做数据库交互时又不会映射到SQL语句中,示例代码如下:

public class User {
    private Long id;

    @TableField("user_name")
    private String userName;

    private Integer age;
    private String email;

    @TableField(exist = false) // 此字段不映射到数据库
    private String temporaryData;
}

  这样,temporaryData 就不会映射到数据库表中,不会影响数据库查询和更新,当你想给前端多返回一个额外的字段,例如temporaryData字段,这样只需要加这么一个注解便可直接搞定。

3. 插入或更新时忽略字段

  有时候,我们可能希望字段在插入时或更新时被忽略。比如createTime字段,我们只在插入时记录创建时间,不希望更新时更改它。这时可以使用@TableField(fill = FieldFill.INSERT)。示例代码如下:

@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

🔄 动态选择字段:@TableField(select = false)

  如果有些字段比较敏感,不希望在查询时被默认查出,比如密码字段,可以用@TableField(select = false)来排除它,防止不必要的暴露:

@TableField(select = false)
private String password;

  这样,当执行查询时,password 字段不会出现在查询结果中,但我们可以在特定需求时手动包含它,比如通过自定义查询。

📝 复杂场景中 @TableField 配置

  在实际开发中,我们可能会遇到复杂的数据库映射场景,以下是几个 @TableField 高级配置的实际应用示例:

1. 自动填充字段

  对于诸如createTimeupdateTime这样的字段,通常需要自动填充,我们可以通过@TableField(fill = FieldFill.INSERT_UPDATE)来自动完成插入和更新时的填充:

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

  MyBatisPlus会在数据插入或更新时自动赋值,省去手动设置的繁琐步骤。

具体这个大家还需要单独写个MP注入处理器,例如如下:

/**
 * bug菌
 * MP注入处理器
 */
@Slf4j
public class InjectionMetaObjectHandler implements MetaObjectHandler {

	//新增时自动插入
    @Override
    public void insertFill(MetaObject object) {
        try {
            if (ObjectUtil.isNotNull(object) && object.getOriginalObject() instanceof BaseEntity) {
                BaseEntity entity = (BaseEntity) object.getOriginalObject();
                Date date = ObjectUtil.isNotNull(entity.getCreateTime()) ? entity.getCreateTime() : new Date();
                String username = StrUtil.isNotBlank(entity.getCreateBy())
                        ? entity.getCreateBy() : LoginHelper.getNickNameNe();
                entity.setCreateBy(username);
                entity.setUpdateBy(username);
                entity.setCreateTime(date);
                entity.setUpdateTime(date);
            }
        } catch (Exception e) {
            throw new ServiceException("新增自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
        }
    }

	//更新时自动插入
    @Override
    public void updateFill(MetaObject object) {
        try {
            if (ObjectUtil.isNotNull(object) && object.getOriginalObject() instanceof BaseEntity) {
                BaseEntity entity = (BaseEntity) object.getOriginalObject();
                entity.setUpdateBy(LoginHelper.getUsernameNe());
                entity.setUpdateTime(new Date());
            }
        } catch (Exception e) {
            throw new ServiceException("修改自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
        }
    }
}

示例演示:

这里我将我本地项目直接起来,给大家调用一个接口进行演示一波。大家请看:

代码解析:

  如上我定义的这个InjectionMetaObjectHandler 类,它实现了一个基于 MyBatis-Plus 的自动注入处理器,用于实体对象在新增和更新时填充默认的公共字段,如创建时间、更新时间、创建人和更新人。下面是详细解析:

  首先我们需要定义一个 InjectionMetaObjectHandler 类且继承 MetaObjectHandler 接口,是一个 MyBatis-Plus 的元对象处理器。

  1. 新增自动填充逻辑:insertFill 方法 当进行插入操作时,填充实体对象的创建时间、更新时间、创建人等字段。

    逻辑步骤:

    • 判断 MetaObject 是否不为空,且其 getOriginalObject() 返回的对象是 BaseEntity 类型的实例。
    • 如果实体的 createTime 不为空,使用已有时间;否则,使用当前时间(new Date())。
    • 创建人字段 createBy
      • 如果实体中已经设置了值(非空字符串),则使用该值;
      • 否则调用 LoginHelper.getNickNameNe() 获取当前登录用户的昵称。
    • 设置 BaseEntity 的以下字段:
      • createByupdateBy 都设为创建人用户名。
      • createTimeupdateTime 都设为创建时间。

    异常处理:

    • 捕获异常并抛出自定义 ServiceException,同时指定 HTTP 状态码。
  2. 更新自动填充逻辑:updateFill 方法 当进行更新操作时,填充实体对象的更新时间和更新人。

    逻辑步骤:

    • 同样检查 MetaObject 是否不为空,且其 getOriginalObject() 返回的对象是 BaseEntity 类型的实例。
    • 更新人字段 updateBy
      • 直接调用 LoginHelper.getUsernameNe() 获取当前登录用户的用户名。
    • 更新时间字段 updateTime
      • 设为当前时间(new Date())。

    异常处理:

    • 捕获异常并抛出自定义 ServiceException,指定 HTTP 状态码。
  3. 工具类的使用

    • ObjectUtil:判断对象是否非空。
    • StrUtil:判断字符串是否非空白。
    • LoginHelper:获取当前登录用户的相关信息(如昵称、用户名)。
    • BaseEntity:是代码中表示数据库基础实体的类,包含了常见的公共字段,如创建时间、更新时间等。
    • ServiceException:自定义异常类,用于抛出业务异常。
    • HttpStatus.HTTP_UNAUTHORIZED:表示 HTTP 状态码 401(未授权)。

代码功能说明

  该类的核心目的是在 MyBatis-Plus 操作数据库时,通过自动填充机制统一处理实体类的公共字段,目的是为了减少手动赋值的繁琐步骤,确保数据的统一性和完整性,就凭这点,大家不用它都说不过去了吧。

  这种设计说实话非常适合业务系统中需要频繁记录“谁创建了数据”“谁更新了数据”等场景,且通过异常机制处理了可能的意外情况,提高了代码的健壮性,审计信息也不需要耦合进业务层面中去了,简直就是谁用谁爽,这真不是我吹,必须包的!

2. 动态查询控制

  如果有些字段只在特定条件下查询,我们可以通过@TableField(condition = SqlCondition.LIKE)设置字段的查询条件:

@TableField(condition = SqlCondition.LIKE)
private String email;

  这样,email 字段在查询时会自动使用LIKE查询,适合需要模糊查询的场景,这个场景倒是用得不多,毕竟MP提供的Wrapper条件构造器已经够全面了。

🛠️ @TableField@TableId 的区别

  虽然 @TableField@TableId 都用于字段的映射,但它们在功能上有着显著区别,如下:

  • @TableId:主要用于主键字段的映射,指定表中的主键列。
  • @TableField:用于非主键字段的映射,灵活配置字段属性。

  例如,如果id是主键,我们可以这样定义:

@TableId
private Long id;

  而其他字段则通过@TableField来管理,如userNameemail等。

💡 最佳实践与注意事项

  1. 保持字段命名一致:尽量在数据库和实体类中保持一致的命名风格,避免过多@TableField配置,减少维护成本。

  2. 灵活应用selectexist属性: 在查询时控制字段的选择,既能提高安全性,也能提升性能。

  3. 使用自动填充优化字段管理:对于需要自动更新的时间戳字段,尽量使用FieldFill.INSERT_UPDATE等自动填充功能。

  4. 定期更新数据库映射:如果数据库表结构变更,及时同步更新实体类和 @TableField 映射,确保映射关系正确。

📈 总结

  ok,说到这儿,本期即将要接近尾声啦,在结束之前,我来简单讲两句:@TableField 它是MyBatis-Plus提供的一个灵活且强大的注解,专为那些不希望或不方便直接映射的字段而生。通过灵活配置 @TableField注解,我们可以在数据库字段和Java对象之间创建清晰且稳定的映射关系,减少代码和数据库耦合度。

  在实际项目中,@TableField 的应用可以帮助我们更高效地进行代码维护和管理,减少不必要的代码输出。希望今天的分享能够让你对 @TableField 有一个更全面的理解,有需要的小伙伴们赶紧在你的项目中试试吧!🎉

  希望这篇理论+实战保姆级教程能够真正帮助到大家,真正掌握@TableField的妙用核心,能够在日常crud中“如鱼得水”!

📣 关于我

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿哇。

-End-