Mostrando postagens com marcador JPA. Mostrar todas as postagens
Mostrando postagens com marcador JPA. 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"/>

quinta-feira, 14 de novembro de 2019

Criando DataSource no Tomcat 7 e usando com persistence.xml e/ou hibernate.cfg.xml

     Quando criamos uma aplicação, é comum termos ambientes diferentes e/ou clientes diferentes, e isso nos traz a necessidade de fazer a nossa aplicação se conectar com bases de dados diferentes para cada ambiente/cliente. Para resolver isso de forma elegante, tiramos a responsabilidade de conhecer a URL, Usuário e Senha do Banco da aplicação e passamos para o servidor. Nesse post mostro como montar o DataSource no Tomcat. (Foi feito no Tomcat 7 que é o que uso no trabalho).


1 - Copiar as libs referentes ao Driver JDBC e c3p0 para o TomCat



Ex:

- Driver JDBC

postgresql-8.4-702.jdbc3.jar



-Lib c3p0

c3p0-0.9.1.jar



- Dependências da lib c3p0

antlr-2.7.6.jar

commons-collections-3.2.jar

dom4j-1.6.1.jar

jta-1.0.1B.jar



2 - Adicione o seguinte trecho ao arquivo web.xml (Note que o <res-ref-name> contém o mesmo nome que vamos dar a configuração do DataSource no Tomcat)



<resource-ref>

<description>DB Connection</description>

<res-ref-name>jdbc/nome_datasource</res-ref-name>

<res-type>javax.sql.DataSource</res-type>

<res-auth>Container</res-auth>

</resource-ref>



Para quem usa hibernate.cfg.xml



3 - No hibernate.cfg.xml remova as informações de conexão (url, usuário e senha) e de pool, e acrescente apenas a seguinte linha:



<property name="hibernate.connection.datasource">java:/comp/env/jdbc/nome_datasource</property>



Para quem usa JPA



4 - No persistence.xml remova as informações de conexão (url, usuário e senha) e de pool, e acrescente apenas a seguinte linha:



<non-jta-data-source>java:comp/env/jdbc/nome_datasource</non-jta-data-source>



5 - Configurar o context.xml (apache-tomcat-7.0.96/conf/context.xml) do Tomcat adicionando o trecho de código abaixo



(Alterando endereço, senha e usuário do banco de acordo com o ambiente/cliente que o Tomcat está alocado)



<Resource name="jdbc/nome_datasource" auth="Container"

factory="org.apache.naming.factory.BeanFactory"

type="com.mchange.v2.c3p0.ComboPooledDataSource"

driverClass="org.postgresql.Driver"

jdbcUrl="jdbc:postgresql://x.x.x.x:5432/nome_banco"

user="usuario_bd"

password="senha_bd"

minPoolSize="10"

maxPoolSize="160" />







Fonte: