最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Mybatis操作Clickhouse數(shù)組的最佳實踐分享

 更新時間:2025年09月19日 08:39:38   作者:Jaising666  
ClickHouse 的 Array(T) 數(shù)據(jù)類型支持任意有效數(shù)據(jù)類型作為元素,包括基本類型、嵌套數(shù)組和可空類型,本文給大家分享了Mybatis操作Clickhouse數(shù)組的最佳實踐,需要的朋友可以參考下

ClickHouse Array 類型概述

ClickHouse 的 Array(T) 數(shù)據(jù)類型支持任意有效數(shù)據(jù)類型作為元素,包括基本類型、嵌套數(shù)組和可空類型,關鍵特性包括:

  • 索引從1開始:區(qū)別于多數(shù)編程語言的 0 索引機制
  • 自動類型推斷:選擇最窄兼容類型以優(yōu)化存儲
  • 嚴格類型檢查:混合不兼容類型將導致異常
  • NULL值處理:包含 NULL 值時自動轉(zhuǎn)換為 Nullable 類型
-- 有效用法
SELECT array(1, 2, 3);           -- Array(UInt8)
SELECT array('a', 'b', 'c');     -- Array(String)
SELECT array([1, 2], [3, 4]);    -- Array(Array(UInt8))

-- 無效用法:類型不兼容
SELECT array(1, 'a');
-- Error: There is no supertype for types UInt8, String

MyBatis 寫入 ClickHouse 數(shù)組的兩種方法比較

JPA 與 Mybatis 是常見的兩種 ORM 框架。JPA 主要為 OLTP 設計,ClickHouse 是 OLAP 數(shù)據(jù)庫,JPQL 難以表達 ClickHouse 的復雜分析查詢,而這正好可以發(fā)揮 Mybaits 靈活控制 SQL 的特性。對于 Clickhouse 的數(shù)組類型寫入一般有兩種方法:

方法一:使用 ClickHouse array() 函數(shù) + ${} 參數(shù)替換

Mybatis XML 代碼:

insert into xxx_base
    (array1)
values (array(${array1Value}))

Java 代碼:

// 手動格式化數(shù)組
List<String> userIds = Arrays.asList("user1", "user2", "user3");
String userIdsStr = userIds.stream()
    .map(s -> "'" + s.replace("'", "\\'") + "'")  // 轉(zhuǎn)義單引號
    .collect(Collectors.joining(","));
// userIdsStr = "'user1','user2','user3'"

// 處理空值
String deviceIdsStr = deviceIds.isEmpty() ? "" : 
    deviceIds.stream()
        .map(s -> "'" + s.replace("'", "\\'") + "'")
        .collect(Collectors.joining(","));

方法二:使用自定義 TypeHandler + #{} 參數(shù)綁定

Mybatis XML 代碼:

insert into xxx_base
    (array1)
values (#{array1Value,typeHandler=com.test.clickhousemybatisdemo.typehandler.ClickHouseArrayTypeHandler})

Java 代碼:

// 直接使用List對象
List<String> userIds = Arrays.asList("user1", "user2", "user3");
List<String> deviceIds = new ArrayList<>();  // 空列表也可以直接使用
// TypeHandler會自動處理轉(zhuǎn)換和空值情況

方案對比分析

維度array() + ${}TypeHandler + #{}
安全性? SQL注入風險? 預編譯安全,類型安全
可讀性? 需要格式化處理? 直接使用List
可維護性? 邏輯分散? 邏輯集中
性能?? 字符串拼接開銷? 預編譯緩存

得到的結(jié)論是推薦方法二:

  • 安全性差異${}參數(shù)替換存在 SQL 注入漏洞,#{}預編譯機制提供安全保障
  • 開發(fā)復雜度:字符串拼接方案需要復雜的格式化與轉(zhuǎn)義處理,TypeHandler 方案支持直接對象操作

自定義 TypeHandler 的優(yōu)勢

MyBatis 內(nèi)置的 TypeHandler 主要針對關系型數(shù)據(jù)庫的標準 SQL 類型設計,對于 ClickHouse 這樣的分析型數(shù)據(jù)庫的特殊數(shù)據(jù)類型支持有限,Java 的List<T>與 ClickHouse 的Array(T)之間缺少直接的類型轉(zhuǎn)換機制。而如果使用 Java String 來處理又會帶來數(shù)據(jù)類型頻繁轉(zhuǎn)換的工程問題,代碼可讀性與可維護性都會受到影響。

ClickHouse JDBC 驅(qū)動對數(shù)組類型的處理與傳統(tǒng)關系型數(shù)據(jù)庫存在差異:

// 傳統(tǒng)數(shù)據(jù)庫的數(shù)組處理(如PostgreSQL)
Array sqlArray = connection.createArrayOf("varchar", stringArray);

// ClickHouse需要特殊的類型名稱映射
Array sqlArray = connection.createArrayOf("String", stringArray);  // 注意:"String"而非"varchar"

于是,擴展 TypeHandler 實現(xiàn)處理數(shù)組問題就變得有必要:

  1. 雙向轉(zhuǎn)換:實現(xiàn) Java List<T> ↔ ClickHouse Array(T) 的無縫轉(zhuǎn)換
  2. 類型安全:確保編譯期和運行期的類型一致性
  3. 空值處理:正確處理 null 值和空數(shù)組的邊界情況
  4. 性能優(yōu)化:避免不必要的字符串拼接和解析開銷

擴展 TypeHandler 支持 Clickhouse Array

TypeHandler 接口設計

Mybatis TypeHandler 的設計就是為了緩解 JDBC 與 Java 數(shù)據(jù)類型不匹配的問題,通過擴展 TypeHandler 可以對各種數(shù)據(jù)庫的各種數(shù)據(jù)類型予以支持。

TypeHandler 接口很簡潔,一個是 setParameter 方法通過 PreparedStatement 為 SQL 語句綁定參數(shù),實現(xiàn) JDBC 到 Java 的數(shù)據(jù)類型轉(zhuǎn)換;另外三個 getResult 重載方法通過 ResultSet 獲取數(shù)據(jù)時,將 Java 轉(zhuǎn)換成 JDBC 數(shù)據(jù)類型。

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

BaseTypeHandler 抽象類設計

BaseTypeHandler 實現(xiàn) TypeHandler 接口抽象成基類,setParameter 提取出參數(shù)是否為空的條件判斷,如果為空就會調(diào)用 PreparedStatement#setNull,如果不為空就會調(diào)用 setNonNullParameter。前者會委托給具體的數(shù)據(jù)庫驅(qū)動,這里引入的 clickhouse-jdbc 就會實現(xiàn) setNull 方法;后者則會交給具體的 TypeHandler 實現(xiàn)類。

類似的,BaseTypeHandler 實現(xiàn)了 TypeHandler#getResult 后抽象出了 getNullableResult 方法,委托給具體的 TypeHandler 實現(xiàn)。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        ...
        ps.setNull(i, jdbcType.TYPE_CODE);
        ...
    } else {
      ...
      setNonNullParameter(ps, i, parameter, jdbcType);
      ...
    }
  }

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
	...
    return getNullableResult(rs, columnIndex);
    ...
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
	...
    return getNullableResult(rs, columnIndex);
    ...
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
	...
    return getNullableResult(rs, columnIndex);
    ...
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
      throws SQLException;

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

要想擴展簡單的 TypeHandler 就可以繼承 BaseTypeHandler,實現(xiàn)設置非空 Java 數(shù)據(jù)類型和獲取非空 JDBC 數(shù)據(jù)類型的 4 個抽象方法。

Mybatis 內(nèi)置了一些常用的 BaseTypeHandler 實現(xiàn)類,比如 ArrayTypeHandler(當然,這個指的是 Java 中的基礎類型 Array 而不是 List)、ClobTypeHandler、LocalDateTimeTypeHandler 等。

實現(xiàn) BaseTypeHandler<List<String>>

參考這些內(nèi)置的 BaseTypeHandler,容易實現(xiàn)支持 Java 的 List 與 ClickHouse 的 Array 的類型綁定,首先支持 List<String> 與 Array(String) 的類型綁定。

/**
 * ClickHouse Array(String) 類型處理器
 * 處理 Java List<String> 和 ClickHouse Array(String) 之間的轉(zhuǎn)換
 */
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.ARRAY)
public class ClickHouseArrayTypeHandler extends BaseTypeHandler<List<String>> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null || parameter.isEmpty()) {
            ps.setArray(i, null);
        } else {
            // 將 List<String> 轉(zhuǎn)換為數(shù)組
            String[] array = parameter.toArray(new String[0]);
            Array sqlArray = ps.getConnection().createArrayOf("String", array);
            ps.setArray(i, sqlArray);
        }
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Array array = rs.getArray(columnName);
        return convertArrayToList(array);
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Array array = rs.getArray(columnIndex);
        return convertArrayToList(array);
    }

    @Override
    public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Array array = cs.getArray(columnIndex);
        return convertArrayToList(array);
    }

    /**
     * 將 SQL Array 轉(zhuǎn)換為 List<String>
     */
    private List<String> convertArrayToList(Array sqlArray) throws SQLException {
        if (sqlArray == null) {
            return new ArrayList<>();
        }
        
        Object[] array = (Object[]) sqlArray.getArray();
        if (array == null || array.length == 0) {
            return new ArrayList<>();
        }
        
        List<String> result = new ArrayList<>();
        for (Object item : array) {
            if (item != null) {
                result.add(item.toString());
            }
        }
        return result;
    }
}

實現(xiàn) BaseTypeHandler<List<T>>

進一步的,可以將 Array(String) 擴展為支持 Array(T),這樣可以處理 Clickhouse 通用數(shù)組類型。

通過動態(tài)類型檢測實現(xiàn)多數(shù)據(jù)類型支持,getSqlTypeName 方法提供 Java 類型到 ClickHouse 類型的映射機制:

/**
 * ClickHouse Array(T) 類型處理器
 * 處理 Java List<T> 和 ClickHouse Array(T) 之間的轉(zhuǎn)換
 * 支持 String, Integer, Long, Double, Float, Boolean 等基本類型
 */
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.ARRAY)
public class ClickHouseArrayTypeHandler<T> extends BaseTypeHandler<List<T>> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null || parameter.isEmpty()) {
            ps.setArray(i, null);
        } else {
            // 檢測元素類型并創(chuàng)建相應的數(shù)組
            Object firstElement = parameter.get(0);
            String sqlTypeName = getSqlTypeName(firstElement);
            Object[] array = parameter.toArray();
            Array sqlArray = ps.getConnection().createArrayOf(sqlTypeName, array);
            ps.setArray(i, sqlArray);
        }
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Array array = rs.getArray(columnName);
        return convertArrayToList(array);
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Array array = rs.getArray(columnIndex);
        return convertArrayToList(array);
    }

    @Override
    public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Array array = cs.getArray(columnIndex);
        return convertArrayToList(array);
    }

    /**
     * 將 SQL Array 轉(zhuǎn)換為 List<T>
     */
    @SuppressWarnings("unchecked")
    private List<T> convertArrayToList(Array sqlArray) throws SQLException {
        if (sqlArray == null) {
            return new ArrayList<>();
        }
        
        Object[] array = (Object[]) sqlArray.getArray();
        if (array == null || array.length == 0) {
            return new ArrayList<>();
        }
        
        List<T> result = new ArrayList<>();
        for (Object item : array) {
            if (item != null) {
                result.add((T) convertToTargetType(item));
            }
        }
        return result;
    }

    /**
     * 根據(jù) Java 對象類型獲取對應的 SQL 類型名稱
     */
    private String getSqlTypeName(Object obj) {
        if (obj instanceof String) {
            return "String";
        } else if (obj instanceof Integer) {
            return "Int32";
        } else if (obj instanceof Long) {
            return "Int64";
        } else if (obj instanceof Double) {
            return "Float64";
        } else if (obj instanceof Float) {
            return "Float32";
        } else if (obj instanceof Boolean) {
            return "UInt8";
        } else {
            // 默認轉(zhuǎn)換為字符串
            return "String";
        }
    }

    /**
     * 將對象轉(zhuǎn)換為目標類型
     */
    private Object convertToTargetType(Object obj) {
        // 對于基本類型,直接返回
        if (obj instanceof String || obj instanceof Integer || obj instanceof Long ||
            obj instanceof Double || obj instanceof Float || obj instanceof Boolean) {
            return obj;
        }
        // 對于其他類型,轉(zhuǎn)換為字符串
        return obj.toString();
    }
}

Docker 容器化測試

這里引入 TestContainers 實現(xiàn)自動化測試,避免安裝 Clickhouse 的繁瑣、避免使用替身數(shù)據(jù)庫無法還原真實依賴,確保 MyBatis 與ClickHouse 數(shù)組操作的可靠性。

環(huán)境配置

Docker Compose 配置(docker-compose.yml):

version: '3.8'
services:
  clickhouse:
    image: clickhouse/clickhouse-server:latest
    ports:
      - "8123:8123"
      - "9000:9000"
    environment:
      CLICKHOUSE_DB: test_db
      CLICKHOUSE_USER: test_user
      CLICKHOUSE_PASSWORD: test_password
    healthcheck:
      test: ["CMD", "wget", "--spider", "http://localhost:8123/ping"]
      interval: 30s
      timeout: 10s

Maven 依賴:

<dependencies>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>testcontainers</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

集成測試實現(xiàn)

測試類核心實現(xiàn):

@SpringBootTest
@ActiveProfiles("test")
@Testcontainers
class ClickHouseIntegrationTest {

    @Container
    static GenericContainer<?> clickhouseContainer = new GenericContainer<>(
            DockerImageName.parse("clickhouse/clickhouse-server:latest"))
            .withExposedPorts(8123, 9000)
            .withEnv("CLICKHOUSE_DB", "test_db")
            .withEnv("CLICKHOUSE_USER", "test_user")
            .withEnv("CLICKHOUSE_PASSWORD", "test_password")
            .waitingFor(Wait.forHttp("/ping").forPort(8123));

    @Autowired
    private xxxMapper xxxMapper;
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", () -> 
            "jdbc:clickhouse://localhost:" + clickhouseContainer.getMappedPort(8123) + "/test_db");
        registry.add("spring.datasource.username", () -> "test_user");
        registry.add("spring.datasource.password", () -> "test_password");
    }

    @BeforeEach
    void initializeDatabase() {
        jdbcTemplate.execute("CREATE DATABASE IF NOT EXISTS test_db");
        jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS xxx_base (" +
            "uuid String, name String, xxx_type Array(String), userIds Array(String), " +
            "deviceIds Array(String)) ENGINE = MergeTree() ORDER BY uuid");
    }
}

核心測試用例

容器狀態(tài)驗證:

@Test
void testContainerIsRunning() {
    assertTrue(clickhouseContainer.isRunning());
}

數(shù)組 CRUD 操作測試:

@Test
void testArrayInsertAndQuery() {
    int result = xxxMapper.insert(testxxx);
    assertEquals(1, result);
    
    List<xxxBase> list = xxxMapper.selectByPage(0, 10);
    assertNotNull(list);
    
    xxxBase found = list.stream()
            .filter(a -> test.getUuid().equals(a.getUuid()))
            .findFirst().orElse(null);
    
    assertNotNull(found.getUserIds());
}

執(zhí)行測試

命令行執(zhí)行:

# 使用TestContainers自動化測試
mvn test -Dtest=ClickHouseIntegrationTest

# 或使用Docker Compose手動環(huán)境
docker-compose up -d && mvn test

測試配置(application-test.properties):

mybatis.type-handlers-package=com.test.clickhousemybatisdemo.typehandler
logging.level.com.test.clickhousemybatisdemo=DEBUG

驗證結(jié)果

容器化讀寫測試驗證 TypeHandler 實現(xiàn)了 Java List<T> 與 Clickhouse Array(T) 的映射關系。

以上就是Mybatis操作Clickhouse數(shù)組的最佳實踐分享的詳細內(nèi)容,更多關于Mybatis操作Clickhouse數(shù)組的資料請關注腳本之家其它相關文章!

相關文章

  • Java責任鏈模式的實現(xiàn)方法詳解

    Java責任鏈模式的實現(xiàn)方法詳解

    責任鏈模式是一種設計模式,在責任鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈,這篇文章主要介紹了Java責任鏈模式實現(xiàn)方法的相關資料,需要的朋友可以參考下
    2025-07-07
  • 使用SpringBoot Actuator監(jiān)控應用示例

    使用SpringBoot Actuator監(jiān)控應用示例

    Actuator是Spring Boot提供的對應用系統(tǒng)的自省和監(jiān)控的集成功能,可以對應用系統(tǒng)進行配置查看、相關功能統(tǒng)計等。這篇文章主要介紹了使用SpringBoot Actuator監(jiān)控應,有興趣的可以了解一下
    2018-05-05
  • Java使用線程池批量處理數(shù)據(jù)操作具體流程

    Java使用線程池批量處理數(shù)據(jù)操作具體流程

    這篇文章主要給大家介紹了關于Java使用線程池批量處理數(shù)據(jù)操作的相關資料,Java多線程編程中線程池是一個非常重要的概念,線程池可以提高線程的復用率和任務調(diào)度的效率,尤其是當需要查詢大批量數(shù)據(jù)時,需要的朋友可以參考下
    2023-06-06
  • springboot實現(xiàn)瀏覽器截屏并添加文字

    springboot實現(xiàn)瀏覽器截屏并添加文字

    大家好,本篇文章主要講的是springboot實現(xiàn)瀏覽器截屏并添加文字,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-02-02
  • JAVA文件掃描(遞歸)的實例代碼

    JAVA文件掃描(遞歸)的實例代碼

    這篇文章主要介紹了JAVA文件掃描(遞歸)的實例代碼 ,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • 基于Java并發(fā)容器ConcurrentHashMap#put方法解析

    基于Java并發(fā)容器ConcurrentHashMap#put方法解析

    下面小編就為大家?guī)硪黄贘ava并發(fā)容器ConcurrentHashMap#put方法解析。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Java多個線程同時執(zhí)行的方法

    Java多個線程同時執(zhí)行的方法

    這篇文章主要介紹了Java多線程處理文件詳解與代碼示例,通過本文的介紹和代碼示例,我們了解了如何使用Java多線程來處理文件,使用多線程技術可以顯著提高文件處理的效率,特別是對于大量文件的處理任務,需要的朋友可以參考下
    2024-12-12
  • Spring?Boot3整合OAuth2實現(xiàn)第三方登錄功能詳細示例

    Spring?Boot3整合OAuth2實現(xiàn)第三方登錄功能詳細示例

    OAuth是一個關于授權(quán)的開放網(wǎng)絡標準,在全世界得到廣泛應用,目前的版本是2.0版,這篇文章主要介紹了Spring?Boot3整合OAuth2實現(xiàn)第三方登錄功能的相關資料,需要的朋友可以參考下
    2025-06-06
  • Java多態(tài)中動態(tài)綁定原理解析

    Java多態(tài)中動態(tài)綁定原理解析

    這篇文章主要介紹了Java多態(tài)中動態(tài)綁定原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-12-12
  • Spring學習筆記3之消息隊列(rabbitmq)發(fā)送郵件功能

    Spring學習筆記3之消息隊列(rabbitmq)發(fā)送郵件功能

    這篇文章主要介紹了Spring學習筆記3之消息隊列(rabbitmq)發(fā)送郵件功能的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-07-07

最新評論

鄂州市| 西乌珠穆沁旗| 东明县| 新泰市| 昌乐县| 高雄市| 乌鲁木齐县| 白沙| 洞头县| 开化县| 石狮市| 聂拉木县| 锡林郭勒盟| 顺义区| 巴彦县| 拉萨市| 宜昌市| 宁都县| 黑龙江省| 通州区| 桂东县| 西乡县| 岳西县| 贵州省| 丰镇市| 灵川县| 博野县| 广南县| 莫力| 武山县| 宁蒗| 岚皋县| 五台县| 鸡西市| 团风县| 阿拉善盟| 海南省| 家居| 普兰店市| 安阳县| 江陵县|