Mybatis Plus 系列6 —— 字段类型处理器


类型处理器,用于 JavaType JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSetCallableStatement 中取出一个值,本文讲解 mybaits-plus 内置常用类型处理器如何通过TableField注解快速注入到 mybatis 容器中。

在我们开发过程中,肯定遇到过将对象转成json然后存在数据库的场景,那么你是怎么做的,保存的时候,先手动转成json字符串再保存?查询的时候,在解析成对象?其实不管是mybatis plus还是mybatis都可以通过实现BaseTypeHandler自定义一个转换器来实现!而在mybaits plus中就提供了一个这样的转化器——JacksonTypeHandler,当然你完全可以自己实现!那么就来看看在mybatis plus中怎么使用吧!

一. 示例工程

1. build.gradle

plugins {
    id 'org.springframework.boot' version '2.3.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

group = 'cn.justmr.mybatis-plus.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    // 为了加快速度,配置阿里镜像仓库
    maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
    mavenCentral()
}

ext {
    set('mybaitsPlusVersion', "3.4.2")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    testCompileOnly 'org.projectlombok:lombok'
    // 这里采用内存数据库
    runtimeOnly 'com.h2database:h2'
    // 引入mybatis plus
    compile "com.baomidou:mybatis-plus-boot-starter:${mybaitsPlusVersion}"
    annotationProcessor 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}


test {
    useJUnitPlatform()
}

2. application.yml

spring:
  datasource:
    driver-class-name: org.h2.Driver
    # 启动应用后会自动执行该sql
    schema: classpath:db/schema-h2.sql
    url: jdbc:h2:mem:test
    username: test
    password: 123456
  h2:
    console:
      # 开启h2控制台
      enabled: true

# 这里根据需要配置
mybatis-plus:
  configuration:
  	# 打印日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启自动驼峰命名规则(camel case)映射,默认就是true
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      # 插入策略 忽略 null
      insert-strategy: not_null
      # 更新策略 忽略 null
      update-strategy: not_null
  # 处理枚举    
  type-enums-package: cn.**.enums

3. 初始化表结构

DROP TABLE IF EXISTS user;

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 '性别',
    other_info  varchar(2000) 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)
);

4. 实体

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

    private Integer age;

    private String email;

    private SexEnum sex;

    private OtherInfo otherInfo;

    @Data
    public static class OtherInfo {

        /**
         * 居住城市
         */
        private String city;

        /**
         * 爱好
         */
        private String hobby;

    }
}


@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;
}

5. UserMapper

package cn.justmr.mybatisplus.demo.mapper;

import cn.justmr.mybatisplus.demo.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper extends BaseMapper<User> {
}

二. 测试

1. 添加注解

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

    private Integer age;

    private String email;

    private SexEnum sex;

    // 添加注解
    @TableField(typeHandler = JacksonTypeHandler.class)
    private OtherInfo otherInfo;

    @Data
    public static class OtherInfo {

        /**
         * 居住城市
         */
        private String city;

        /**
         * 爱好
         */
        private String hobby;

    }
}

2. 测试方法

@Test
public void test() {
  User user = new User();
  user.setName("shiv");
  user.setAge(22);
  user.setEmail("22@qq.com");
  user.setSex(SexEnum.MAIL);

  User.OtherInfo otherInfo = new User.OtherInfo();
  otherInfo.setCity("300000");
  otherInfo.setHobby("篮球");
  user.setOtherInfo(otherInfo);

  // 插入
  userMapper.insert(user);

  // 查询结果
  User user1 = userMapper.selectById(user.getId());
  System.out.println(user1);

  // 更新
  User.OtherInfo otherInfo1 = new User.OtherInfo();
  otherInfo1.setHobby("篮球;");
  User u = new User();
  u.setOtherInfo(otherInfo1);
  ChainWrappers.lambdaUpdateChain(userMapper)
    .eq(User::getId, user.getId())
    .update(u);
  
  // 查询结果
  final User one = ChainWrappers.lambdaQueryChain(userMapper)
    .eq(User::getId, user.getId())
    .one();
  System.out.println(one);
}

结果:

// 1. 查询日志,可见插入效果达到了,但是查询出来otherInfo为null
==>  Preparing: INSERT INTO user ( name, age, email, sex, other_info, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
==> Parameters: shiv(String), 22(Integer), 22@qq.com(String), 20(Integer), {"city":"300000","hobby":"篮球"}(String), 2021-02-14T16:51:16.917(LocalDateTime), 2021-02-14T16:51:16.923(LocalDateTime)
<==    Updates: 1
User(super=BaseEntity(id=1, isDelete=false, createTime=2021-02-14T16:51:16.917, updateTime=2021-02-14T16:51:16.923), name=shiv, age=22, email=22@qq.com, sex=MAIL, otherInfo=null)

// 2. 更新日志,更新效果也达到了,但是查询出来的otherInfo为null
==>  Preparing: UPDATE user SET other_info=?, update_time=? WHERE (id = ?)
==> Parameters: {"city":null,"hobby":"篮球;"}(String), 2021-02-14T16:51:17.217(LocalDateTime), 1(Long)
<==    Updates: 1
User(super=BaseEntity(id=1, isDelete=false, createTime=2021-02-14T16:51:16.917, updateTime=2021-02-14T16:51:17.217), name=shiv, age=22, email=22@qq.com, sex=MAIL, otherInfo=null)

可以看到,插入、更新都达到了效果,但是查询却不在意料之中!关于这个效果,官网有段说明:

关于autoResultMap的说明:

mp会自动构建一个ResultMap并注入到mybatis里(一般用不上).下面讲两句: 因为mp底层是mybatis,所以一些mybatis的常识你要知道,mp只是帮你注入了常用crudmybatis里 注入之前可以说是动态的(根据你entity的字段以及注解变化而变化),但是注入之后是静态的(等于你写在xml的东西) 而对于直接指定typeHandler,mybatis只支持你写在2个地方:

  1. 定义在resultMap里,只作用于select查询的返回结果封装
  2. 定义在insertupdatesql的#{property}里的property后面(例:#{property,typehandler=xxx.xxx.xxx}),只作用于设置值 而除了这两种直接指定typeHandler,mybatis有一个全局的扫描你自己的typeHandler包的配置,这是根据你的property的类型去找typeHandler并使用.

3. 添加@TableName注解

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@TableName(autoResultMap = true) // 需要添加这个
public class User extends BaseEntity {
    private String name;

    private Integer age;

    private String email;

    private SexEnum sex;

    @TableField(typeHandler = JacksonTypeHandler.class)
    private OtherInfo otherInfo;

    @Data
    public static class OtherInfo {

        /**
         * 居住城市
         */
        private String city;

        /**
         * 爱好
         */
        private String hobby;

    }
}

然后再次测试:

==>  Preparing: INSERT INTO user ( name, age, email, sex, other_info, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
==> Parameters: shiv(String), 22(Integer), 22@qq.com(String), 20(Integer), {"city":"300000","hobby":"篮球"}(String), 2021-02-14T16:55:32.307(LocalDateTime), 2021-02-14T16:55:32.311(LocalDateTime)
<==    Updates: 1
User(super=BaseEntity(id=1, isDelete=false, createTime=2021-02-14T16:55:32.307, updateTime=2021-02-14T16:55:32.311), name=shiv, age=22, email=22@qq.com, sex=MAIL, otherInfo=User.OtherInfo(city=300000, hobby=篮球))

==>  Preparing: UPDATE user SET other_info=?, update_time=? WHERE (id = ?)
==> Parameters: {"city":null,"hobby":"篮球;"}(String), 2021-02-14T16:55:32.473(LocalDateTime), 1(Long)
<==    Updates: 1
User(super=BaseEntity(id=1, isDelete=false, createTime=2021-02-14T16:55:32.307, updateTime=2021-02-14T16:55:32.473), name=shiv, age=22, email=22@qq.com, sex=MAIL, otherInfo=User.OtherInfo(city=null, hobby=篮球;))

可见现在一切都按预期运行了!

三. 总结

整个过程需要三步:

  1. 自定义字段处理类型(继承BaseTypeHandler
  2. 在对应字段上添加@TableField(typeHandler = xxxxxx.class)
  3. 在实体类上添加@TableName(autoResultMap = true) ,保证查询结果正确

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