MybatisPlus 系列2 —— 常用注解


上一章我们讲了 Mybatis Plus 系列1 —— 起步,这一章我们继续了解常用注解部分!

一. @TableName

表名注解


/**
 * 数据库表相关
 *
 * @author hubin, hanchunlin
 * @since 2016-01-23
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface TableName {

    /**
     * 实体对应的表名
     */
    String value() default "";

    /**
     * 配置此值将覆盖全局配置的 schema
     * @since 3.1.1
     */
    String schema() default "";

    /**
     * 是否保持使用全局的 tablePrefix 的值
     * 只生效于 既设置了全局的 tablePrefix 也设置了上面 #value() 的值
     * 如果是 false , 全局的 tablePrefix 不生效 
     * @since 3.1.1
     */
    boolean keepGlobalPrefix() default false;

    /**
     * 实体映射结果集,
     * 只生效与 mp 自动注入的 method
     */
    String resultMap() default "";

    /**
     * 是否自动构建 resultMap 并使用,
     * 只生效与 mp 自动注入的 method,
     * 如果设置 resultMap 则不会进行 resultMap 的自动构建并注入,
     * 只适合个别字段 设置了 typeHandler 或 jdbcType 的情况
     * @since 3.1.2
     */
    boolean autoResultMap() default false;

    /**
     * 需要排除的属性名
     * @since 3.3.1
     */
    String[] excludeProperty() default {};
}

这里我们挑几个常用的属性具体说明下

1. value属性作用

默认情况下,我们的实体类名会自动映射为表名,对应的规则会将驼峰的类名转为带下划线的表名,如:

CreditUser -> credit_user

有时会为了避免实体类名称过长,或者表名前缀只有一个字母(如:t_user,这种映射成对应的实体会出新一些意料之外的情况),我们会将实体命名成另外的名称,如将上面的实体类命名为User,这个时候,我们需要做的就是在User类上加上注解@TableName("credit_user")即可:

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@TableName("credit_user")
public class User extends BaseEntity {
    private String name;

    private Integer age;

    private String email;

    private String sex;
}
2. schema

这个应该会比较少用到,也看下效果:

首先,看一下我们数据库的配置,连接的schematest

spring:
  datasource:
    driver-class-name: org.h2.Driver
    schema: classpath:db/schema-h2.sql
    data: classpath:db/data-h2.sql
    url: jdbc:h2:mem:test
    username: test
    password: 123456

现在我们在User上添加注解:

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
// 添加注解,注意:这里数据库表名已经改为user了,不再是credit_userl了
@TableName(schema = "demo")
public class User extends BaseEntity {
    private String name;

    private Integer age;

    private String email;

    private String sex;
}

运行测试方法:

@Test
 public void testSelect() {
     log.info("------ selectAll method test ------");
     List<User> userList = userMapper.selectList(null);
     userList.forEach(System.out::println);
 }

结果报错:

image-20210212180102625

可以看到已经去访问demo.user了。

3. autoResultMap

这个代码注释上也说明了,需要配和配合 typeHandler 或 numericScale使用,碰到对应的场景我们再进行说明。

4. excludeProperty

如果实体类上有属性不是表中的某个字段,我们就可以使用该属性来排除。

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@TableName(excludeProperty = "abcd")
public class User extends BaseEntity {
    private String name;

    private Integer age;

    private String email;

    private String sex;

    /**
     * 表中没有的字段 
     */
    private String abcd;
}

继续执行上面的测试方法:

image-20210212180738347

可以看到select语句并没有包含abcd字段,同时插入、更新也会忽略该字段,这里就不演示了。

要排除某个字段,我这里还是推荐在对应字段上使用@TableField(exist = false),因为@TableName(excludeProperty = "abcd")需要维护字符串abcd,不是类型安全的,使用TableField如下:

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
// 这里不需要 @TableName(excludeProperty = "abcd") 了
public class User extends BaseEntity {
    private String name;

    private Integer age;

    private String email;

    private String sex;

    /**
     * 忽略该字段
     */
    @TableField(exist = false)
    private String abcd;
}

二. @TableId

主键注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {

    /**
     * 字段名(该值可无)
     */
    String value() default "";

    /**
     * 主键类型
     * {@link IdType}
     */
    IdType type() default IdType.NONE;
}

type取值情况:

描述
AUTO 数据库ID自增
NONE 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT 用户输入ID,insert前自行set主键值
ASSIGN_ID 以下3种类型、只有当插入对象ID 为空,才自动填充分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID 分配UUID (主键类型为 string) 默认实现类 com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator(UUID.replace(“-“,””))

在我们的 Mybatis Plus 起步例子里,我们使用的是AUTO,因为我们的数据库表设置了自增属性:

image-20210212182547539

image-20210212182647716

测试:

@Test
   public void testInsert() {
       // 这里没有设置id值
       User user = new User();
       user.setName("自增");
       user.setAge(22);
       user.setEmail("22@qq.com");

       userMapper.insert(user);
       final List<User> list = userRepository.lambdaQuery()
               .list();
       System.out.println(list);
   }

结果:

image-20210212182951910

三. @TableField

字段注解(非主键)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableField {

    /**
     数据库字段值
     不需要配置该值的情况:
     当 com.baomidou.mybatisplus.core.MybatisConfiguration.mapUnderscoreToCamelCase 为 true 时, (mp下默认是          true,mybatis默认是false), 数据库字段值.replace("_","").toUpperCase() == 实体属性名.toUpperCase()
     当 com.baomidou.mybatisplus.core.MybatisConfiguration.mapUnderscoreToCamelCase 为 false 时, 数据库字段  值.toUpperCase() == 实体属性名.toUpperCase()
     */
    String value() default "";

    /**
     * 是否为数据库表字段
     * 默认 true 存在,false 不存在
     */
    boolean exist() default true;

    /**
     * 字段 where 实体查询比较条件
     * 默认 {@link SqlCondition.EQUAL}
     */
    String condition() default "";

    /**
    字段 update set 部分注入, 该注解优于 el 注解使用
例1:@TableField(.. , update="%s+1") 其中 %s 会填充为字段 输出 SQL 为:update 表 set 字段=字段+1 where ...
例2:@TableField(.. , update="now()") 使用数据库时间 输出 SQL 为:update 表 set 字段=now() where ...
     */
    String update() default "";

    /**
     * 字段验证策略之 insert: 当insert操作时,该字段拼接insert语句时的策略
     * IGNORED: 直接拼接 insert into table_a(column) values (#{columnProperty});
     * NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
     * NOT_EMPTY: insert into table_a(<if test="columnProperty != null and columnProperty!=''">column</if>) values (<if test="columnProperty != null and columnProperty!=''">#{columnProperty}</if>)
     * NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL
     * @since 3.1.2
     */
    FieldStrategy insertStrategy() default FieldStrategy.DEFAULT;

    /**
     * 字段验证策略之 update: 当更新操作时,该字段拼接set语句时的策略
     * <p>
     * IGNORED: 直接拼接 update table_a set column=#{columnProperty}, 属性为null/空string都会被set进去
     * NOT_NULL: update table_a set <if test="columnProperty != null">column=#{columnProperty}</if>
     * NOT_EMPTY: update table_a set <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
     * NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL
     *
     * @since 3.1.2
     */
    FieldStrategy updateStrategy() default FieldStrategy.DEFAULT;

    /**
     * 字段验证策略之 where: 表示该字段在拼接where条件时的策略
     * IGNORED: 直接拼接 column=#{columnProperty}
     * NOT_NULL: <if test="columnProperty != null">column=#{columnProperty}</if>
     * NOT_EMPTY: <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
     * NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL
     *
     * @since 3.1.2
     */
    FieldStrategy whereStrategy() default FieldStrategy.DEFAULT;

    /**
     * 字段自动填充策略
     * 在对应模式下将会忽略 insertStrategy 或 updateStrategy 的配置,等于断言该字段必有值
     */
    FieldFill fill() default FieldFill.DEFAULT;

    /**
     * 是否进行 select 查询
     * 大字段可设置为 false 不加入 select 查询范围
     */
    boolean select() default true;

    /**
     * 是否保持使用全局的 columnFormat 的值
     * 只生效于 既设置了全局的 columnFormat 也设置了上面 {@link #value()} 的值
     * 如果是 false , 全局的 columnFormat 不生效
     *
     * @since 3.1.1
     */
    boolean keepGlobalFormat() default false;

    /**
     * JDBC类型 (该默认值不代表会按照该值生效),
     * 只生效与 mp 自动注入的 method,
     * 建议配合 {@link TableName#autoResultMap()} 一起使用
     * {@link ResultMapping#jdbcType} and {@link ParameterMapping#jdbcType}
     *
     * @since 3.1.2
     */
    JdbcType jdbcType() default JdbcType.UNDEFINED;

    /**
     * 类型处理器 (该默认值不代表会按照该值生效),
     * 只生效与 mp 自动注入的 method,
     * 建议配合 {@link TableName#autoResultMap()} 一起使用
     * {@link ResultMapping#typeHandler} and {@link ParameterMapping#typeHandler}
     *
     * @since 3.1.2
     */
    Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;

    /**
     * 只在使用了 {@link #typeHandler()} 时判断是否辅助追加 javaType
     * <p>
     * 一般情况下不推荐使用
     * {@link ParameterMapping#javaType}
     *
     * @since 3.4.0 @2020-07-23
     */
    boolean javaType() default false;

    /**
     * 指定小数点后保留的位数,
     * 只生效于 mp 自动注入的 method,
     * 建议配合 {@link TableName#autoResultMap()} 一起使用
     * {@link ParameterMapping#numericScale}
     *
     * @since 3.1.2
     */
    String numericScale() default "";
}

这个注解是属性最多,也最复杂的一个。我们也挑几个常用的属性具体说明下

1. value

数据库字段名,如果实体类里面的字段名称不想和数据库”一致”,可以使用value指定数据库字段名称。

比如:数据库有字段is_delete,但是在实体类里面你不想使用private Boolean isDelete;你想使用private Boolean deleted;

我们不修改表结构,直接如下操作,可使程序正常运行:

@TableField("is_delete")
private Boolean deleted;
2. exist

是否为数据库表字段,比较简单,在说@TableName也举例过,这里不做过多说明了!

3. insertStrategy

字段验证策略之 insert: 当insert操作时,该字段拼接insert语句时的策略

  • IGNORED: 直接拼接 insert into table_a(column) values (#{columnProperty});
  • NOT_NULL: insert into table_a(column) values (#{columnProperty})
  • NOT_EMPTY: insert into table_a(column) values (#{columnProperty}) NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL
  • DEFAULT:默认的,一般只用于注解里,1. 在全局里代表 NOT_NULL 2. 在注解里代表 跟随全局
  • NEVER:不加入 SQL

默认情况下,会根据全局配置生效,全局配置默认为NOT_NULL

mybatis-plus:
  global-config:
    db-config:
      insert-strategy: not_null # 默认就是 not_null

在不加注解情况下,我们跑一下测试方法:

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
    private String name;

    private Integer age;

    private String email;

    private String sex;
}

// 测试
@Test
public void testInsert() {
  User user = new User();
  user.setName("自增");
  user.setAge(22);
  user.setEmail("22@qq.com");

  // 插入
  userMapper.insert(user);
}

结果:

==>  Preparing: INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
==> Parameters: 自增(String), 22(Integer), 22@qq.com(String)
<==    Updates: 1

由于我们的全局插入策略是:insert-strategy: not_nullsex字段值为null,所以插入时就忽略了。

那我修改一下全局策略看看结果:

情况一:

mybatis-plus:
  global-config:
    db-config:
      insert-strategy: IGNORED # 默认就是 not_null
-- 虽然报错了(由于有些字段设置了not null),但是sql已经打印出来了:

INSERT INTO user (name,
                  age,
                  email,
                  sex,
                  is_delete,
                  create_time,
                  update_time)
VALUES (?,
        ?,
        ?,
        ?,
        ?,
        ?,
        ?)

情况二:

mybatis-plus:
  global-config:
    db-config:
      insert-strategy: NOT_EMPTY # 默认就是 not_null
@Test
public void testInsert() {
  User user = new User();
  user.setName("自增");
  user.setAge(22);
  user.setEmail("22@qq.com");
  // 设置为字符串
  user.setSex("");

  // 插入
  userMapper.insert(user);
}
-- 结果 同样忽略了 sex 字段
==>  Preparing: INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
==> Parameters: 自增(String), 22(Integer), 22@qq.com(String)
<==    Updates: 1

情况三:

mybatis-plus:
  global-config:
    db-config:
      insert-strategy: NEVER # 根本不会这么设置,做一下演示
@Test
public void testInsert() {
  User user = new User();
  user.setName("自增");
  user.setAge(22);
  user.setEmail("22@qq.com");
  // 设置为字符串
  user.setSex("");

  // 插入
  userMapper.insert(user);
}
-- 结果 直接报错
### Error updating database.  Cause: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "INSERT INTO USER    VALUES[*]"; expected "ROW, (, DEFAULT, NOT, EXISTS, INTERSECTS, UNIQUE"; SQL statement:
INSERT INTO user    VALUES [42001-200]

到此我们先恢复,全局的默认配置:

mybatis-plus:
  global-config:
    db-config:
      insert-strategy: not_null # 默认就是 not_null

然后测试一下@TableFiledinsertStrategy属性(该配置比全局配置优先级高)

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
    private String name;

    private Integer age;

    private String email;

    private String sex;
}

@Test
public void testInsert() {
  User user = new User();
  user.setName("自增");
  user.setAge(22);
  user.setEmail("22@qq.com");
  user.setSex("");

  // 插入
  userMapper.insert(user);
}

结果,sexsql中:

==>  Preparing: INSERT INTO user ( name, age, email, sex ) VALUES ( ?, ?, ?, ? )
==> Parameters: 自增(String), 22(Integer), 22@qq.com(String), (String)
<==    Updates: 1

设置@TableField(insertStrategy = FieldStrategy.NOT_EMPTY),如下:

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
    private String name;

    private Integer age;

    private String email;

    @TableField(insertStrategy = FieldStrategy.NOT_EMPTY)
    private String sex;
}

@Test
public void testInsert() {
  User user = new User();
  user.setName("自增");
  user.setAge(22);
  user.setEmail("22@qq.com");
  user.setSex("");

  // 插入
  userMapper.insert(user);
}

结果,sex不在sql中了:

==>  Preparing: INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
==> Parameters: 自增(String), 22(Integer), 22@qq.com(String)
<==    Updates: 1
4. updateStrategy

字段验证策略之 update,在 update 的时候的字段验证策略,可参考insertStrategy.

5. whereStrategy

表示该字段在拼接where条件时的策略,可参考insertStrategy.

6. fill

字段自动填充策略

通常在开发中,我们每个表中可能都有两个字段表示创建时间和更新时间,如:create_time,update_time。为了避免每次新增或修改的时候给这两个字段赋值,我们可能会采用数据库默认值,对于更新时间,mysql可以采用on update CURRENT_TIMESTAMP实现。说下我们之前使用mysql时,是通过数据库自身功能实现的,sql如下:

image-20210212194602581

这样在新增,修改表时就可以不关注这两个字段了,但是有一点on update CURRENT_TIMESTAMP并不是所有数据库都支持,我们测试例子中使用的h2就不支持。

要实现这个效果,我们就可以使用mybatis plus的自动填充功能,下面开始测试:

  1. 修改schema-h2.sql去掉create_timeupdate_time的默认值

    CREATE TABLE user
    (
        id          BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
        name        VARCHAR(30) NOT NULL DEFAULT '' COMMENT '姓名',
        age         INT(11) NULL DEFAULT NULL COMMENT '年龄',
        email       VARCHAR(50) NOT NULL DEFAULT '' COMMENT '邮箱',
        sex         varchar(10) NOT NULL DEFAULT '' COMMENT '性别',
    
        is_delete   TINYINT              default 0 not null comment '是否删除',
        create_time timestamp            not null comment '创建时间',
        update_time timestamp            not null comment '更新时间',
        PRIMARY KEY (id)
    );
    
    -- 注意data-h2.sql文件里的初始化sql先注释掉
  2. 在相关字段上添加注解

    @Data
    public class BaseEntity {
        /**
         * 数据库设置了 ID自增
         */
        @TableId(type = IdType.AUTO)
        private Long id;
    
        /**
         * 逻辑删除字段
         */
        private Boolean isDelete;
     
    
        /**
         * 创建时间  ====== 插入时填充字段 ====== 
         */
        @TableField(fill = FieldFill.INSERT)
        private LocalDateTime createTime;
    
        /**
         * 更新时间 ====== 插入和更新时填充字段 ======
         */
        @TableField(fill = FieldFill.INSERT_UPDATE)
        private LocalDateTime updateTime;
    
    }
    
    @Data
    @ToString(callSuper = true)
    @EqualsAndHashCode(callSuper = true)
    public class User extends BaseEntity {
        private String name;
    
        private Integer age;
    
        private String email;
    
        private String sex;
    }
  3. 实现MetaObjectHandler

    @Slf4j
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
    
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("====== start insert fill ....");
            // 严格填充,只针对非主键的字段,只有该表注解了fill 并且 字段名和字段属性 能匹配到才会进行填充(严格模式填充策略,默认有值不覆盖,如果提供的值为null也不填充)
            this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
            this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("====== start update fill ....");
            // 严格填充,只针对非主键的字段,只有该表注解了fill 并且 字段名和字段属性 能匹配到才会进行填充(严格模式填充策略,默认有值不覆盖,如果提供的值为null也不填充)
            this.strictUpdateFill(metaObject, "updateTime",  LocalDateTime.class, LocalDateTime.now());
        }
    }
  4. 单元测试

    @Test
    public void testInsert() {
      User user = new User();
      user.setName("自增");
      user.setAge(22);
      user.setEmail("22@qq.com");
    
      // 插入
      userMapper.insert(user);
    
      // 查询结果
      userRepository.lambdaQuery().list();
    }

    结果:

    image-20210212201347787

可以看到已经默认赋值了。

四. @EnumValue

后面再单独举例子讲

五. @Version

乐观锁注解、标记 @Verison 在字段上,后面再单独举例子讲

六. @TableLogic

表字段逻辑处理注解(逻辑删除),这个我用的较少,都是自己代码处理!


文章作者: shiv
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 shiv !
评论
  目录