类型处理器,用于
JavaType
与JdbcType
之间的转换,用于PreparedStatement
设置参数值和从ResultSet
或CallableStatement
中取出一个值,本文讲解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
只是帮你注入了常用crud
到mybatis
里 注入之前可以说是动态的(根据你entity
的字段以及注解变化而变化),但是注入之后是静态的(等于你写在xml的东西) 而对于直接指定typeHandler
,mybatis
只支持你写在2个地方:
- 定义在
resultMap
里,只作用于select
查询的返回结果封装- 定义在
insert
和update
sql的#{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=篮球;))
可见现在一切都按预期运行了!
三. 总结
整个过程需要三步:
- 自定义字段处理类型(继承
BaseTypeHandler
) - 在对应字段上添加
@TableField(typeHandler = xxxxxx.class)
- 在实体类上添加
@TableName(autoResultMap = true)
,保证查询结果正确