Soy bastante novato en Snowflake y en lugar de implementar Snowflake JDBCDriver, decidimos usar Hibernate/Spring-Data JPA para eso, ya que era más conveniente de usar. Llegamos a esta publicación: ¿Alguien creó una aplicación usando Java Spring Framework que se conecta a Snowflake en Snowflake Community y verificó si nuestro caso de uso se estaba cumpliendo con eso o no?
Según nuestro caso de uso, nuestra clase de modelo se ve así y hemos mantenido la parte del dialecto vacío y otras configuraciones tal como se describe en el enlace.
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() { } }Y creamos la tabla en Snowflake usando esta consulta:
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) );El código anterior funciona como se esperaba y genera la identificación incrementada automáticamente como clave principal. También intentamos ejecutar varias instancias de nuestro servicio y, dado que Snowflake no aplica la restricción única, nos encontramos con el problema de tener valores de identificación duplicados. (Había varias fuentes de inserción de datos para una sola tabla).
Con respecto al dialecto, no pudimos encontrar ningún dialecto de Hibernate para Snowflake, por lo que usamos los mismos detalles del dialecto que se describen en el enlace de referencia. Creamos la clase EmptyDialect y le dimos su ruta en el archivo de propiedades.
public class EmptyDialect extends org.hibernate.dialect.Dialect {}Archivo de propiedades:
spring.jpa.properties.hibernate.dialect= absolute path of the EmptyDialect classProbamos todas las estrategias de generación de ID como IDENTIDAD, SECUENCIA, AUTO, etc., pero recibimos excepciones que pueden deberse a que no tenemos un dialecto de copo de nieve separado. Agregará el seguimiento de la pila de los errores si es necesario.
Creamos la secuencia a través de la siguiente consulta y, en consecuencia, también realizamos cambios en la consulta de creación de tabla y las anotaciones.
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 ejecutará la siguiente consulta en el momento de la inserción de la Entidad:
select next_val as id_val from sequence_note for updateSeguimiento de la pila de errores:
{"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)Entonces, ¿hay alguna forma de administrar la generación del campo Id (único, incremento automático, clave principal) desde el propio código Springboot?
ACTUALIZACIÓN EL 01/03/2022
Gracias a la respuesta de Alexey Veleshko, logramos resolver esta Excepción realizando los siguientes cambios en nuestro código.
La clase EmptyDialect ahora se ve así:
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; } }Aquí, estamos anulando los métodos que consultarán para obtener el nextVal de la base de datos subyacente y generarán la identificación autoincrementada para la tabla.
Pero, según nuestro caso de uso, queremos la inserción de entidades en un lote e incluso si se ejecutan varias instancias de un servicio, siempre debe generar un valor único y AutoIncrementado para Id para cada entidad. En este caso, cuando se inicie la aplicación, en el momento de la inserción de la entidad, Hibernate consultará para obtener el nextVal de la secuencia. Y ahora se insertará un lote de entidades con los valores de identificación generados, para la inserción de otro lote, Hibernate no consultará en la secuencia de Snowflake por nextVal pero obtendrá el último valor de su memoria local (último nextVal generado + número de insertados entidades). Ahora suponga que hay varias instancias de la aplicación ejecutándose e insertando las entidades en Snowflake. Como estas instancias no consultarán en la base de datos el nextVal en cada inserción, estas instancias podrían tener el mismo nextVal almacenado en su memoria local, lo que daría como resultado una duplicación de ID en la base de datos.
en lugar de usar la función de incremento automático, puede usar secuencias, que luego puede manipular usando sus propias propiedades nativas.
Puede leer la documentación sobre secuencias y su uso sugerido aquí:
Supongo que la esencia del problema es que desea realizar inserciones por lotes a través de múltiples instancias de aplicaciones en la misma tabla de copos de nieve que tienen una clave principal (id) única y de incremento automático.
Snowflake usa la estrategia SEQUENCE para incrementar automáticamente la identificación de la clave principal.
Ahora, como lo mencionaron usted y Alexey Veleshko, podemos manipular la consulta de secuencia para "seleccionar secuencia_nota.nextVal" en la clase Dialect anulando algunos de los métodos. Pero como su aplicación activará esta consulta solo una vez en el arranque, no resolverá el problema mientras haya varias instancias de aplicación que intenten insertar un lote de datos en la misma tabla.
Entonces, lo que necesitamos aquí es ejecutar:
"select sequence_note.nextVal"consulta antes de cada llamada de inserción para tener el último valor de "id" para cada fila. Podemos lograr este tipo de comportamiento ejecutándolo manualmente.
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();Sé que esta no es una solución óptima, ya que necesitaría ejecutar una consulta adicional antes de cada llamada de inserción, pero mirando su caso de uso, creo que este podría ser su último recurso. Otra solución puede ser usar la siguiente consulta:
insert into note (user_id, content) values (1, "testContent");Como su consulta de generación de tablas es:
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) );Administrará directamente la generación de identificación por usted (sin embargo, si desea usar estrictamente Spring JPA, esto no sería apropiado para que lo use)