hibernate跨資料庫,json欄位處理方案,自定義擴展JsonStringType

来源:https://www.cnblogs.com/luze/archive/2023/03/08/17185828.html
-Advertisement-
Play Games

概述 鎖是電腦協調多個進程或線程併發訪問某一資源的機制。在資料庫中,除傳統的計算資源(CPU、RAM、I/O)的爭用以外,數據也是一種供許多用戶共用的資源。如何保證數據併發訪問的一致性、有效性是所有資料庫必須解決的一個問題,鎖衝突也是影響資料庫併發訪問性能的一個重要因素。從這個角度來說,鎖對資料庫 ...


一、背景

對於一些不經常更新的靜態數據,我們喜歡使用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.vladmihalcea</groupId>
   <artifactId>hibernate-types-52</artifactId>
   <version>2.10.1</version>
</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<User> 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<Object> 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 <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicBinder<X>(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

本文來自博客園,作者:·志堅行遠·,轉載請註明原文鏈接:https://www.cnblogs.com/luze/p/17185828.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言: Servlet 容器初始化 Servlet 時,會為這個 Servlet 創建一個 ServletConfig 對象,並將 ServletConfig 對象作為參數傳遞給 Servlet 。通過 ServletConfig 對象即可獲得當前 Servlet 的初始化參數信息。一個 Web 應 ...
  • Tread多線程 什麼是線程? 線程(Thread)是一個程式內部的一條執行流程。 程式中如果只有一條執行流程,那這個程式就是單線程的程式。 多線程是什麼? 多線程是指從軟硬體上實現的多條執行流程的技術(多條線程由cpu負責調度執行)。 多線程的創建方式 方式一:繼承Thread ①定義一個子類My ...
  • python的基本認識 初識python: python是一種跨平臺的、開源的、免費的、解釋型的高級編程語言; python的應用領域十分廣泛、如web編程、圖像處理、黑客編程、網路爬蟲和科學計算等; python學習簡單、開發速度快、節省時間和精力。 python具有豐富和強大的庫,能夠把用其他語 ...
  • 昨天木子問我能不能做自動刷某音短視頻,還要自動刷小哥哥,不是小哥哥就划走。 我心想,這女人真麻煩,怎麼這麼多事。 不好好工作天天想著小哥哥! 為了不得罪她,當時我就先答應了下來,然而實際上我把小哥哥變成了小姐姐,刷什麼小哥哥,多沒品味! 好了,話不多說,我們直接上代碼! 代碼實戰 首先導入需要使用的 ...
  • 前言 每次啟動SpringBoot項目時,總是能看到控制台列印了一串字元,隱約能辨認出是“Spring”,不知大家是否也好奇過是怎麼實現的,是直接列印固定的字元串,還是根據什麼演算法去生成的?於是閑暇無事,探究一番。 只想修改banner可以跳到文末查看 SpringBoot是怎麼列印的 Banner ...
  • 簡介 在現實世界中,我們常常需要等待其它任務完成,才能繼續執行下一步。Java實現等待子線程完成再繼續執行的方式很多。我們來一一查看一下。 Thread的join方法 該方法是Thread提供的方法,調用join()時,會阻塞主線程,等該Thread完成才會繼續執行,代碼如下: private st ...
  • 說起開源CMS,你會想到哪些呢?WordPress?DoraCMS?joomla? 今天再給大家推薦一個非常好用的開源CMS:Wagtail 如果您正在選型的話,可以瞭解一下Wagtail的特點: 基於Django構建,具有出色的文檔管理功能和友好的用戶界面。 提供了一個靈活且易於使用的頁面編輯器, ...
  • 學習網頁設計和網路編程可能是一種有趣而有意義的體驗,但需要時間,精力和練習.這裡有一些技巧可以幫助您更輕鬆地學習這些技能: 從基礎知識開始:在您深入研究高級主題之前,重要的是要有牢固的理解很重要基礎知識.首先學習HTML,CSS和JavaScript,這是網路的基礎語言. 使用線上資源:線上資源有許 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...