簡體   English   中英

如何使用 Hibernate/Spring JPA 為 Snowflake 表生成唯一的自動增量 ID?

[英]How to generate Unique, AutoIncremented Id using Hibernate/Spring JPA for Snowflake table?

我是 Snowflake 的新手,我們決定使用 Hibernate/Spring-Data JPA,而不是實現 Snowflake JDBCDriver,因為這樣使用起來更方便。 我們通過這篇文章: 是否有人使用 Java Spring 框架構建了一個應用程序,該框架連接到雪花社區上的雪花,並檢查了我們的用例是否得到滿足。

根據我們的用例,我們的 Model class 看起來像這樣,我們保持空方言部分和其他配置與鏈接中描述的相同。


import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

@Entity
@Table(name = "note")
public class Note implements Serializable {

    @Id
    @GenericGenerator(name = "id_generator", strategy = "increment")
    @GeneratedValue(generator = "id_generator")
    private Long id;

    @Basic(optional = false)
    @Column(name = "user_id")
    private String userId;

    @Basic(optional = false)
    @Column(name = "content")
    private String content;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Note(String userId, String content) {
        this.userId = userId;
        this.content = content;
    }

    public Note() {
    }
}

我們使用以下查詢在 Snowflake 中創建了表:

CREATE OR REPLACE TABLE "WAREHOUSE"."SCHEMA".note (
 id INT NOT NULL AUTOINCREMENT UNIQUE,
 user_id STRING NOT NULL, 
 content STRING NOT NULL, 
 PRIMARY KEY (id)   
);

上面的代碼按預期工作,並生成自動遞增的 id 作為主鍵。 我們還嘗試運行我們服務的多個實例,並且由於 Snowflake 沒有強制執行唯一約束,我們遇到了重復 id 值的問題。 (單個表有多個數據插入源。)

關於方言,我們找不到任何適用於雪花的 Hibernate 方言,因此我們使用了與參考鏈接中所述相同的方言詳細信息。 我們創建了 EmptyDialect class 並在屬性文件中給出了它的路徑。

public class EmptyDialect extends org.hibernate.dialect.Dialect {}

屬性文件:

spring.jpa.properties.hibernate.dialect= absolute path of the EmptyDialect class 

我們已經嘗試了所有的 ID 生成策略,如 IDENTITY、SEQUENCE、AUTO 等,但收到的異常可能是由於沒有單獨的雪花方言。 如果需要,將添加錯誤的堆棧跟蹤。

  • 序列方法:

我們通過以下查詢創建了序列,並相應地對表創建查詢和注釋進行了更改。

create or replace sequence "Warehouse"."Schema".sequence_note start = 1 increment = 1;
CREATE OR REPLACE TABLE "Warehouse"."Schema".note (
 id INT NOT NULL DEFAULT "Warehouse"."Schema".SEQUENCE_NOTE.nextval UNIQUE,
 user_id STRING NOT NULL, 
 content STRING NOT NULL, 
 PRIMARY KEY (id)   
);
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_note")
private Long id;

Hibernate 將在插入實體時執行以下查詢:

select next_val as id_val from sequence_note for update

錯誤堆棧跟蹤:

{"time":"2021-12-20T06:11:07.335+00:00","@version":1,"message":"SQL Error: 1003, SQLState: 42000","logger_name":"org.hibernate.engine.jdbc.spi.SqlExceptionHelper","thread_name":"http-nio-8080-exec-2","level":"WARN","caller_class_name":"org.hibernate.engine.jdbc.spi.SqlExceptionHelper","caller_method_name":"logExceptions","caller_file_name":"SqlExceptionHelper.java","caller_line_number":137}
{"time":"2021-12-20T06:11:07.337+00:00","@version":1,"message":"SQL compilation error:
syntax error line 1 at position 45 unexpected 'for'.","logger_name":"org.hibernate.engine.jdbc.spi.SqlExceptionHelper","thread_name":"http-nio-8080-exec-2","level":"ERROR","caller_class_name":"org.hibernate.engine.jdbc.spi.SqlExceptionHelper","caller_method_name":"logExceptions","caller_file_name":"SqlExceptionHelper.java","caller_line_number":142}
{"time":"2021-12-20T06:11:12.428+00:00","@version":1,"message":"Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: error performing isolated work; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: error performing isolated work] with root cause","logger_name":"org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]","thread_name":"http-nio-8080-exec-2","level":"ERROR","stack_trace":"net.snowflake.client.jdbc.SnowflakeSQLException: SQL compilation error:
syntax error line 1 at position 45 unexpected 'for'.
at net.snowflake.client.jdbc.SnowflakeUtil.checkErrorAndThrowExceptionSub(SnowflakeUtil.java:127)
at net.snowflake.client.jdbc.SnowflakeUtil.checkErrorAndThrowException(SnowflakeUtil.java:67)
at net.snowflake.client.core.StmtUtil.pollForOutput(StmtUtil.java:442)
at net.snowflake.client.core.StmtUtil.execute(StmtUtil.java:345)
at net.snowflake.client.core.SFStatement.executeHelper(SFStatement.java:487)
at net.snowflake.client.core.SFStatement.executeQueryInternal(SFStatement.java:198)
at net.snowflake.client.core.SFStatement.executeQuery(SFStatement.java:135)
at net.snowflake.client.core.SFStatement.execute(SFStatement.java:781)
at net.snowflake.client.core.SFStatement.execute(SFStatement.java:677)
at net.snowflake.client.jdbc.SnowflakeStatementV1.executeQueryInternal(SnowflakeStatementV1.java:238)
at net.snowflake.client.jdbc.SnowflakePreparedStatementV1.executeQuery(SnowflakePreparedStatementV1.java:117)

那么,有沒有辦法從 Springboot 代碼本身管理 Id 字段(唯一、自動增量、主鍵)的生成?

2022 年 3 月 1 日更新

感謝 Alexey Veleshko 的回答,我們設法通過對代碼進行以下更改來解決此異常。

EmptyDialect Class 現在看起來像這樣:

public class EmptyDialect extends org.hibernate.dialect.Dialect {
     
    @Override
    public String getSelectSequenceNextValString(String sequenceName) {
        return sequenceName + ".nextVal";
    }

    @Override
    public String getSequenceNextValString(String sequenceName) {
        return "select " + getSelectSequenceNextValString(sequenceName);
    }

    @Override
    public boolean supportsSequences() {
        return true;
    }

    @Override
    public boolean supportsPooledSequences() {
        return true;
    }
  
}

在這里,我們將覆蓋將從基礎數據庫中查詢以獲取 nextVal 並為表生成 AutoIncremented Id 的方法。

但是,根據我們的用例,我們希望批量插入實體,即使服務的多個實例正在運行,它也應該始終為每個實體的 Id 生成一個唯一且 AutoIncremented 值。 在這種情況下,當應用程序啟動時,在插入實體時,Hibernate 將查詢從序列中獲取 nextVal。 現在將使用生成的 id 值插入一批實體,對於插入另一批,Hibernate 不會在雪花序列中查詢 nextVal,而是會從其本地內存中獲取最后一個值(最后生成的 nextVal + 插入的數量實體)。 現在假設有多個應用程序實例在運行並在雪花中插入實體。 由於這些實例不會在每次插入時在數據庫中查詢 nextVal,因此這些實例可能將相同的 nextVal 存儲在其本地 memory 中,這將導致數據庫中的 id 重復。

您可以使用序列,而不是使用自動增量功能,然后您可以使用它們自己的本機屬性對其進行操作。

您可以在此處閱讀有關序列及其建議用法的文檔:

創建序列 — Snowflake Documentation

使用序列 — Snowflake Documentation

在使用帶有 JPA 的雪花時,是否有人得到了上述問題的任何解決方案。

我猜這個問題的要點是你想通過多個應用程序實例在同一個雪花表上執行批量插入,該表具有唯一且自動遞增的主鍵(id)。

Snowflake 使用 SEQUENCE 策略來自動增加主鍵 ID。

現在正如您和 Alexey Veleshko 所提到的,我們可以通過覆蓋某些方法來操作序列查詢以在方言 class 中“選擇 sequence_note.nextVal” 但由於此查詢只會在啟動時由您的應用程序觸發一次,因此當有多個應用程序實例試圖將一批數據插入同一個表時,它不會解決問題。

所以,我們這里需要執行的是:

"select sequence_note.nextVal"

在每次插入調用之前查詢以獲取每行的最新“id”值。 我們可以通過手動執行來實現這種行為。

entitiesToSave.stream().filter(Objects::nonNull).forEach(entity -> {
    int nextVal = sequenceRepo.getNextVal();
    entity.setId(Long.valueOf(nextVal));
    entityRepo.save(entity);
});



 @Query(nativeQuery = true, value = "select sequence_note.nextVal")
    int getNextVal();

我知道這不是最佳解決方案,因為您需要在每次插入調用之前執行額外的查詢,但查看您的用例我認為這可能是您最后的手段。 另一種解決方案是使用以下查詢:

insert into note (user_id, content) values (1, "testContent");

由於您的表生成查詢是:

CREATE OR REPLACE TABLE "Warehouse"."Schema".note (
 id INT NOT NULL DEFAULT "Warehouse"."Schema".SEQUENCE_NOTE.nextval UNIQUE,
 user_id STRING NOT NULL, 
 content STRING NOT NULL, 
 PRIMARY KEY (id)   
);

它將直接為您管理 id 生成(但是,如果您嚴格想使用 Spring JPA 這不適合您使用)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM