Mostrando postagens com marcador Hibernate. Mostrar todas as postagens
Mostrando postagens com marcador Hibernate. Mostrar todas as postagens

terça-feira, 30 de julho de 2024

Pool de conexões no Spring Boot

         Toda aplicação precisa de um pool de conexões para gerenciar o acesso ao banco de dados de forma eficiente. No Spring Boot é carregado um pool automaticamente, mas devemos configurar para o ambiente de produção de acordo com a necessidade.

        

        A configuração varia de acordo com cada aplicação, então é preciso testar e observar o comportamento da aplicação pra saber se mais conexões disponíveis são necessárias.


        Segue um exemplo de configurações de pool no Spring Boot:


spring.datasource.hikari.minimum-idle=25

spring.datasource.hikari.maximum-pool-size=50

spring.datasource.hikari.connectionTimeout=15000  

spring.datasource.hikari.idleTimeout=600000

spring.datasource.hikari.maxLifetime=1800000 



quinta-feira, 8 de fevereiro de 2024

Relacionamento bidirecional recursivo com JPA

           Neste artigo vou mostrar um exemplo de um relacionamento bidirecional recursivo, ou seja, o objeto faz referência a ele mesmo. 


As vezes ao invés de usar herança, essa solução resolve de forma mais simples não tendo que alterar muita coisa no modelo já existente. Recentemente usei ela para o desdobramento de uma entidade que era a base do sistema e foi reclassificado em elementos "pai e filho". 


Claro que houve a necessidade de aumentar alguns campos na tabela que serão usados apenas quando for "filho" ou "pai", mas a desnormalização do modelo muitas vezes se faz necessário. O uso de outra solução acarretaria em uma grande mudança em todo o sistema. 


Para o exemplo vamos pensar para fins didáticos em uma cadeia de comando onde um Chefe tem Subordinados, mas também está subordinado à um Chefe.

 

No banco existem os seguintes campos: id, nome e chefe_superior_id. Segue o código:

 

 

import javax.persistence.*;

import java.util.List;

 

@Entity

@Table(name = "chefe")

public class Chefe {

 

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

 

    @Column(name = "nome")

    private String nome;

 

  @OneToMany(mappedBy = "chefe", cascade = CascadeType.ALL, fetch = FetchType.LAZY)

    private List<Chefe> subordinados;

 

    @ManyToOne

    @JoinColumn(name = "chefe_superior_id")

    private Chefe chefe;

 

    // getters e setters

 

}

sábado, 23 de julho de 2022

Como e porquê configurar o cache do Hibernate

Nesse artigo vou trazer algo que vi sobre a importância de fazer uso correto de cache do Hibernate em um artigo do Vlad Mihalcea que deixo no final para que possam acessar e ver outros detalhes como o gráfico que mostra a diferença de tempo entre a consulta que usa e a que não usa o cache.

 

Não vou me prender a demonstração da melhora de performance pois isto está no artigo dele. Aqui quero apenas mostrar a configuração necessária e dizer o porquê configurar o cache de acordo com o tamanho da sua aplicação.

 

As consultas JPQL são compiladas antes da execução, e como sabemos, há um custo para se compilar um código. Então, caso o cache não esteja configurado corretamente, todas as vezes que as consultas forem executas elas serão compiladas, gerando sempre esse custo adicional. As consultas usando Criteria API também passam por esse impacto porque são transformadas em JPQL;

 

Por padrão o Hibernate coloca 2048 consultas no cache, mas se seu sistema for muito grande isso pode não ser suficiente.

 

A propriedade que trata dessa configuração é a hibernate.query.plan_cache_max_size. Basta calcular quantas consultas seu sistema tem e configurar essa propriedade para atender essa demanda. Lembrando que o cache não armazena apenas JPQL, então o valor precisa ser a soma de todas as consultas do sistema.

 

No caso que Native Queries, embora não haja esse processo de compilação, o hibernate guarda metadados sobre os parâmetros passados, e a configuração desse cache é feita na propriedade hibernate.query.plan_parameter_metadata_max_size. O valor default é 128, mas como mostrado no artigo do Mihalcea, a diferença para Native Queries não é tão grande com ou sem o cache configurado.


Você pode configurar essas propriedades no persistence.xml como abaixo:


<persistence>
    <persistence-unit name="my-persistence-unit">
        ...
        <properties>
            <! – consultas - default: 2048 – >
            <property name="hibernate.query.plan_cache_max_size" value="4096"/>
            <! – metadados - default: 128 – >
            <property name="hibernate.query.plan_parameter_metadata_max_size"
                value="256"/>
        </properties>
    </persistence-unit>

</persistence>


Fonte: https://vladmihalcea.com/hibernate-query-plan-cache/

terça-feira, 25 de agosto de 2020

JPA - Carregando multiplas listas em uma mesma entidade evitando o erro MultipleBagFetchException

    É comum termos um entidade que precise carregar mais de uma lista, e quando essas listas precisam ser carregadas juntas no mesmo select, podemos nos deparar com o seguinte erro:
 
Caused by: org.hibernate.loader.MultipleBagFetchException: 
cannot simultaneously fetch multiple bags

    Bag significa algo como "bolsa" ou "sacola" (tradução livre rsrsrs), e isso quer dizer estamos tentando carregar mais de uma "bolsa" de dados e o JPA não sabe como organizar esses dados para nos entregar.

    Isso ocorre porque geralmente o banco nos devolve uma lista ordenada de dados (List), mas como o JPA está tentando montar uma lista que contém listas dentro delas, ele pode acabar desordenando essas listas. Então ele quer que você diga pra ele como tratar esses dados.

    Existem basicamente 3 formas de contornarmos esse problema:

  1. Nunca carregar as listas na mesma consulta.
  2. Usar Set ao invés de List ao criar as listas na entidade.
  3. Usando a anotação @OrderColumn.
   Vamos abordar cada uma delas a seguir:

    1 - Nunca carregar as listas na mesma consulta.

    Ao invés de você usar o FetchType.EAGER ou no select fazer um join fetch caso o carregamento seja FetchType.LAZY nas "n" listas da sua entidade, você carrega apenas uma delas no primeiro momento, e a(s) outra(s) você carrega separadamente, por exemplo chamando o método get da(s) outra(s) lista(s) com o objeto ainda gerenciado pelo JPA, de forma que ele vá ao banco e preencha essas listas.
     
    2 - Usar Set ao invés de List ao criar as listas na entidade.

    Outra solução é usar o Set ao invés do List. Como o Set não é ordenado como o List o JPA não vai se perder tentando ordenar essa lista. Lembrando que o Set não trabalha com dados duplicados.

    3 - Usando a anotação @OrderColumn.

    Você também pode criar um campo para o hibernate trabalhar a ordenação e não se perder. A desvantagem dessa solução é unicamente a de criar um campo extra para o JPA poder ordenar os dados. 

    Se o mapeamento for bidirecional, será preciso criar a propriedade e mapea-la na classe que detêm o mapeamento além de usá-la no @orderColumn.
    
@Entity
public class Paciente {
 
    //se tiver o mappedBy (bidirecional) precisa criar a propriedade na outra classe 
    @OneToMany(mappedBy="paciente")
    @OrderColumn(name="ordenacao_hibernate")
    private List<PacienteSintoma> lstPacienteSintoma;
 
    //se tiver o mappedBy (bidirecional) precisa criar a propriedade na outra classe 
    @OneToMany(mappedBy="paciente")
    @OrderColumn(name="ordenacao_hibernate")
    private List<PacienteDiagnostico> lstPacienteDiagnostico; 
 
    ...
}

@Entity
public class PacienteSintoma {
    ...
    //bidirecional, necessário mapear a propriedade
    @Column(name="ordenacao_hibernate")
    private int ordenacaoHibernate;

    @ManyToOne
    @JoinColumn(name="paciente_id", nullable=false)
    private Paciente  paciente;
 
    ...
}
 
@Entity
public class PacienteDiagnostico {
    ...
    //bidirecional, necessário mapear a propriedade
    @Column(name="ordenacao_hibernate")
    private int ordenacaoHibernate;

    @ManyToOne
    @JoinColumn(name="paciente_id", nullable=false)
    private Paciente  paciente;
 
    ...
} 
 
    Se o mapeamento for unidirecional, será preciso criar apenas o campo no banco e passá-lo no @orderColumn.

@Entity
public class Paciente {
 
    //unidirecional 
    @OneToMany
    @JoinColumn(name="paciente_id", nullable=false) 
    @OrderColumn(name="ordenacao_hibernate")
    private List<PacienteSintoma> lstPacienteSintoma;
 
    //se tiver o mappedBy (bidirecional) precisa criar a propriedade na outra classe 
    @OneToMany(mappedBy="paciente")
    @JoinColumn(name="paciente_id", nullable=false)  
    @OrderColumn(name="ordenacao_hibernate")
    private List<PacienteDiagnostico> lstPacienteDiagnostico; 
 
    ...
}

@Entity
public class PacienteSintoma {
    ...
    //bidirecional, necessário mapear a propriedade
    @Column(name="ordenacao_hibernate")
    private int ordenacaoHibernate;

    @ManyToOne
    @JoinColumn(name="paciente_id", nullable=false, insertable=false, updatable=false)
    private Paciente  paciente;
 
    ...
}
 
@Entity
public class PacienteDiagnostico {
    ...
    //bidirecional, necessário mapear a propriedade
    @Column(name="ordenacao_hibernate")
    private int ordenacaoHibernate;

    @ManyToOne
    @JoinColumn(name="paciente_id", nullable=false, insertable=false, updatable=false)
    private Paciente  paciente;
 
    ...
} 
Fonte: https://docs.jboss.org/hibernate/stable/annotations/reference/en/html/entity.html#entity-hibspec-collection-extratype-indexbidir


 

sexta-feira, 6 de março de 2020

E se eu tiver o persistence.xml e o hibernate.cfg.xml na mesma aplicação?

    As vezes existem projetos legados que usam a implementação do hibernate diretamente ao invés do JPA, e pode ser de interesse passar a usar a especificação ao invés da implementação diretamente. Porém nem sempre isso pode ser feito de uma vez: imagine um aplicação que usa muita criteria do hibernate.

    Nesse caso, você pode criar um arquivo persistence.xml e dentro dele chamar o arquivo do hibernate para aproveitar as configurações do arquivo que já existe, isto é possível através da seguinte propriedade dentro do persistence.xml:


<property name="hibernate.ejb.cfgfile" value="/hibernate.cfg.xml"/>

sábado, 25 de maio de 2019

Usando Hibernate Envers para auditoria


Pra quem não conhece o Hibernate Envers é o módulo de auditoria e versionamento de entidades. Neste post vou mostrar como configurar o envers no Spring Boot.

Primeiro precisamos adicionar a dependência do Envers no POM do nosso projeto:

<dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-envers</artifactId>
     <version>5.3.1.Final</version>
</dependency>

Precisamos também criar uma tabela padrão do Envers chamada “REVINFO” com dois campos, um ID e um campo pra guardar o timestamp das alterações:
  
CREATE TABLE revinfo (
rev integer NOT NULL,
revtstmp bigint,
CONSTRAINT revinfo_pkey PRIMARY KEY (rev)
)

Note que o campo para guardar a chave de revisão por padrão é um integer, para mudar para bigint é preciso fazer customização do Envers, criando uma entidade que contenha o campo do tipo Long como abaixo:

@Entity
@RevisionEntity
public class EntityRevisionCustom implements Serializable {
 
  @Id
  @GeneratedValue
  @RevisionNumber
  private Long rev;

  @RevisionTimestamp
  private Long timestamp;

  /* getts and setts */
}

Agora o SQL que cria a tabela passa a ser assim:

CREATE TABLE revinfo (

rev bigint NOT NULL,
revtstmp bigint,
CONSTRAINT revinfo_pkey PRIMARY KEY (rev)
)


Outra coisa que leva a criação dessa classe customizada é poder colocar mais informações nela, como por exemplo o usuário logado, que é o responsável pela alteração. Para isto é preciso criar um listener que possa pegar esse usuário e settar na entidade de forma transparente para o desenvolvedor.

Isso requer que informemos o Listener na classe customizada:

@Entity
@RevisionEntity(MyRevisionListener.class)
public class EntityRevisionCustom implements Serializable {
...
}

          Segue o exemplo de um listener que pega o usuário da sessão e preenche na classe customizada:

public class MyRevisionListener implements RevisionListener {
    @Override
    public void newRevision(Object revisionEntity) {
        EnversCustomRevision rev = (EnversCustomRevision) revisionEntity;
        rev.setUsuCodUsuario(SessionBean.getCodUsuarioLogado());
    }
}

Depois de adicionar a dependência e criar a tabela revinfo (fazendo as customizações ou não), podemos usar o Envers na nossa aplicação. Basta anotar cada Entidade que for auditada com a anotação @Audited do pacote org.hibernate.envers e criar uma tabela de mesmo nome da tabela original adicionando o sufixo “_AUD” (ou “_aud”) dependento do seu padrão, acrescentando dois campos: rev (do tipo integer se não tiver customizado) e revtype (do tipo smalint).

@Entity
@Table(name = "USU_USUARIO")
@Audited
public class Usuario implements Serializable {
...
}

Caso a tabela criada para auditoria seja diferente do nome da tabela da entidade + o sufixo “_AUD”, deve ser informado na classe colocando a anotação @AuditTable, que recebe a propriedade value para informar o nome da tabela a ser usada.   

@AuditTable(value = "USU_AUD")

Pronto, assim o sistema já está sendo auditado.