Problema envolvendo tabela particionada + Spring Boot: GenerationTarget encountered exception accepting command : Error executing DDL

Hibernate

PostgreSQL

Java

Spring Data JPA

Spring Boot

31/05/2023

Estou trabalhando com tabelas particionadas usando Postgres e Spring Boot, consegui fazer a partição e nos testes que eu fiz o método GET e POST está funcionando do jeito que eu queria, porém gostaria de ajuda para resolver o seguinte erro, que é retornado quando eu inicio a aplicação:

GenerationTarget encountered exception accepting command : Error executing DDL "create table teste (data date not null, id int4 not null, nome varchar(255), primary key (data, id))" via JDBC Statement org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table teste (data date not null, id int4 not null, nome varchar(255), primary key (data, id))" via JDBC Statement at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:67) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]


O passo a passo do que eu fiz antes do erro aparecer:

Criei o Model principal, que fará referência a tabela particionada:

    @Entity
    @Table(name = "teste")
    @IdClass(TesteId.class)
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Teste {

        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "teste_id_seq")
        @SequenceGenerator(name = "teste_id_seq", sequenceName = "teste_id_seq", initialValue = 1, allocationSize = 1)
        private int id;

        @Id
        private LocalDate data;

        private String nome;

    }


Criei a classe do id composto:

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @EqualsAndHashCode
    public class TesteId implements Serializable {

        private static final long serialVersionUID = 1L;

        private int id;

        private LocalDate data;

    }


E então iniciei a aplicação, tudo rodou normalmente.

Após isso pausei a aplicação e particionei a tabela ''teste'' seguindo a documentação do Postgres: https://www.postgresql.org/docs/current/ddl-partitioning.html Tópico: 5.11.2.1. Example

E minha tabela ficou da seguinte forma:

    CREATE TABLE IF NOT EXISTS public.teste
    (
        data date NOT NULL,
        id integer NOT NULL,
        nome character varying(255) COLLATE pg_catalog."default",
        CONSTRAINT teste_pkey PRIMARY KEY (data, id)
    ) PARTITION BY RANGE (data);



Então eu iniciei a aplicação novamente e o seguinte erro foi impresso no log do Spring Boot:

    WARN 16668 --- [ main] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget             
    encountered exception accepting command : Error executing DDL "create table teste (data 
    date not null, id int4 not null, nome varchar(255), primary key (data, id))" via JDBC 
    Statement org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL 
    "create table teste (data date not null, id int4 not null, nome varchar(255), primary 
    key (data, id))" via JDBC Statement at 
    org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetTo
    Database.java:67) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final] at 
    org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlString(AbstractSchemaMigra
    tor.java:562) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final] at 
    org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlStrings(AbstractSchemaMigr
    ator.java:507) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]


Meu application.properties está assim:

    spring.datasource.url=jdbc:postgresql://localhost:5432/teste
    spring.datasource.username=postgres
    spring.datasource.password=postgres
    spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.defer-datasource-initialization=true
    spring.datasource.driver-class-name=org.postgresql.Driver


Eu sei que alterar o ''spring.jpa.hibernate.ddl-auto'' para ''none'' resolveria o problema, porém não é a solução que eu desejo, já que não quero gerenciar manualmente todas as alterações no banco de dados.
Yago Vieira

Yago Vieira

Curtidas 0

Respostas

Frank Hosaka

Frank Hosaka

31/05/2023

Eu testei o comando no MySQL

create table teste (data date not null, id int4 not null, nome varchar(255), primary key (data, id))


e a mensagem que apareceu foi esse:

Error Code: 1050. Table 'teste' already exists


Por que você está tentando criar um arquivo que já existe?

GOSTEI 0
Yago Vieira

Yago Vieira

31/05/2023

A questão é o seguinte:

A tabela já existe realmente e eu não quero tentar criar ela novamente, mas é o Hibernate que está tentando fazer isso.

Quando eu inicio a aplicação o Hibernate não tentar criar novamente as tabelas "normais", porém tenta criar novamente a tabela que eu particionei.

Eu recriei o banco de dados várias vezes para fazer testes e só retorna o erro se eu particionar a tabela.
GOSTEI 0
Yago Vieira

Yago Vieira

31/05/2023

A questão é o seguinte:

A tabela já existe realmente e eu não quero tentar criar ela novamente, mas é o Hibernate que está tentando fazer isso.

Quando eu inicio a aplicação o Hibernate não tentar criar novamente as tabelas "normais", porém tenta criar novamente a tabela que eu particionei.

Eu recriei o banco de dados várias vezes para fazer testes e só retorna o erro se eu particionar a tabela.
GOSTEI 0
Arthur Heinrich

Arthur Heinrich

31/05/2023

O Hibernate é um framework que auxilia na construção dinâmica das queries. Porém, precisa saber utilizá-lo.

No entanto, queria entender o por quê você está particionando a tabela e por quê por data.

O particionamento é uma feature muito restrita. São raros os casos onde é realmente útil.

Particionar por data vai criar uma partição para cada data? Não faz muito sentido.
GOSTEI 0
Yago Vieira

Yago Vieira

31/05/2023

O Hibernate é um framework que auxilia na construção dinâmica das queries. Porém, precisa saber utilizá-lo.

No entanto, queria entender o por quê você está particionando a tabela e por quê por data.

O particionamento é uma feature muito restrita. São raros os casos onde é realmente útil.

Particionar por data vai criar uma partição para cada data? Não faz muito sentido.


Resposta:

A ideia é criar particionamentos mensais, exemplo: janeiro, fevereiro, março ...

O principal motivo é poder apagar os dados mais antigos depois de um 1 ano ou mais, sem tem que rodar uma query na tabela principal. Além disso, caso seja possível e eu consiga, futuramente eu gostaria de fazer SELECTs direto em uma tabela particionada, exemplo: quero buscar os dados somente de hoje, então vou fazer uma consulta na tabela de junho, para não precisar filtrar na tabela principal, onde terá muitos dados.

Eu consegui resolver o problema que mencionei, o que fiz foi atualizar o Spring Boot para a última versão e adicionar a seguinte dependência no pom.xml:
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>6.2.3.Final</version>
		</dependency>



Mas se tiver uma forma mais adequada de lidar com esse caso, sem usar particionamento, eu gostaria de ouvir sugestões.
GOSTEI 0
Arthur Heinrich

Arthur Heinrich

31/05/2023

Eu não conheço o PostgreSQL a fundo, para saber como funciona o particionamento dele. Pelo que pude ver, até o momento, não existem "global indexes". Então, a estratégia de dropar partições, ao invés de excluir dados, parece ser uma alternativa viável.

Porém, você precisa ter em mente que, ao não permitir índices globais, sempre que você pensar em filtrar uma determinada característica que não seja a utilizada na partição, o banco terá que fazer múltiplos range scans.

Imagine uma tabela com 5 anos de dados, particionada mês a mês. O banco precisará acessar cada uma das 60 partições e efetuar a busca no índice.

Agora, imagine um join entre duas tabelas particionadas. Se a tabela A e B possuem ambas 60 partições, ao fazer um join, o banco vai acessar cada uma das partições da tabela A e, para fazer o join com a tabela B, terá que acessar todas as partições da tabela B, totalizando 3600 combinações.

Agora que ficou claro que você pode estar adicionando uma dificuldade a mais, para obter um bom desempenho nas queries, que tal pensar em uma estratégia que não utilize partições?

Ao invés de purgar 1 mês inteiro, durante a virada, porque não purgar 1 dia por vez, durante a madrugada, ou mesmo purgar 1 hora de dados, de hora em hora?
GOSTEI 0
POSTAR