mybatis查詢mysql 數據庫中 BLOB字段,結果出現亂碼

起因

mybatis-plus 通過Mapper 查詢數據,映射出來的BLOB字段中的yml數據中文是亂碼的

---
DefaultValue: ''
Formula: ''
HintContent: ''
HintType: ''
OptionsColor:
  处理: ''
  外修中: ''
  完成: ''
  接单: ''
  新建: ''
  评价: ''
  转信息科: ''
  转总务科: ''
  转设备科: ''
OptionsIcon:
  处理: ''
  外修中: ''
  完成: ''
  接单: ''
  新建: ''
  评价: ''
  转信息科: ''
  转总务科: ''
  转设备科: ''
PossibleComments:
  处理: ''
  外修中: ''
  完成: ''
  接单: ''
  新建: ''
  评价: ''
  转信息科: ''
  转总务科: ''
  转设备科: ''
PossibleValues:
  处理: 处理
  外修中: 外修中
  完成: 完成
  接单: 接单
  新建: 新建
  评价: 评价
  转信息科: 转信息科
  转总务科: 转总务科
  转设备科: 转设备科
Regex: ''
RegexHint: ''
TreeView: '0'
Unique: '0'

我們看一下正常的數據是長下面這樣的

---
DefaultValue: ''
Formula: ''
HintContent: ''
HintType: ''
OptionsColor:
  處理: ''
  外修中: ''
  完成: ''
  接單: ''
  新建: ''
  評價: ''
  轉信息科: ''
  轉總務科: ''
  轉設備科: ''
OptionsIcon:
  處理: ''
  外修中: ''
  完成: ''
  接單: ''
  新建: ''
  評價: ''
  轉信息科: ''
  轉總務科: ''
  轉設備科: ''
PossibleComments:
  處理: ''
  外修中: ''
  完成: ''
  接單: ''
  新建: ''
  評價: ''
  轉信息科: ''
  轉總務科: ''
  轉設備科: ''
PossibleValues:
  處理: 處理
  外修中: 外修中
  完成: 完成
  接單: 接單
  新建: 新建
  評價: 評價
  轉信息科: 轉信息科
  轉總務科: 轉總務科
  轉設備科: 轉設備科
Regex: ''
RegexHint: ''
TreeView: '0'
Unique: '0'

在來看看這個字段在數據庫中存儲的樣子:

image-20220623180920568

image-20220623180943762

排查過程

一開始想到的就是經典的亂碼問題。所以嘗試了如下方法

1、url 屬性排查

檢查數據庫 url 鏈接上有沒有添加 characterEncoding=UTF-8,這裡查看是沒有問題的,因為用的是nacos,擔心是覆蓋出了問題。我還特意在代碼中打印出來了。

    @Value("${spring.datasource.druid.url}")
    String dateURL;

    log.info("數據庫連接配置 url 屬性為: "+dateURL);

結果如下:

image-20220623181436544


2、IDEA 編碼排查

排查是否是 IDEA 問題,雖然概率小,但是也要排查一下,排查結果還是沒有問題

排查路徑:Setting—>Editor—>File Encodings

image-20220623181629922

3、mybatis xml文件排查

這裡主要是看 xml 有時候是手寫或者網上複製過來的話,也可能會造成亂碼。排查結果也是沒有問題

image-20220623181851200


4、排查數據庫中的數據是否亂碼

這個時候,我們在查詢步驟中基本上都排查完了,現在懷疑是不是插入時,數據庫本身存儲就是亂碼的

直接去數據庫中查找改字段,並把數據放到 Notepad++ 或者其他編輯器裏面,可以確定數據庫中存的數據是正常的


5、排查查詢其他中文字段是否會出現亂碼

直接寫SQL去查詢其他中文字段,查出來的結果是正常的,這就證明了問題確實是出現在 BLOB 這個字段裏面了

解決辦法

到這裡,基本上排查的方法都用上了,其實上面的排查過程還是比較消耗時間的,這裡我就不做過多繁瑣的描述了,還有一些其他的排查方式。比如換機器,換配置文件的等也都一一試過,但是環境這方面的排查,我就不講述了,實在是無聊又耗時間。最後確定問題出現在BLOB類型之後,參考網上的文章做了如下的解決方案。

把查出來的數據,作為位元組數組,保留最完整的原始性,在把 byte[] 強轉為 UTF-8 的 String 類型。

此時就去String中嘗試查找是否純在此類。萬幸的是找到了

    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }

最後是使用這個方法實現了轉換

new String( dynamicFieldConfig字段值 , “utf-8” );

但是我們項目中,很多地方都用到了這個字段。


此時想到mybatis的結果集攔截器,我們可以在結果集攔截器中對這個字段進行攔截,並對他做語言轉換處理。

最後實現的效果:

package com.dt.cloud.tools;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.sql.*;

/**
 * @Description:
 * @author: zch
 * @Date: 2022/6/23 16:36
 * @Version 1.0
 */
public class ConvertBlobTypeHandler extends BaseTypeHandler<String> {

    private static final String DEFAULT_CHARSET = "utf-8";


    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
                                    String parameter, JdbcType jdbcType) throws SQLException {
        ByteArrayInputStream bis;
        try {
            bis = new ByteArrayInputStream(parameter.getBytes(DEFAULT_CHARSET));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Blob Encoding Error!");
        }
        ps.setBinaryStream(i, bis, parameter.length());
    }

   @Override
    //rs 為返回結果集,columnName 就是我們需要處理的字段名,當我們根據字段名在返回結果集中找出的這個字段就是 longblob 類型的數據了
    public String getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        Blob blob = rs.getBlob(columnName);
        byte[] returnValue = null;
        if (null != blob) {
            returnValue = blob.getBytes(1, (int) blob.length());
        }
        try {
            // 核心代碼,把結果集攔截下來,並且強制轉換為utf-8格式
            return new String(returnValue, DEFAULT_CHARSET);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Blob Encoding Error!");
        }
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        Blob blob = cs.getBlob(columnIndex);
        byte[] returnValue = null;
        if (null != blob) {
            returnValue = blob.getBytes(1, (int) blob.length());
        }
        try {
            return new String(returnValue, DEFAULT_CHARSET);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Blob Encoding Error!");
        }
    }

    @Override
    public String getNullableResult(ResultSet arg0, int arg1)
            throws SQLException {

        return null;
    }
}

在我們的 mapper 文檔中需要攔截的 resultMap 中的字段中增加一個 typeHandler 類型攔截器,這個 typeHandler 的值就是我們 ConvertBlobTypeHandler 類的地址

image-20220623183403389

最後總結:數據庫中的存儲使用的就是 longblob 類型,這個類型在查詢出來的時候如果不進行處理的話就會出現亂碼問題。簡單的處理方式就是在 Mybatis 查詢數據庫的時候增加一個攔截器,給這個類型的字段改變一下編碼方式。