为什么要是用MyBatis中Json类型?

数据库表设计时,如果遇到字段数量不确定或结构频繁变化的情况(比如用户自定义配置、动态表单等),将这些数据以 JSON 格式存储可以避免频繁地修改数据库表结构。这样可以提高系统的灵活性和扩展性。
比方说

第一种方法

  1. 先创建一个表类型如下
    在这里插入图片描述

  2. 创建一个项目
    在这里插入图片描述

  3. 写一个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 字符串存储

  1. 添加依赖
        <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;
    }

}
  1. 更改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>

最后添加后的结果里存放的有全类名
添加结果:在这里插入图片描述
查询的时候就能反序列化转化为对应的类型
查询结果:在这里插入图片描述
但这种方法

第三种方法

就是根据第二种方式,自己写一个序列化工具

  1. 写一个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;
    }

}
  1. 在把方法二的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”

结果和第二种方法一样

GitHub 加速计划 / js / json
41.72 K
6.61 K
下载
适用于现代 C++ 的 JSON。
最近提交(Master分支:1 个月前 )
960b763e 3 个月前
8c391e04 6 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐