为什么要是用MyBatis中Json类型?
数据库表设计时,如果遇到字段数量不确定或结构频繁变化的情况(比如用户自定义配置、动态表单等),将这些数据以 JSON 格式存储可以避免频繁地修改数据库表结构。这样可以提高系统的灵活性和扩展性。
比方说
第一种方法
-
先创建一个表类型如下
-
创建一个项目
-
写一个JsonTypeHandler类继承BaseTypeHandler
BaseTypeHandler 这个类的作用是在JDBC中处理JSON类型的数据,将JSON数据与Java对象相互转换
public class JsonTypeHandler<T> extends BaseTypeHandler<T> { private Class<T> clazz; //构造函数 --- >接收一个 Class 对象作为参数,用于指定处理的数据类型。 public JsonTypeHandler(Class<T> clazz) { this.clazz = clazz; } public JsonTypeHandler() { } //插入数据将任何类型转换为json @Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JSON.toJSONString(parameter)); } //获取数据json转换类型 @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { return JSON.parseObject(rs.getString(columnName), clazz); } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return null; } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return null; } }
复制
实体类
package com.by.moder; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; import java.util.Map; @Data public class User { private Integer id; private String name; private Map<String,String> addressBook; // private String[] addressBook; private Address friendAddress; private List<Pet> pet; @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class Pet { public String name; public String category; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class Address { private String province; private String city; private String area; } }
复制
Controller
package com.by.controller; import cn.hutool.json.JSONUtil; import com.by.moder.User; import com.by.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController public class UserController { @Autowired private UserService userService; //添加 @GetMapping("/api/add") public void add(){ User user = new User(); user.setName("小明"); Map map = new HashMap(); map.put("张三","123123132"); map.put("李四","789789798"); map.put("王五","456456464"); //数组类型存储 // String[] map={"张三","123123132","李四","789789798","王五","456456464"}; //hutool转json工具 // String s = JSONUtil.toJsonStr(map); user.setAddressBook(map); User.Address adderss = User.Address.builder().province("河南省").city("郑州市").area("高新区").build(); user.setFriendAddress(adderss); List<User.Pet> list = new ArrayList<>(); list.add(User.Pet.builder().name("喵喵").category("哺乳类").build()); list.add(User.Pet.builder().name("汪汪").category("哺乳类").build()); list.add(User.Pet.builder().name("鹦鹉").category("鸟类").build()); //String s1 = JSONUtil.toJsonStr(list); user.setPet(list); userService.add(user); } //查询 @GetMapping("/api/select") public List<User> select(){ List<User> select = userService.select(); return null; } }
复制
Service
@Service public class UserService { @Autowired private UserDao userDao; public List<User> select() { return userDao.select(); } public void add(User user){ userDao.add(user); } }
复制
Dao
@Mapper public interface UserDao { void add(User user); List<User> select(); }
复制
Mapper.xml 主要使用是在xml中配置类型 typeHandler=“com.by.config.JsonTypeHandler”
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <-- --> <mapper namespace="com.by.dao.UserDao"> <resultMap id="UserMap" type="com.by.moder.User"> <result property="id" column="id"/> <result property="addressBook" column="addressBook" typeHandler="com.by.config.JsonTypeHandler"/> <result property="friendAddress" column="friendAddress" typeHandler="com.by.config.JsonTypeHandler"/> <result property="pet" column="pet" typeHandler="com.by.config.JsonTypeHandler"/> </resultMap> <insert id="add"> insert into user2 (name, addressBook, friendAddress,pet) values (#{name}, #{addressBook,typeHandler=com.by.config.JsonTypeHandler}, #{friendAddress,typeHandler=com.by.config.JsonTypeHandler},#{pet,typeHandler=com.by.config.JsonTypeHandler}) </insert> <select id="select" resultMap="UserMap"> select * from user2 </select> </mapper>
复制
添加结果
查询结果
虽然结果可以的出来但是pet类型不对,list中本来该存放的是Animal对象,但存储的却是json类型。
有没有什么办法可以将全类名和这个对象都存储到数据库(以json类型形式),取得时候就可以得到与其对应的的类型。
第二种方法:根据redis的序列化工具进行改进
使用Jackson2JsonRedisSerializer ,它 Spring Data Redis 提供的一个序列化器,用于将 Java 对象序列化为 JSON 字符串存储
- 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
复制
2.改进
public class JsonTypeHandler02<T> extends BaseTypeHandler<T> { private Class<T> clazz; Jackson2JsonRedisSerializer<Object> serializer; public JsonTypeHandler02(Class<T> clazz) { this.clazz = clazz; serializer = new Jackson2JsonRedisSerializer<>(Object.class); //定义一个对象映射器 ObjectMapper objectMapper = new ObjectMapper(); //JsonAutoDetect.Visibility.ANY 代表所有属性或字段都可以序列化 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //新版用法 //以数组的方式存放到Redis,Class Type 全类名作为为第一个元素,Json字符串为第二个元素。 objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); //老版用法,已弃用 //objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(objectMapper); } @Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { ps.setString(i,new String( serializer.serialize(parameter))); } @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { return (T) serializer.deserialize(rs.getString(columnName).getBytes(StandardCharsets.UTF_8)); } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return null; } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return null; } }
复制
- 更改Mapper.xml中的类型typeHandler=“com.by.config.JsonTypeHandler02”
<resultMap id="UserMap" type="com.by.moder.User"> <result property="id" column="id"/> <result property="addressBook" column="addressBook" typeHandler="com.by.config.JsonTypeHandler02"/> <result property="friendAddress" column="friendAddress" typeHandler="com.by.config.JsonTypeHandler02"/> <result property="pet" column="pet" typeHandler="com.by.config.JsonTypeHandler02"/> </resultMap> <insert id="add"> insert into user2 (name, addressBook, friendAddress,pet) values (#{name}, #{addressBook,typeHandler=com.by.config.JsonTypeHandler02}, #{friendAddress,typeHandler=com.by.config.JsonTypeHandler02},#{pet,typeHandler=com.by.config.JsonTypeHandler02}) </insert> <select id="select" resultMap="UserMap"> select * from user2 </select>
复制
最后添加后的结果里存放的有全类名
添加结果:
查询的时候就能反序列化转化为对应的类型
查询结果:
但这种方法
第三种方法
就是根据第二种方式,自己写一个序列化工具
- 写一个MybatisToJsonConfig序列化工具
public class MybatisToJsonConfig<T> { public static final Charset DEFAULT_CHARSET; private final JavaType javaType; private ObjectMapper objectMapper = new ObjectMapper(); public MybatisToJsonConfig(Class<T> type) { this.javaType = this.getJavaType(type); } public MybatisToJsonConfig(JavaType javaType) { this.javaType = javaType; } public T deserialize(@Nullable byte[] bytes) throws Exception { try { return this.objectMapper.readValue(bytes, 0, bytes.length, this.javaType); } catch (Exception var3) { throw new Exception("Could not read JSON: " + var3.getMessage(), var3); } } public byte[] serialize(@Nullable Object t) throws Exception { try { return this.objectMapper.writeValueAsBytes(t); } catch (Exception var3) { throw new Exception("Could not write JSON: " + var3.getMessage(), var3); } } public void setObjectMapper(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "'objectMapper' must not be null"); this.objectMapper = objectMapper; } protected JavaType getJavaType(Class<?> clazz) { return TypeFactory.defaultInstance().constructType(clazz); } static { DEFAULT_CHARSET = StandardCharsets.UTF_8; } }
复制
- 在把方法二的Jackson2JsonRedisSerializer工具替换成MybatisToJsonConfig自己写的序列化工具。
public class JsonTypeHandler03<T> extends BaseTypeHandler<T> { private static MybatisToJsonConfig<Object> serializer = null; static { serializer = new MybatisToJsonConfig<>(Object.class); //创建对象映射器 ObjectMapper objectMapper = new ObjectMapper(); //JsonAutoDetect.Visibility.ANY 代表所有属性或字段都可以序列化 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //新版用法 //以数组的方式存放到Redis,Class Type 全类名作为为第一个元素,Json字符串为第二个元素。 objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); //老版用法,已弃用 //objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(objectMapper); } public JsonTypeHandler03(Class<T> clazz) { } //插入数据将任何类型转换为json @Override @SneakyThrows //@SneakyThrows 是 Lombok 提供的一个注解,用于在方法上自动抛出异常。 // 使用 @SneakyThrows 注解可以使方法在遇到异常时,自动将异常转换为 java.lang.RuntimeException 并抛出, // 而无需显式地在方法中编写异常处理代码 public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { ps.setString(i,new String(serializer.serialize(parameter))); } //获取数据json转换类型 @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { try { return (T) serializer.deserialize(rs.getString(columnName).getBytes(StandardCharsets.UTF_8)); } catch (Exception e) { throw new RuntimeException(e); } } @Override public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return null; } @Override public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return null; } }
复制
最后更改mapper.xml类型typeHandler=“com.by.config.JsonTypeHandler03”
结果和第二种方法一样