(tradução) Lombok e JPA: O que pode dar errado?
Published: 2023-10-26 16:08:07

_Esta é uma tradução do artigo [Lombok and JPA: What go wrong?](https://jpa-buddy.com/blog/lombok-and-jpa-what-may-go-wrong/) escrito por Andrey Oganesyan._

Lombok é uma ótima ferramenta que traz uma maior concisão e um menor ruído ao seu código. Porém, há algumas coisas a se considerar quando se usa junto com JPA. Neste artigo, iremos investigar como o mau uso do Lombok pode trazer perdas de perfomance para aplicações que usam JPA (ou até mesmo quebrá-las), e como evitar isso, mantendo os benefícios de usar Lombok.

Nós desenvolvemos JPA Buddy - um plugin para IntelliJ IDEA [notas do tradutor: um ótimo plugin, inclusive, que recomendo] pensando em ter um uso mais simplificado do JPA. Antes de escrever qualquer código desse plugin, buscamos entender, lendo vários projetos do Github, como as pessoas trabalham com JPA. Percebemos que um monte deles usam Lombok para definir suas entidades.

É absolutamente OK usar Lombok nos seus projetos que incluem JPA, porém, há alguns "caveats" a serem observados. Analisando os projetos, vemos pessoas caírem nas mesmas armadilhas sempre e sempre. Por este motivo, incluímos várias regras de inspeção de código Lombok no JPA Buddy. Este artigo mostra os principais problemas que você pode enfrentar usando Lombok com entidades JPA.

### HashSets e HashMaps quebrados

Muitas vezes, classes que definem entidades são anotadas com `@EqualsAndHashcode` ou `@Data`. A documentação para `@EqualsAndHashCode` define que:

> [Tradução direta] Por padrão, irá usar todas as propriedades que não sejam estáticas e nem transientes, mas você pode modificar quais propriedades são usadas (e mesmo especificar que o retorno de vários métodos devem ser usados), simplesmente marcando os membros com `@EqualsAndHashCode.Include` ou `@EqualsAndHashCode.Exclude`

A implementação de `equals` ou `hashCode` para entidades JPA é um assunto sensível. Naturalmente, entidades são mutáveis. Mesmo o id de uma entidade é frequentemente gerado por um banco de dados, fazendo com que a mesma seja mudada depois que a entidade é persistida pela primeira vez. Isto significa que não podemos contar com nenhuma propriedade para calcular um hashCode confiável.

Por exemplo, vamos criar uma entidade de teste: 

```java
@Entity
@EqualsAndHashCode
public class TestEntity {
  
  @Id 
  @Generatedvalue(strategy = GenerationType.IDENTITY) 
  @Column(nullable = false) 
  private Long id;

}
```

E executar o código abaixo:

```java
TestEntity testEntity = new TestEntity();
Set<TestEntity> set = new HashSet<>();

set.add(testEntity);
testEntityRepository.save(testEntity);

Assert.isTrue(set.contains(testEntity), "Entity not found in the set");
```

A asserção na última linha falha, mesmo que a entidade foi adicionada ao Set nas linhas anteriores. "Delomboking" [nota do tradutor: é possível retirar o lombok sem trazer alterações no funcionamento do código, através da ferramenta [Delombok](https://projectlombok.org/features/delombok)] a anotação `@EqualsAndHashCode` nos dá o seguinte código:

```java
public int hashCode() {
   final int PRIME = 59;
   int result = 1;
   final Object $id = this.getId();
   result = result * PRIME + ($id == null ? 43 : $id.hashCode());
   return result;
}
```

Assim que o id é gerado (na primeira chamada no método `save`), o hashCode muda. Então o HashSet procura pela entidade em uma "pool" diferente, porém não a encontra. Isso não seria um problema se o id fosse configurado durante a criação do objeto da entidade (isto é, se o UUID ou o id autoincrementável fosse definido pela aplicação), mas ids autogerados pelo banco de dados são mais comuns.

### Carregar acidentalmente atributos "Lazy"

Como mencionado acima, `@EqualsAndHashCode` inclui todos as propriedades do objeto por padrão. O mesmo pode ser dito para o `@ToString`: 

> [tradução direta] Toda e qualquer definição de classe pode ser anotada com `@ToString` para que o Lombok gere uma implementação do método `toString()`. Por padrão, ele irá retornar o nome da sua classe, junto com cada propriedade, em ordem, separado por vírgulas.

Estes métodos evocam `equals`, `hashCode`, `toString` em _todas_ as propriedades de um objeto. Isso pode levar a efeitos colaterais indesejados para entidades JPA: acidentalmente carregar atributos que são "Lazy".

Por exemplo, invocar `hashCode` em uma propriedade `@OneToMany` que é lazy pode carregar todas as entidades que ele contém. Isto pode facilmente prejudicar a performance da aplicação. Isso pode levar também à exceção  `LazyInitializationException` se isto acontece fora de uma transação.

Nós acreditamos que `@EqualsAndHashCode` e `@Data` não devem ser usadas para entidades por completo. Neste caso, JPA Buddy emite um aviso para os usuários:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q7umezzm7m8vgn0ck8dm.png)

`@ToString` pode continuar a ser usado, mas todas propriedades que são "lazy" precisam ser excluídas. Basta colocar `@ToString.Exclude` nas propriedades que precisam excluídas, ou então usar `@ToString(onlyExplicitlyIncluded=true)` na classe e anotar as propriedades que não são "lazy" com `@ToString.Include`. JPA Buddy tem um quick fix para isso:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c6acj23zil40ouvorw2w.png)

### Falta de um construtor sem argumentos

De acordo com a especificação do JPA, é obrigatório que todas as entidades tenham um construtor sem argumentos que seja `public` ou `protected`. Obviamente, quando `@AllArgsConstructor` é utilizado, o compilador não gera o construtor padrão. O mesmo vale também para `@Builder`. 

> [tradução direta] Anotar uma classe com `@Builder` temo mesmo efeito que anotar ela com  `@AllArgsConstructor(access=AccessLevel.PACKAGE)`, e, juntamente com isso, anotar o construtor gerado com `@Builder` 

Então tenha certeza de sempre utilizar `@NoArgsConstructor` ou escreva um construtor explicitamente:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4o3am9oaviftyjtr48qg.png)

### Conclusão

Lombok faz seu código parecer melhor, mas como qualquer ferramenta "mágica", é importante entender como exatamente ela funciona e quando usar ela. Você pode contar com ferramentas de desenvolvimento para antecipar potenciais problemas para você. Ou então, você pode acidentalmente prejudicar a performance da aplicação, ou mesmo quebrar tudo.

Ao trabalhar com JPA e Lombok, lembre-se sempre dessas regras:

* Evite usar `@EqualsAndHashCode` e `@Data` com entidades JPA;
* Sempre exclua atributos "lazy" quando usar `@ToString`; 
* Nunca se esqueça de adicionar `@NoArgsConstructor` a entidades com `@Builder` ou `@AllArgsConstructor`. 

[Última frase do texto excluída deliberadamente].