跳到主要内容SpringBoot+MyBatis Plus+PostgreSQL 整合常用数据类型(json、array)操作 | 极客日志Javajava
SpringBoot+MyBatis Plus+PostgreSQL 整合常用数据类型(json、array)操作
本文介绍如何在 SpringBoot 结合 MyBatis Plus 与 PostgreSQL 中处理常用数据类型。重点解决 JSON 和数组类型在 Java 实体与数据库间的映射问题,通过自定义 TypeHandler 实现 FastJSON 对象与 PostgreSQL JSON/JSONB 类型的转换,以及 List/Array 与 PostgreSQL 数组类型的映射。包含项目搭建、配置文件、TypeHandler 实现及接口测试示例。
Kubernet1 浏览 前言
- SpringBoot+MyBatis Plus+PostgreSQL 整合常用数据类型的使用基本上和 MySQL 使用差不多。
- 但是 PGSQL 中 Json 类型和数组类型并不能直接像 MySQL 那样,例如 MySQL 中 JSON 可以使用 String 类型存储和查询。
- 但是在 PGSQL 中,如果使用 String 接收数组类型,当插入数据的时候就会报错:
column "xxx" is of type json but expression is of type character varying
- 本文核心展示在整合使用时的常用数据类型使用以及 json 和数组的接口与插入等,并且可以根据解决方案拓展自定义类型。
注意:默认大家已经掌握 SpringBoot、MyBatis Plus、PostgreSQL 的基础使用。
项目搭建
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/>
</>
com.codecoord
springboot-postgresql
1.0
21
${java.version}
${java.version}
UTF-8
org.springframework.boot
spring-boot-starter-web
com.alibaba
fastjson
2.0.57
org.projectlombok
lombok
com.baomidou
mybatis-plus-spring-boot3-starter
3.5.14
com.alibaba
druid-spring-boot-starter
1.2.27
org.postgresql
postgresql
42.7.8
aliyun
aliyun
https://maven.aliyun.com/repository/public
true
true
org.springframework.boot
spring-boot-maven-plugin
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
parent
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
<properties>
<java.version>
</java.version>
<maven.compiler.source>
</maven.compiler.source>
<maven.compiler.target>
</maven.compiler.target>
<project.build.sourceEncoding>
</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>
</id>
<name>
</name>
<url>
</url>
<releases>
<enabled>
</enabled>
</releases>
<snapshots>
<enabled>
</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>
</groupId>
<artifactId>
</artifactId>
</plugin>
</plugins>
</build>
</project>
数据库脚本
DROP TABLE IF EXISTS data_type;
CREATE TABLE data_type (
id bigserial PRIMARY KEY,
tiny_int_col smallint,
small_int_col smallint,
integer_col integer,
big_int_col bigint,
decimal_col decimal(10,2),
numeric_col numeric(15,4),
real_col real,
double_precision_col doubleprecision,
char_col char(10),
varchar_col varchar(255),
text_col text,
date_col date,
time_col time,
timestamp_col timestamp,
timestamp_tz_col timestamptz,
boolean_col boolean,
json_col json,
jsonb_col jsonb,
array_col int[],
decimal_array_col decimal(10,2)[]
);
实体类
指定类型处理器,如果不指定就需要在配置文件中配置处理器扫描路径。
package com.codecoord.postgresql.domain;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.codecoord.postgresql.handler.ArrayTypeHandler;
import com.codecoord.postgresql.handler.JsonTypeHandler;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.util.List;
@Data
@TableName("public.data_type")
public class DataType implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
private Short tinyIntCol;
private Short smallIntCol;
private Integer integerCol;
private Long bigIntCol;
private BigDecimal decimalCol;
private BigDecimal numericCol;
private Float realCol;
private Double doublePrecisionCol;
private String charCol;
private String varcharCol;
private String textCol;
private LocalDate dateCol;
private LocalTime timeCol;
private LocalDateTime timestampCol;
private OffsetDateTime timestampTzCol;
private Boolean booleanCol;
private JSONObject jsonCol;
private JSONObject jsonbCol;
private List<Integer> arrayCol;
private BigDecimal[] decimalArrayCol;
}
配置文件
这里最重要的就是指定 TypeHandler 所在的包路径。
type-handlers-package: com.codecoord.postgresql.handler
server:
port: 80
spring:
datasource:
url: jdbc:postgresql://ip:5432/databasename
driver-class-name: org.postgresql.Driver
username: username
password: password
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 1
min-idle: 1
max-active: 10
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
id-type: auto
banner: false
type-handlers-package: com.codecoord.postgresql.handler
类型处理器
解决 Java 和数据库数据类型之间的匹配,实际上就是通过 TypeHandler 来告诉 MyBatis/MP 这个数据应该怎么处理。基本上所有的类型匹配都可以采用这种方式解决。
JSON 类型处理器
此处使用 fastjson 的 JSONObject 类型来作为 JSON 载体和 MyBatis/MP 类型处理器之间的载体。也可以自定义一个类型,比如下面的处理器只支持 JSONObject 和 Map 数据类型,如果有自己的新类型,按照下面的代码示例加入即可。
package com.codecoord.postgresql.handler;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.*;
import java.util.Map;
@MappedJdbcTypes(JdbcType.OTHER)
@MappedTypes({JSONObject.class, com.alibaba.fastjson2.JSONObject.class, Map.class})
@SuppressWarnings("unused")
public class JsonTypeHandler extends BaseTypeHandler<Object> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setObject(i, JSON.toJSONString(parameter), Types.OTHER);
}
@Override
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
return JSON.parse(rs.getString(columnName));
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return JSON.parse(rs.getString(columnIndex));
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return JSON.parse(cs.getString(columnIndex));
}
}
Array 类型处理器
package com.codecoord.postgresql.handler;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.sql.*;
import java.util.*;
@MappedJdbcTypes(JdbcType.ARRAY)
@SuppressWarnings("unused")
@MappedTypes({List.class, int[].class, long[].class, short[].class, Integer[].class, Short[].class, Long[].class, BigDecimal[].class})
public class ArrayTypeHandler extends BaseTypeHandler<Object> {
private boolean isListType;
private static final Map<Class<?>, String> TYPES = Map.of(
Short.class, "smallint",
Integer.class, "integer",
Long.class, "bigint",
short.class, "smallint",
int.class, "integer",
long.class, "bigint",
String.class, "text",
BigDecimal.class, "numeric"
);
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
Object[] array;
if (parameter instanceof List) {
array = ((List<?>) parameter).toArray();
} else if (parameter.getClass().isArray()) {
array = (Object[]) parameter;
} else {
throw new IllegalArgumentException("PG array type only supports Array or List");
}
Array pgArray = ps.getConnection().createArrayOf(resolvePgType(ps, parameter), array);
ps.setArray(i, pgArray);
}
private String resolvePgType(PreparedStatement ps, Object parameter) {
isListType = false;
Class<?> componentType;
if (parameter instanceof List<?> list) {
componentType = list.isEmpty() ? String.class : list.getFirst().getClass();
isListType = true;
} else {
componentType = parameter.getClass().getComponentType();
}
String pgType = TYPES.get(componentType);
if (StringUtils.hasLength(pgType)) {
return pgType;
}
throw new IllegalArgumentException("Unsupported PG array element type " + componentType);
}
@Override
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
return toJavaArray(rs.getArray(columnName));
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return toJavaArray(rs.getArray(columnIndex));
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return toJavaArray(cs.getArray(columnIndex));
}
private Object toJavaArray(Array array) throws SQLException {
if (Objects.isNull(array)) {
return null;
}
Object[] objects = (Object[]) array.getArray();
if (!isListType || ArrayUtils.isEmpty(objects)) {
return array.getArray();
}
return new ArrayList<>(Arrays.asList(objects));
}
}
接口代码
package com.codecoord.postgresql.controller;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.codecoord.postgresql.domain.DataType;
import com.codecoord.postgresql.service.DataTypeService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
@RestController
@RequestMapping("/sql")
public class PostgreSqlController {
@Resource
private DataTypeService dataTypeService;
@RequestMapping("/autoincrement")
public DataType autoincrement(@RequestParam(value = "id", required = false) Long id) {
DataType dataType = new DataType();
if (Objects.nonNull(id)) {
dataType.setId(id);
}
dataType.setVarcharCol("测试自增字段");
dataTypeService.save(dataType);
return dataTypeService.getById(dataType.getId());
}
@RequestMapping("/numeric")
public DataType numeric() {
DataType dataType = new DataType();
dataType.setSmallIntCol((short) 123);
dataType.setIntegerCol(456);
dataType.setBigIntCol(789L);
dataType.setDecimalCol(new BigDecimal("123.45"));
dataType.setNumericCol(new BigDecimal("678.90"));
dataType.setRealCol(12.34f);
dataType.setDoublePrecisionCol(56.78);
dataTypeService.save(dataType);
return dataTypeService.getById(dataType.getId());
}
@RequestMapping("/string")
public DataType string() {
DataType dataType = new DataType();
dataType.setVarcharCol("测试字符串");
dataType.setCharCol("A");
dataType.setTextCol("长文本内容");
dataTypeService.save(dataType);
return dataTypeService.getById(dataType.getId());
}
@RequestMapping("/date")
public DataType date() {
DataType dataType = new DataType();
dataType.setDateCol(LocalDate.parse("2025-12-29"));
dataType.setTimeCol(LocalTime.parse("15:30:45"));
dataType.setTimestampCol(LocalDateTime.parse("2025-12-29T15:30:45"));
dataType.setTimestampTzCol(OffsetDateTime.now());
dataTypeService.save(dataType);
return dataTypeService.getById(dataType.getId());
}
@RequestMapping("/boolean")
public DataType booleanType() {
DataType dataType = new DataType();
dataType.setBooleanCol(true);
dataTypeService.save(dataType);
return dataTypeService.getById(dataType.getId());
}
@RequestMapping("/json")
public DataType json() {
DataType dataType = new DataType();
dataType.setJsonCol(JSON.parseObject("{"name":"test","value":"[]","array":[1,2,3],"nested":{"key":"value"}}"));
dataType.setJsonbCol(JSON.parseObject("{"title":"sample jsonb","content":"PostgreSQL JSONB data","tags":["json","postgresql","database"],"metadata":{"created":"2025-12-29","version":1.0,"active":true}}"));
dataTypeService.save(dataType);
return dataTypeService.getById(dataType.getId());
}
@RequestMapping("/array")
public DataType array() {
DataType dataType = new DataType();
dataType.setArrayCol(List.of(1, 2, 3, 5));
dataType.setDecimalArrayCol(new BigDecimal[]{new BigDecimal("123.45"), new BigDecimal("678.90")});
dataTypeService.save(dataType);
return dataTypeService.getById(dataType.getId());
}
}
接口测试
测试 json 类型
测试数组类型