Esta é uma tradução do artigo Lombok and JPA: What 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:
@Entity
@EqualsAndHashCode
public class TestEntity {
@Id
@Generatedvalue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
private Long id;
}
E executar o código abaixo:
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] a anotação @EqualsAndHashCode
nos dá o seguinte código:
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étodotoString()
. 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:
@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:
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:
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].
Top comments (0)