Mybatis(Plus)对JSON / Array类型进行序列化
本文目的: 实体类中对应JdbcType的json/array类型自动映射, 免去在各个位置声明typeHandler的困扰
依赖项说明:
springboot:3.5.19
- org.mybatis:mybatis:3.5.19(mybatis plus也一样)
- org.postgresql:postgresql:42.7.5(可改为MySQL)
- org.reflections:reflections:0.10.2
前言
对于各种带有泛型的java类型, 由于mybatis的typeHandler对泛型非常不友好, 所以一般情况下无法解析
因此我们首先要做的就是消除泛型, 然后再解析, 解析逻辑就会简单多了
MySQL或者其他数据库也都一样, 就不详细说明了
声明类型接口, 用于消除泛型
-
Postgresql JSON类型接口
public interface PgJsonType { }
-
Postgresql Array类型接口(仅支持基本类型, 如long, boolean, string, decimal等, 不支持多维数组和组合类型)
public interface PgArrayType<T> extends Collection<T> {JdbcType getJdbcType(); }
示例json类型声明
消除泛型的做法, 就是讲带有泛型的类, 重新声明为一个新的类型, 如:
假设有对象:
@Data
class Demo {private String name;private Integer age;
}
json类型示例为:
{name: "张三", age: 10}
或json数组类型:
[{name: "张三", age: 10}]
-
则为json集合类型创建类
public class DemoJsonList extends ArrayList<Demo> implements PgJsonType { }
-
为json类型创建类
public class DemoJson implements PgJsonType { }
此时实体类中不再使用List<Demo>
或者Demo
来声明字段, 而是使用下面的方式:
@Data
class SomeEntity {private DemoJson demoJson;private DemoJsonList demoJsonList;
}
示例Array类型
-
字符串数组
public class StrList extends ArrayList<String> implements PgArrayType<String> {@Overridepublic JdbcType getJdbcType() {return JdbcType.VARCHAR;} }
-
Long数组
public class LongList extends ArrayList<Long> implements PgArrayType<Long> {@Overridepublic JdbcType getJdbcType() {return JdbcType.NUMERIC;} }
为数组和json类型创建typeHandler
-
先创建一个SpringContextUtil
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component;@Component public class SpringContextUtil implements ApplicationContextAware {private static ApplicationContext context;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {context = applicationContext;}public static <T> T getBean(Class<T> requiredType) {return context.getBean(requiredType);} }
-
然后创建包扫描工具类
import org.reflections.Reflections;import java.util.Set;/*** 类型扫描相关工具类*/ public class TypeScanUtil {/*** 子类型扫描** @param prefix 包前缀* @param clazz 目标类型* @return 目标类型的子类型*/public static <T> Set<Class<? extends T>> scanSubTypesOf(String prefix, Class<T> clazz) {Reflections reflections = new Reflections(prefix);return reflections.getSubTypesOf(clazz);}
-
PgArrayTypeHandler.java(用于处理array类型)
import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.springframework.beans.BeanUtils; import org.springframework.core.GenericTypeResolver;import java.sql.*; import java.util.Objects;@RequiredArgsConstructor public class PgArrayTypeHandler<T extends PgArrayType<V>, V> extends BaseTypeHandler<T> {private final Class<T> clazz;@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, PgArrayType parameter, JdbcType jdbcType) throws SQLException {Array array = ps.getConnection().createArrayOf(parameter.getJdbcType().name(), parameter.toArray());ps.setArray(i, array);array.free();}@Overridepublic T getNullableResult(ResultSet rs, String columnName) throws SQLException {return extractArray(rs.getArray(columnName));}@Overridepublic T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return extractArray(rs.getArray(columnIndex));}@Overridepublic T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return extractArray(cs.getArray(columnIndex));}protected T extractArray(Array array) throws SQLException {if (Objects.isNull(array)) {return null;}Object[] javaArray = (Object[]) array.getArray();array.free();if (Objects.isNull(javaArray)) {return null;}ObjectMapper objectMapper = SpringContextUtil.getBean(ObjectMapper.class);// noinspection uncheckedClass<V> type = (Class<V>) GenericTypeResolver.resolveTypeArgument(clazz, PgArrayType.class);T result = BeanUtils.instantiateClass(clazz);for (Object o : javaArray) {V value = objectMapper.convertValue(o, type);result.add(value);}return result;} }
-
PgJsonTypeHandler.java(用于处理json类型)
如果是MySQL, setNonNullParameter方法声明为:
ps.setString(i, toJson(parameter));
另外如果pg指定了jdbc参数stringtype=unspecified时, 也可以用上述指令, 这样就可以同时支持json和jsonb了
指定stringtype示例:
jdbc:postgresql:///db?stringtype=unspecified
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.postgresql.util.PGobject;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;@RequiredArgsConstructor
public class PgJsonTypeHandler<T extends PgJsonType> extends BaseTypeHandler<T> {private final Class<T> clazz;@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {PGobject json = new PGobject();json.setType("jsonb");json.setValue(toJson(parameter));ps.setObject(i, json);}@Overridepublic T getNullableResult(ResultSet rs, String columnName) throws SQLException {final String json = rs.getString(columnName);return StringUtils.isBlank(json) ? null : parse(json);}@Overridepublic T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {final String json = rs.getString(columnIndex);return StringUtils.isBlank(json) ? null : parse(json);}@Overridepublic T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {final String json = cs.getString(columnIndex);return StringUtils.isBlank(json) ? null : parse(json);}@SneakyThrows(JsonProcessingException.class)public T parse(String json) {return SpringContextUtil.getBean(ObjectMapper.class).readValue(json, clazz);}@SneakyThrows(JsonProcessingException.class)public String toJson(T obj) {return SpringContextUtil.getBean(ObjectMapper.class).writeValueAsString(obj);}
}
-
注册类型转换器
其中kim.nzxy.mybatis.type.xxx替换成
StrList
等类所在的包即可import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import lombok.RequiredArgsConstructor; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.type.TypeHandlerRegistry; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component;import java.time.LocalDateTime; import java.util.Set;@Component @RequiredArgsConstructor @Configuration public class MybatisConfig {@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public CommandLineRunner registerTypes(SqlSessionFactory sqlSessionFactory) {return args -> {TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();// noinspection rawtypesSet<Class<? extends PgArrayType>> pgArrayTypes = TypeScanUtil.scanSubTypesOf("kim.nzxy.mybatis.type.xxx", PgArrayType.class);pgArrayTypes.forEach(it -> typeHandlerRegistry.register(it, PgArrayTypeHandler.class));Set<Class<? extends PgJsonType>> pgJsonTypes = TypeScanUtil.scanSubTypesOf("kim.nzxy.mybatis.type.xxx", PgJsonType.class);pgJsonTypes.forEach(it -> typeHandlerRegistry.register(it, PgJsonTypeHandler.class));};} }
-
完结撒花