【DB系列】SpringBoo系列Mybatis之自定义类型转换TypeHandler

文章目录
  1. I. 环境准备
    1. 1. 数据库准备
    2. 2. 项目环境
  2. II. 实例演示
    1. 1. entity定义
    2. 2. Mapper测试接口
    3. 3. 类型转换
    4. 4. TypeHandler注册
      1. 4.1 result标签中指定
      2. 4.2 SqlSessionFactory全局配置
      3. 4.3 全局xml配置
    5. 4.4 SpringBoot配置方式
    6. 5. 小结
  3. III. 不能错过的源码和相关知识点
    1. 0. 项目
    2. 1. 一灰灰Blog

在使用mybatis进行db操作的时候,我们经常会干的一件事情就是将db中字段映射到java bean,通常我们使用ResultMap来实现映射,通过这个标签可以指定两者的绑定关系,那么如果java bean中的字段类型与db中的不一样,应该怎么处理呢?

如db中为timestamp, 而java bean中定义的却是long

  • 通过BaseTypeHandler来实现自定义的类型转换

I. 环境准备

1. 数据库准备

使用mysql作为本文的实例数据库,新增一张表

1
2
3
4
5
6
7
8
9
10
CREATE TABLE `money` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
`money` int(26) NOT NULL DEFAULT '0' COMMENT '钱',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0',
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

2. 项目环境

本文借助 SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

pom依赖如下

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>

db配置信息 application.yml

1
2
3
4
5
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password:

II. 实例演示

1. entity定义

注意上面case中的create_atupdate_at的类型都是timestmap,我们定义的Entity如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
public class MoneyPo {
private Integer id;

private String name;

private Long money;

private Integer isDeleted;

private Timestamp createAt;

private Long updateAt;
}

2. Mapper测试接口

定义一个简单的查询接口,这里直接使用注解的方式(至于xml的写法差别也不大)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 主键查询
*
* @param id id
* @return {@link MoneyPo}
*/
@Select("select * from money where id = #{id}")
@Results(id = "moneyResultMap", value = {
@Result(property = "id", column = "id", id = true, jdbcType = JdbcType.INTEGER),
@Result(property = "name", column = "name", jdbcType = JdbcType.VARCHAR),
@Result(property = "money", column = "money", jdbcType = JdbcType.INTEGER),
@Result(property = "isDeleted", column = "is_deleted", jdbcType = JdbcType.TINYINT),
@Result(property = "createAt", column = "create_at", jdbcType = JdbcType.TIMESTAMP),
// @Result(property = "updateAt", column = "update_at", jdbcType = JdbcType.TIMESTAMP)})
@Result(property = "updateAt", column = "update_at", jdbcType = JdbcType.TIMESTAMP, typeHandler = Timestamp2LongHandler.class)})
MoneyPo getById(@Param("id") int id);

// 关于 SelectProvider 的使用,后面再说,主要是动态sql的演示
@SelectProvider(type = MoneyService.class, method = "getByIdSql")
@ResultMap(value = "moneyResultMap")
MoneyPo getByIdForProvider(@Param("id") int id);

说明:

  • @Results: 这个注解与 ResultMap 标签效果一致,主要用于定义db的字段与java bean的映射关系
  • id = "moneyResultMap" 这个id定义,可以实现@Results的复用
  • @Result: 关注下updateAt的typeHandler,这里指定了自定义的TypeHandler,来实现JdbcType.TEMSTAMP与Java Bean中的long的转换

3. 类型转换

自定义类型转换,主要是继承BaseTypeHandler类,泛型的类型为Java Bean中的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 自定义类型转换:将数据库中的日期类型,转换成long类型的时间戳
*
* 三种注册方式:
* 1.直接在 result标签中,指定typeHandler,如@Result(property = "updateAt", column = "update_at", jdbcType = JdbcType.TIMESTAMP, typeHandler = Timestamp2LongHandler.class)
* 2.在SqlSessionFactory实例中,注册 在SqlSessionFactory实例中.setTypeHandlers(new Timestamp2LongHandler());
* 3.xml配置,<typeHandler handler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>
*
* @author yihui
* @date 2021/7/7
*/
@MappedTypes(value = Long.class)
@MappedJdbcTypes(value = {JdbcType.DATE, JdbcType.TIME, JdbcType.TIMESTAMP})
public class Timestamp2LongHandler extends BaseTypeHandler<Long> {

/**
* 将java类型,转换为jdbc类型
*
* @param preparedStatement
* @param i
* @param aLong 毫秒时间戳
* @param jdbcType db字段类型
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Long aLong, JdbcType jdbcType) throws SQLException {
if (jdbcType == JdbcType.DATE) {
preparedStatement.setDate(i, new Date(aLong));
} else if (jdbcType == JdbcType.TIME) {
preparedStatement.setTime(i, new Time(aLong));
} else if (jdbcType == JdbcType.TIMESTAMP) {
preparedStatement.setTimestamp(i, new Timestamp(aLong));
}
}

@Override
public Long getNullableResult(ResultSet resultSet, String s) throws SQLException {
return parse2time(resultSet.getObject(s));
}

@Override
public Long getNullableResult(ResultSet resultSet, int i) throws SQLException {
return parse2time(resultSet.getObject(i));
}

@Override
public Long getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return parse2time(callableStatement.getObject(i));
}

private Long parse2time(Object value) {
if (value instanceof Date) {
return ((Date) value).getTime();
} else if (value instanceof Time) {
return ((Time) value).getTime();
} else if (value instanceof Timestamp) {
return ((Timestamp) value).getTime();
}
return null;
}
}
  • setNonNullParameter:将java类型,转换为jdbc类型
  • getNullableResult:将jdbc类型转java类型

4. TypeHandler注册

我们自己定义一个TypeHandler没啥问题,接下来就是需要它生效,一般来讲,有下面几种方式

4.1 result标签中指定

通过result标签中的typeHandler指定

使用xml的方式如

1
<result column="update_at" property="updateAt" jdbcType="TIMESTAMP" typeHandler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>

注解@Result的方式如

1
@Result(property = "updateAt", column = "update_at", jdbcType = JdbcType.TIMESTAMP, typeHandler = Timestamp2LongHandler.class)

4.2 SqlSessionFactory全局配置

上面的使用姿势为精确指定,如果我们希望应用到所有的场景,则可以通过SqlSessionFactory来实现

1
2
3
4
5
6
7
8
9
10
11
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(
// 设置mybatis的xml所在位置,这里使用mybatis注解方式,没有配置xml文件
new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
// 注册typehandler,供全局使用
bean.setTypeHandlers(new Timestamp2LongHandler());
return bean.getObject();
}

4.3 全局xml配置

除上面case之外,还有一个就是借助mybatis-config.xml配置文件来注册,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//ibatis.apache.org//DTD Config 3.1//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 驼峰下划线格式支持 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<typeHandlers>
<typeHandler handler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>
</typeHandlers>
</configuration>

注意,使用上面的配置文件,需要在SpringBoot中指定如下配置,否则将不会生效

1
2
mybatis:
config-location: classpath:mybatis-config.xml

4.4 SpringBoot配置方式

springboot配置文件,可以通过指定type-handlers-package来注册TypeHandler

1
2
mybatis:
type-handlers-package: com.git.hui.boot.mybatis.handler

5. 小结

本文主要介绍db中的类型与java bean中类型的映射适配策略,主要是通过继承BaseTypeHandler来实现自定义的类型转化

要使用自定义的TypeHandler,有全局生效与精确指定两种方式

  • @Result/<result>标签中,通过typeHandler指定
  • SqlSessionFactory 全局设置typeHandler
  • mybatis-config.xml 配置文件设置typeHandlers

此外本文的配置中,还支持了驼峰与下划线的互转配置,这个也属于常见的配置,通过在mybatis-config中如下配置即可

1
<setting name="mapUnderscoreToCamelCase" value="true"/>

接下来问题来了,驼峰可以和下划线互转,那么有办法实现自定义的name映射么,如果有知道的小伙伴,请不吝指教

III. 不能错过的源码和相关知识点

0. 项目

mybatis系列博文

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到