


[{"content":" Anteriormente\u0026hellip; # No primeiro artigo introduzimos o conceito do Persistence Context e exploramos algumas de suas peculiaridades usando um exemplo.\nFocamos em como ele pode nos ajudar (ou não tanto) ao buscar entidades, mas como ele realmente carrega e gerencia essas entidades? E por que o recurso de leituras repetíveis (repeatable-reads) existe, dadas as limitações que vimos no último artigo?\nVamos dar uma olhada!\nO processo de carregamento de entidades do Hibernate # Quando utilizamos o Hibernate para carregar uma entidade, ele verifica, em ordem, o cache de primeiro nível, o cache de segundo nível (se habilitado) e o banco de dados.\nAssumindo que estamos solicitando uma entidade pela primeira vez, teríamos um miss em ambos os caches, carregando os dados diretamente do banco de dados.\nQuando os dados são retornados, eles vêm com dois objetos: a entidade e o estado da entidade (entity loaded state). O estado é o ResultSet JDBC convertido para um Object[] e é usado para determinar quais mudanças foram feitas na entidade antes de enviá-la de volta ao banco de dados (flushing). Tanto a entidade quanto o estado da entidade são armazenados no cache de primeiro nível.\nPrimeiro carregamento da entidade Suponha agora que solicitamos a mesma entidade usando o método EntityManager.find. Como os dados já estarão no Persistence Context teremos um cache hit, retornando a entidade sem ir ao banco de dados.\nCarregando uma entidade já presente no PC com .find() Se solicitarmos a mesma entidade usando uma consulta JPQL/HQL, teremos os mesmos passos de carregamento, mas neste caso não há como o EntityManager saber que nossa consulta requisita uma entidade que já está carregada.\nPor causa disso, ele solicitará que o objeto seja carregado mesmo que o resultado não seja persistido no cache de primeiro nível, já que a entidade já está lá.\nCarregando uma entidade já presente no PC com JPQL O cache de segundo nível armazena diretamente os estados das entidades então, se estiver habilitado, podemos evitar ir ao DB ao solicitar a entidade novamente. Se não estiver habilitado, passamos pelo mesmo processo da primeira vez que solicitamos a entidade.\nEstados da entidade no Persistence Context # Dentro do Persistence Context, podemos ter uma entidade em quatro estados diferentes: New Transient, Managed, Removed ou Detached. Cada um ocorre em diferentes estágios do ciclo de vida da entidade:\nNew Transient: uma entidade que o Persistence Context ainda não sabe que existe, por exemplo, uma entidade recém-criada que será inserida no banco de dados; Managed: quando uma entidade é persistida, ela passa para o estado Gerenciado. Neste estágio, o Persistence Context fica ciente das mudanças feitas na entidade usando o estado da mesma, explorado na seção anterior. Isso é usado para determinar quando e como uma entidade deve ser enviada para o DB; Removed: quando usamos remove em uma entidade gerenciada, a mudamos para o estado removido. Entidades neste estado também são enviadas para o banco de dados; Detached: quando detachamos ou clearamos o contexto, as entidades vão para o estado desanexado. Essas entidades não são mais gerenciadas pelo persistence context, a menos que sejam persistidas novamente; O diagrama abaixo representa a relação entre cada estado (encontrado neste ótimo artigo do Vlad):\nTransições de estado da entidade Quando tentamos carregar uma entidade do cache de primeiro nível, o que o EntityManager procura são as entidades Gerenciadas (Managed) no Persistence Context. Se não for encontrada, a carregamos da fonte de dados para o cache de primeiro nível como uma nova entidade gerenciada.\nLeituras repetíveis do Hibernate (Hibernate\u0026rsquo;s repeatable reads) # Tudo o que foi explorado acima permite que o Hibernate implemente leituras repetíveis a nível de aplicação: consultar uma entidade sempre retornará o mesmo resultado se essa entidade já estiver carregada no Persistence Context, desde que nenhuma mudança tenha sido feita na mesma transação.\nEste recurso é essencial para o design de persistência do Hibernate, evitando que atualizações de entidades sejam perdidas em cenários concorrentes.\nCom isso em mente, leituras repetíveis podem levar a resultados inconsistentes ao lidar com transações de longa duração.\nComo vimos acima, se fizermos uma consulta JPQL/HQL para uma entidade que já estava carregada no Persistence Context, o cache será ignorado e a entidade será carregada diretamente do banco de dados. No entanto, devido ao recurso de leituras repetíveis, a entidade recém-carregada é ignorada e os dados carregados anteriormente permanecem inalterados no Persistence Context, mesmo que o snapshot do banco de dados seja diferente do estado carregado atual.\nIsso é por design, já que o Hibernate está mais focado em escritas consistentes do que em leituras. O diagrama abaixo ilustra uma transação de longa duração onde os dados da entidade são alterados durante a operação (fonte).\nLeitura repetível no nível da aplicação Neste caso, não queremos que as mudanças feitas na entidade durante a transação sejam perdidas por causa de mudanças feitas em outra operação.\nSe o objetivo for apenas a leitura de dados atualizados em relação a fonte de dados é muito mais recomendado usar projeções SQL, pois elas ignoram o cache de primeiro nível, indo diretamente para o BD.\nConclusão # Hibernate e JPA são frameworks muito robustos que facilitam o trabalho com bancos de dados em ambientes Java onde temos relações e operações complexas entre entidades. Por causa disso, alguns dos mecanismos internos dessas ferramentas são complexos e alguns dos erros que podem surgir com elas não são os mais claros.\nCom este artigo e o anterior, espero que alguns dos detalhes relacionados ao carregamento de entidades estejam mais claros e que isso ajude você a projetar um código melhor usando esses frameworks. Boa programação :)!\n","date":"2025-07-15","externalUrl":null,"permalink":"/pt-br/posts/persistence-context-deep-dive/","section":"Posts","summary":"Este artigo explora como o Persistence Context carrega e gerencia entidades, abordando as possíveis armadilhas associdas a esse processo.","title":"Aprofundando no Persistence Context","type":"posts"},{"content":"","date":"2025-07-15","externalUrl":null,"permalink":"/pt-br/tags/hibernate/","section":"Tags","summary":"","title":"Hibernate","type":"tags"},{"content":"","date":"2025-07-15","externalUrl":null,"permalink":"/pt-br/","section":"L10e","summary":"","title":"L10e","type":"page"},{"content":"","date":"2025-07-15","externalUrl":null,"permalink":"/pt-br/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"2025-07-15","externalUrl":null,"permalink":"/pt-br/tags/spring/","section":"Tags","summary":"","title":"Spring","type":"tags"},{"content":"","date":"2025-07-15","externalUrl":null,"permalink":"/pt-br/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" Contexto # Trabalhando em um projeto Spring Data JPA + Hibernate me deparei com algo semelhante a isso:\n// SomeClass.java file @Component public SomeClass { private final CoolJpaRepository coolJpaRepository; private final AnotherClass anotherClass; public SomeClass( final CoolJpaRepository coolJpaRepository, final AnotherClass anotherClass ) { this.coolJpaRepository = coolJpaRepository; this.anotherClass = anotherClass; } @Transactional public void doSomething() { // ... coolJpaRepository.findByCustomField(); anotherClass.doAnotherThing(); // ... } } // AnotherClass.java file @Component public AnotherClass { private final CoolJpaRepository coolJpaRepository; public AnotherClass(final CoolJpaRepository coolJpaRepository) { this.coolJpaRepository = coolJpaRepository; } @Transactional(propagation = Propagation.REQUIRES_NEW) public void doAnotherThing() { // ... coolJpaRepository.findByCustomField(); // ... } } Analisando o exemplo # Primeiro, temos uma classe chamada SomeClass que depende tanto de um repositório JPA chamado CoolJpaRepository quanto de outra classe gerenciada pelo Spring chamada AnotherClass.\n@Component public SomeClass { private final CoolJpaRepository coolJpaRepository; private final AnotherClass anotherClass; public SomeClass( final CoolJpaRepository coolJpaRepository, final AnotherClass anotherClass ) { this.coolJpaRepository = coolJpaRepository; this.anotherClass = anotherClass; } // ... } Esta classe tem um método que executa dentro de uma transação e chama o método findByCustomField do repositório JPA e doAnotherThing de AnotherClass.\n@Transactional public void doSomething() { // ... coolJpaRepository.findByCustomField(); anotherClass.doAnotherThing(); // ... } Quando olhamos para a classe AnotherClass, vemos que ela também depende de CoolJpaRepository.\n@Component public AnotherClass { private final CoolJpaRepository coolJpaRepository; public AnotherClass(final CoolJpaRepository coolJpaRepository) { this.coolJpaRepository = coolJpaRepository; } // ... } E chama findByCustomField em uma nova transação (por causa do @Transactional(propagation = Propagation.REQUIRES_NEW)).\n@Transactional(propagation = Propagation.REQUIRES_NEW) public void doAnotherThing() { // ... coolJpaRepository.findByCustomField(); // ... } Em essência, temos a mesma chamada de método do repositório (findByCustomField) em diferentes classes que, mesmo executando em transações separadas, são partes do mesmo fluxo.\nO problema é que toda vez que executamos este fluxo a consulta relacionada ao método findByCustomField será executada duas vezes, isso, é claro, presumindo que JPA/Hibernate não tenha algum tipo de otimização de performance implementada\u0026hellip;\nApresentando o Persistence Context # O Persistence Context atua como um cache entre a aplicação e o banco de dados, por essa razão sendo também conhecido como cache de primeiro nível (ou first-level cache).\nEste cache é criado a cada nova transação e garante que, dado um id único, uma e somente uma entidade relacionada estará dentro dele, garantindo mudanças consistentes na entidade e permitindo leituras repetidas (repeatable-reads) de dados já carregados.\nEle implementa uma estratégia de cache conhecida como write-behind, que basicamente significa que as mudanças na entidade são primeiro armazenadas no cache e, em um segundo momento, são \u0026ldquo;traduzidas\u0026rdquo; para operações de escrita que são enviadas em lote para o banco de dados.\nQuando usando a especificação JPA o EntityManager gerencia o Persistence Context, enquanto que ao usar diretamente o Hibernate utilizamos o objeto Session.\nIsso ajuda com nosso problema inicial? # Dado que leituras repetidas são uma funcionalidade do Persistence Context podemos usar a nosso favor em nosso problema inicial? Infelizmente, não por enquanto.\nComo explicado acima, temos um novo Persistence Context por transação e em nosso exemplo temos duas transações diferentes no mesmo fluxo graças ao @Transactional(propagation = Propagation.REQUIRES_NEW). Podemos alterar o codigo da seguinte forma:\n@Component public SomeClass { private final CoolJpaRepository coolJpaRepository; private final AnotherClass anotherClass; public SomeClass( final CoolJpaRepository coolJpaRepository, final AnotherClass anotherClass ) { this.coolJpaRepository = coolJpaRepository; this.anotherClass = anotherClass; } @Transactional public void doSomething() { // ... coolJpaRepository.findByCustomField(); anotherClass.doAnotherThing(); // ... } } @Component public AnotherClass { private final CoolJpaRepository coolJpaRepository; public AnotherClass(final CoolJpaRepository coolJpaRepository) { this.coolJpaRepository = coolJpaRepository; } @Transactional // REMOVIDO O PROPAGATION public void doAnotherThing() { // ... coolJpaRepository.findByCustomField(); // ... } } Com o código acima, tanto doSomething quanto doAnotherThing compartilharão o mesmo limite de transação e, consequentemente, o mesmo Persistence Context.\nCom essas mudanças, poderíamos esperar o seguinte comportamento: a primeira chamada de findByCustomField popularia o cache, e a segunda chamada acionaria uma leitura repetida em vez de acessar o banco de dados. Na realidade, ainda veremos duas consultas sendo feitas ao banco de dados ao executar o exemplo acima.\nNunca é tão simples\u0026hellip;\nQueries personalizadas e o cache de primeiro nível # Ao lidar com consultas JPQL/HQL personalizadas ou SQL nativo, o Hibernate não verifica o cache de primeiro nível para entidades relacionadas a essas consultas, indo direto para o cache de segundo nível (se habilitado) ou para o banco de dados.\nIsso explica por que no exemplo o método findByCustomField() executa duas consultas mesmo quando ambas as chamadas acontecem no mesmo limite transacional. Como por baixo dos panos o Spring Data JPA está gerando uma consulta JPQL a partir do método, ele não tem o benefício de obter a entidade do Persistence Context, mesmo se esta entidade já estiver carregada!\nA exceção é quando não tentamos obter entidades através de consultas, mas sim usando métodos como EntityManager.find ou Session.load. Ambos estes métodos interagem diretamente com o Persistence Context e obtêm a entidade através do id associado a ela. No Spring Data JPA podemos usar o método findById para obter o mesmo resultado, já que ele é implementado com base no EntityManager.find.\nPara tornar mais concreto, se tivéssemos algo como:\n@Component public SomeClass { private final CoolJpaRepository coolJpaRepository; private final AnotherClass anotherClass; public SomeClass( final CoolJpaRepository coolJpaRepository, final AnotherClass anotherClass ) { this.coolJpaRepository = coolJpaRepository; this.anotherClass = anotherClass; } @Transactional public void doSomething() { // ... coolJpaRepository.findById(); // ALTERADO PARA findById anotherClass.doAnotherThing(); // ... } } @Component public AnotherClass { private final CoolJpaRepository coolJpaRepository; public AnotherClass(final CoolJpaRepository coolJpaRepository) { this.coolJpaRepository = coolJpaRepository; } @Transactional public void doAnotherThing() { // ... coolJpaRepository.findById(); // ALTERADO PARA findById // ... } } O banco de dados receberia a primeira requisição do findById e salvaria a entidade no cache de primeiro nível. Na segunda chamada de findById, o Persistence Context seria verificado para entidades existentes e, como encontraria aquela que acabamos de carregar, nenhuma nova consulta seria feita ao banco de dados.\nConclusão # Como podemos ver, o Persistence Context pode nos ajudar a diminuir tempos de leitura no BD evitando a execução de consultas, mas apenas para casos de uso específicos onde verificamos diretamente o cache de primeiro nível.\nSe precisarmos de queries personalizadas as entidades carregadas são ignoradas. A única maneira de evitar uma requisição para o banco de dados nesse caso seria implementando um cache de segundo nível.\nEntão por que temos leituras repetidas? No próximo artigo pretendo discutir esta questão, como as entidades são realmente gerenciadas no cache de primeiro nível e os possíveis riscos de ter leituras repetidas.\nFiquem ligados \u0026#x1f440;.\n","date":"2025-02-16","externalUrl":null,"permalink":"/pt-br/posts/persistence-context-by-example/","section":"Posts","summary":"O que é o Persistence Context no JPA/Hibernate? Neste artigo exploramos o conceito com um exemplo e tentamos entender se é possível usá-lo para melhorar tempos de leitura.","title":"Exemplificando o Persistence Context","type":"posts"},{"content":"","date":"2025-01-13","externalUrl":null,"permalink":"/pt-br/tags/blog/","section":"Tags","summary":"","title":"Blog","type":"tags"},{"content":" Por que criar um blog? # Atualmente temos mais maneiras do que nunca de publicar nossas ideias, seja no Medium, Twitter/X, Instagram, etc. Essa democratização é ótima, mas pessoalmente eu prefiro ter controle sobre o que publico e como é publicado.\nComo este artigo explica muito bem, plataformas podem deixar de existir algum dia, assim como MySpace ou Orkut já foram as maiores redes sociais e agora são relíquias esquecidas.\nPor essa razão, decidi criar um blog simples para postar sobre coisas que gosto enquanto mantenho controle total sobre onde e como armazeno esses posts. Além disso, pensei que seria um projeto legal \u0026#x1f642;.\nPré-requisitos # Abaixo estão alguns pré-requisitos que guiaram minha escolha de stack para construir este blog, usando as razões exploradas acima como base:\nSuporte para arquivos de texto # Para armazenar os posts decidi usar arquivos de texto, já que serão os mais fáceis de portar para qualquer plataforma se necessário. Isso significa que qualquer stack que eu escolha deve facilmente converter esses arquivos para páginas HTML.\nConfiguração inicial simples # Não é fácil implementar do zero essa conversão de arquivos de texto para HTML. Além disso, não sou exatamente bom em web design \u0026#x1f440;.\nPor essas razões, a stack deve fornecer uma configuração inicial simples para começar a servir essas paginas web.\nFlexibilidade para expandir # Mesmo querendo uma configuração inicial simples, é importante que a stack ofereça maneiras de personalizar o blog conforme necessário para refletir mudanças que eu possa querer fazer no futuro.\nComponentes # Com os requisitos em mente, decidi usar os seguintes componentes para criar o blog:\nHugo # Hugo é um framework construído em Go voltado para geração de sites estáticos com foco em velocidade e flexibilidade.\nEle usa templates para organizar conteúdo e recursos em páginas HTML estáticas. O conteúdo pode ser escrito em vários formatos de texto simples, mas para este blog estou usando Markdown pela sua flexibilidade para trabalhar em múltiplas plataformas e acessibilidade para ser lido por si só.\nBlowfish # Blowfish é um tema Hugo que oferece templates pré-prontos muito flexíveis, tendo muitas configurações disponíveis e até permitindo personalizar completamente as páginas com layouts customizados.\nGithub # Eu uso o Github para armazenar tudo relacionado ao projeto, desde o conteúdo escrito em Markdown até o código necessário para o Hugo.\nCloudflare Pages # Cloudflare Pages é usado para servir as páginas estáticas.\nOutra opção que considerei foi o Github Pages, mas como já estava usando o Cloudflare para gerenciar o domínio do blog, fazia mais sentido continuar no mesmo ecossistema.\nUma vantagem de usar essa abordagem é que posso deixar o repositório como privado, o que é bom para fazer upload de posts que ainda estão em rascunho para o repositório.\nComo tudo funciona # Colocando a stack acima para funcionar, temos algo assim:\nEscrever o post em um arquivo markdown, usando Hugo para verificar em tempo real como fica convertido para uma página HTML; Abrir um PR e verificar o deployment de preview do Cloud Flare Pages; Se tudo funcionar como esperado, fazer merge das alterações para a branch principal, o que automaticamente acionará o deployment do blog; ","date":"2025-01-13","externalUrl":null,"permalink":"/pt-br/posts/building-a-blog/","section":"Posts","summary":"Nesse artigo eu exploro o como e o porque de ter criado este blog 🚀","title":"Criando um Blog","type":"posts"},{"content":"","date":"2025-01-13","externalUrl":null,"permalink":"/pt-br/tags/escrita/","section":"Tags","summary":"","title":"Escrita","type":"tags"},{"content":"","date":"2024-12-30","externalUrl":null,"permalink":"/tags/writing/","section":"Tags","summary":"","title":"Writing","type":"tags"},{"content":"","date":"2024-08-08","externalUrl":"https://medium.com/building-inventa/infrastructure-resource-creation-with-backstage-f36ae01633b5","permalink":"","section":"Posts","summary":"This article explores AWS resource creation (using SQS as an example) via the Backstage Software Templates plugin and how it helps application and platform developers deploy code faster, cleaner, and safer.","title":"Infrastructure Resource Creation with Backstage","type":"posts"},{"content":"","date":"2020-10-31","externalUrl":"https://medium.com/@luccalafonte/why-bugs-are-kinda-awesome-4ca2bb7ac048","permalink":"","section":"Posts","summary":"My goal with this article is to show the bright side of bug investigation in software development and how we can make the most of it.","title":"Why bugs are (kinda) awesome","type":"posts"},{"content":"","externalUrl":null,"permalink":"/pt-br/series/","section":"Series","summary":"","title":"Series","type":"series"}]