• hibernate跨数据库,json字段处理方案,自定义扩展JsonStringType


    一、背景

    对于一些不经常更新的静态数据,我们喜欢使用json格式存储。推荐的做法是将json数据存储在key-value数据库,但这无疑增加了技术成本,所以我们通常还是存储在RDB数据库中。我们在使用hibernate,对json数据的存取期望是,存能自动转换为json格式存储,取能自动将json数据转换为对象数据。在数据库方面,Mysql5.7版本以后新增的功能,Mysql提供了一个原生的Json类型,Json值将不再以字符串的形式存储,而是采用一种允许快速读取文本元素(document elements)的内部二进制(internal binary)格式。在Json列插入或者更新的时候将会自动验证Json文本,未通过验证的文本将产生一个错误信息。而除了mysql,pg极少部分支持json字段类型的其他数据库,还是需要大文本字段进行存储。

    二、实现方案

    关键技术: hibernate-types 将Java对象或Jackson JsonNode为JPA实体属性。

    • 引入依赖-hibernate-types
    <dependency>
       <groupId>com.vladmihalceagroupId>
       <artifactId>hibernate-types-52artifactId>
       <version>2.10.1version>
    dependency>
    
    • 定义实体类
    import com.alibaba.fastjson.JSONObject;
    import com.vladmihalcea.hibernate.type.array.IntArrayType;
    import com.vladmihalcea.hibernate.type.array.StringArrayType;
    import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
    import com.vladmihalcea.hibernate.type.json.JsonStringType;
    import lombok.Data;
    import org.hibernate.annotations.Type;
    import org.hibernate.annotations.TypeDef;
    import org.hibernate.annotations.TypeDefs;
    
    import javax.persistence.*;
    
    @Data
    @Entity
    @Table(name = "parents")
    @TypeDefs({
            @TypeDef(name = "string-array", typeClass = StringArrayType.class),
            @TypeDef(name = "json", typeClass = JsonStringType.class),
            @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
    })
    public class TestEntity implements Serializable {
        @Id
        private Long id;
    
        @Column(name = "attr")
        @Type(type = "jsonb")
        private JSONObject attr;
    
        @Column(name = "user_list")
        @Type(type = "json")
        private List userList;
    
        @Column(name = "ava_img")
        @Type(type = "string-array")
        private String[] avaImg;
    }
    

    以上为常规操作,在多数据库使用时,你会发现,JsonStringType 类型会转为数据库默认的 varchar(255),根本就存储不了长一点的json数据。

    三、扩展JsonStringType,适配多数据库json字段存储

    也许网络上一搜JsonStringType适配性问题,答案基本都是建议用 @Column(columnDefinition = "json") 、 @Column(columnDefinition = "varchar(max)") 这种写法,我在上一篇文章中说了,columnDefinition具有不可移植性,只能适配一种或一类数据库。所以,需要自定义扩展JsonStringType。

    • 先分析JsonStringType源码
      image
      可以看到JsonStringType的类型描述时由JsonStringSqlTypeDescriptor提供的
      image
      而JsonStringSqlTypeDescriptor中,将数据库对应的类型写死为 12
      image

    12对应数据库字段类型是varchar,这也太坑了,直接给JsonStringType对应的数据库字段类型写死。

    • JsonStringType兼容办法
      既然我们找到了问题原因,那么我们就容易解决,解决主要思路是将JsonStringType类型对应的数据库字段类型可配置
      接着看源码
      image
      JsonStringSqlTypeDescriptor 的父类是 AbstractJsonSqlTypeDescriptor
      image
      在AbstractJsonSqlTypeDescriptor中,将json格式写死为1111,也就是 Types.OTHER类型
      我们重新定义一个JsonStringType,不将SqlType写死,然后通过配置database-platform指定hibernate方言中,将1111转换为我们需要的格式。
      源码如下:
    MyJsonStringType (点击展开)
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.vladmihalcea.hibernate.type.AbstractHibernateType;
    import com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor;
    import com.vladmihalcea.hibernate.type.util.Configuration;
    import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
    import org.hibernate.usertype.DynamicParameterizedType;
    
    import java.lang.reflect.Type;
    import java.util.Properties;
    
    public class MyJsonStringType extends AbstractHibernateType implements DynamicParameterizedType {
        public static final MyJsonStringType INSTANCE = new MyJsonStringType();
    
        public MyJsonStringType() {
            super(MyJsonStringSqlTypeDescriptor.INSTANCE, new JsonTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper()));
        }
    
        public MyJsonStringType(Type javaType) {
            super(MyJsonStringSqlTypeDescriptor.INSTANCE, new JsonTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper(), javaType));
        }
    
        public MyJsonStringType(Configuration configuration) {
            super(MyJsonStringSqlTypeDescriptor.INSTANCE, new JsonTypeDescriptor(configuration.getObjectMapperWrapper()), configuration);
        }
    
        public MyJsonStringType(ObjectMapper objectMapper) {
            super(MyJsonStringSqlTypeDescriptor.INSTANCE, new JsonTypeDescriptor(new ObjectMapperWrapper(objectMapper)));
        }
    
        public MyJsonStringType(ObjectMapperWrapper objectMapperWrapper) {
            super(MyJsonStringSqlTypeDescriptor.INSTANCE, new JsonTypeDescriptor(objectMapperWrapper));
        }
    
        public MyJsonStringType(ObjectMapper objectMapper, Type javaType) {
            super(MyJsonStringSqlTypeDescriptor.INSTANCE, new JsonTypeDescriptor(new ObjectMapperWrapper(objectMapper), javaType));
        }
    
        public MyJsonStringType(ObjectMapperWrapper objectMapperWrapper, Type javaType) {
            super(MyJsonStringSqlTypeDescriptor.INSTANCE, new JsonTypeDescriptor(objectMapperWrapper, javaType));
        }
    
        public String getName() {
            return "json";
        }
    
        protected boolean registerUnderJavaType() {
            return true;
        }
    
        public void setParameterValues(Properties parameters) {
            ((JsonTypeDescriptor) this.getJavaTypeDescriptor()).setParameterValues(parameters);
        }
    }
    
    
    
    
    
    MyJsonStringSqlTypeDescriptor(点击展开)
    import com.vladmihalcea.hibernate.type.json.internal.AbstractJsonSqlTypeDescriptor;
    import org.hibernate.type.descriptor.ValueBinder;
    import org.hibernate.type.descriptor.WrapperOptions;
    import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
    import org.hibernate.type.descriptor.sql.BasicBinder;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    public class MyJsonStringSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor {
        public static final MyJsonStringSqlTypeDescriptor INSTANCE = new MyJsonStringSqlTypeDescriptor();
    
        public MyJsonStringSqlTypeDescriptor() {
        }
    
        public  ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) {
            return new BasicBinder(javaTypeDescriptor, this) {
                protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
                    st.setString(index, (String)javaTypeDescriptor.unwrap(value, String.class, options));
                }
    
                protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException {
                    st.setString(name, (String)javaTypeDescriptor.unwrap(value, String.class, options));
                }
            };
        }
    
        protected Object extractJson(ResultSet rs, String name) throws SQLException {
            return rs.getString(name);
        }
    
        protected Object extractJson(CallableStatement statement, int index) throws SQLException {
            return statement.getString(index);
        }
    
        protected Object extractJson(CallableStatement statement, String name) throws SQLException {
            return statement.getString(name);
        }
    }
    

    实体json定义改为
    @TypeDef(name="json",typeClass = MyJsonStringType.class)
    大功告成,下边就可以根据不同数据库自适应数据库json字段类型


    • mysql适配json字段

      public class MySQLDialect extends MySQL5InnoDBDialect {
      	public MySQLDialect() {
      		registerColumnType(Types.OTHER, "json");
      	}
      }
      
      

      配置文件:
      spring.jpa.database-platform: xxxx.xxxx.xx.MySQLDialect

    • 达梦数据库适配json字段

      public class DaMengDialect extends DmDialect {
      	public DaMengDialect() {
      		registerColumnType(Types.OTHER, "LONGVARCHAR");
      	}
      }
      

      配置文件:
      spring.jpa.database-platform: xxxx.xxxx.xx.DaMengDialect

  • 相关阅读:
    UDP接收报文函数recvfrom和UDP发送报文函数sendto
    springboot+vue实现登录案例(附VUE整个项目代码)
    R语言编写一个研究多重共线性的小程序(1)
    学习【Cesium】第九篇,鼠标交互和数据查询(学不会揍我)
    开放签电子签章企业版V1.6(紧急更新)
    clickhouse 如何使用SQL 管理用户和角色
    STC8H开发(十五): GPIO驱动Ci24R1无线模块
    vue开发不能不知的小知识
    计算机网络性能指标
    Linux 编写shell脚本记录操作用户日志信息
  • 原文地址:https://www.cnblogs.com/luze/p/17185828.html