【发布时间】:2022-01-18 12:59:06
【问题描述】:
我是 Snowflake 的新手,我们决定使用 Hibernate/Spring-Data JPA,而不是实现 Snowflake JDBCDriver,因为这样使用起来更方便。我们在 Snowflake 社区看到了这篇帖子:Has anybody built an application using Java Spring Framework that connects to Snowflake,并检查了我们的用例是否得到了满足。
根据我们的用例,我们的模型类看起来像这样,并且我们保持空方言部分和其他配置与链接中描述的相同。
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 值的问题。 (单个表有多个数据插入源。)
关于方言,我们找不到任何适用于 Snowflake 的 Hibernate 方言,因此我们使用了与参考链接中所述相同的方言详细信息。我们创建了 EmptyDialect 类并在属性文件中给出了它的路径。
public class EmptyDialect extends org.hibernate.dialect.Dialect {}
属性文件:
spring.jpa.properties.hibernate.dialect= absolute path of the EmptyDialect class
我们已经尝试了 IDENTITY、SEQUENCE、AUTO 等所有 ID 生成策略,但收到了可能是由于没有单独的雪花方言而导致的异常。如果需要,将添加错误的堆栈跟踪。
- 序列法:
我们通过以下查询创建了序列,并相应地对表创建查询和注释进行了更改。
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 在插入 Entity 时会执行如下查询:
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 类现在看起来像这样:
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 的方法 基础数据库,并将为表生成自动增量 ID。
但是,根据我们的用例,我们希望批量插入实体,即使服务的多个实例正在运行,它也应该始终为每个实体的 Id 生成一个唯一且自动递增的值。在这种情况下,当应用程序启动时,在插入实体时,Hibernate 将查询从序列中获取 nextVal。现在将使用生成的 id 值插入一批实体,对于另一批的插入,Hibernate 不会在雪花序列中查询 nextVal,而是会从其本地内存中获取最后一个值(最后生成的 nextVal + no. of inserted实体)。现在假设有多个应用程序实例在运行并在雪花中插入实体。由于这些实例不会在每次插入时在数据库中查询 nextVal,因此这些实例可能会在其本地内存中存储相同的 nextVal,这会导致数据库中的 id 重复。
【问题讨论】:
标签: java hibernate spring-data-jpa snowflake-cloud-data-platform auto-increment