Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Sem dúvida os enumeradores facilitam muito o desenvolvimento de aplicações e ajudam no entendimento da regra de negócio já que podemos usar valores autoexplicativos facilitando a vida de qualquer desenvolvedor.
Mas uma dúvida sempre surge quando queremos armazenar essa Enum no banco de dados. Nesse tutorial vamos ensinar várias formas de gravar informações desses enumeradores no banco de dados, usando uma implementação do Jpa.
Temos aqui um enumerador que mostra o tipo do cartão:
Vamos simular uma aplicação que guarda informações de cartões no bando de dados, e para isso vamos criar a classe que espelha a tabela do banco com os dados que queremos armazenar, e um Enum que identifica o tipo do cartão.
package com.artefatox.example;
public enum Type {
DEBIT, CREDIT;
}
Type.javaE aqui a classe que representa um cartão:
package com.artefatox.example;
import java.time.LocalDate;
import javax.persistence.Entity;
@Entity
public class Card {
@Id
private Long id;
private String holderName;
private String number;
private String cvv;
private LocalDate expiration;
private Type type;
//getters and setters
}
Card.javaNa grande maioria das aplicações Java, onde usamos bastante o conceito de enumeradores, e implementações do Jpa, como o Hibernate por exemplo, já nos fornece um método padrão, que é gravar o sequencial da Enum no banco.
Com o exemplo acima se tentarmos gravar usando alguma implementação do Hibernate, como Spring JPA por exemplo, o que seria gravado na coluna que representa o tipo (type), seria a sequência da enum, no caso do tipo ser definido como CREDIT, seria armazenado o valor 1.
Como fica os dados gravados na tabela:
Id | holder_name | number | cvv | expiration | type |
---|---|---|---|---|---|
1 | Kalli Cristina | 5140 6562 3810 2106 | 949 | 2028-04-14 | 1 |
Particularmente não gosto muito dessa abordagem, já que algum desenvolvedor pode vir a inserir um novo valor e não seguir a sequencia, alterando a ordem escrita no código, acrescentando o novo valor em um lugar diferente da ultima posição, dessa forma nossa aplicação pode considerar uma posição diferente para os valores que já possuímos e com prejudicando assim a integridade dos dados armazenados na aplicação.
Uma abordagem bastante utilizada, é gravar o próprio valor ao invés de gravar o valor sequencial, dessa forma fica mais legível inclusive no banco de dados, e independe da posição em que os valores estão escritos.
Entretanto essa abordagem as vezes não é possível porque você precisa definir a coluna do tipo varchar, porque irá armazenar a string em si, dependendo da modelagem do sistema isso pode não ser possível, em aplicações modernas ou que estejam nascendo isso pode não significar problema, mas para refatorar um código legado que já tem o banco de dados modelado pode complicar um pouco alterar tipo de dados de um banco já populado.
Mas caso seja possível utilizar o próprio valor, vamos mostrar em seguida como fazer.
Para essa abordagem vamos demonstrar utilizando o mesmo exemplo acima:
package com.artefatox.example;
import java.time.LocalDate;
import javax.persistence.Entity;
@Entity
public class Card {
@Id
private Long id;
private String holderName;
private String number;
private String cvv;
private LocalDate expiration;
@Enumerated(EnumType.STRING)
private Type type;
//getters and setters
}
Card.javaBasta anotarmos a propriedade que contém a enum com a anotação @Enumerated, passando como parâmetro o tipo EnumType.STRING, isso fará com que no momento da persistência o valor a ser registrado na tabela e o da própria chave do enumerador.
Veja no exemplo abaixo como ficaria na tabela:
Id | holder_name | number | cvv | expiration | type |
---|---|---|---|---|---|
1 | Kalli Cristina | 5140 6562 3810 2106 | 949 | 2028-04-14 | CREDIT |
Agora e quando temos enums mais complexas? enums que possuem atributos, e desejamos persistir o valor de um desses atributos no banco de dados? Vamos mostrar como fazer no próximo tópico.
No exemplo abaixo temos uma propriedade no nosso enumerador que armazena um código numérico, e queremos que o valor persistido seja justamente o desse atributo.
Uma observação a se fazer é que embora preferimos adotar um valor numérico isso não é obrigatório, poderíamos utilizar qualquer tipo de valor.
Vamos para o exemplo:
package com.artefatox.example;
public enum Type {
DEBIT(359),
CREDIT(422)
private Integer code;
private Type(Integer code) {
this.code = code;
}
public Integer getCode() {
return this.code;
}
}
Type.javaPara preparar o nosso enumerador para ser salvo, precisamos antes implementar um método que seja possível recuperar a chave do enum pelo valor do atributo code.
Para isso implementamos o método estático ofCode presentes no exemplo abaixo entre as linhas 18 e 21.
package com.artefatox.example;
public enum Type {
DEBIT(359),
CREDIT(422)
private Integer code;
private Type(Integer code) {
this.code = code;
}
public Integer getCode() {
return this.code;
}
public static Type ofCode(Integer code) {
Stream.of(Type.values()).filter(type -> type.getCode().equals(code))
.findFirst().orElseThrow(IllegalArgumentException::new);
}
}
Type.javaAgora precisamos configurar a conversão de dados no momento da persistência e da leitura da nossa classe.
Vamos criar uma classe TypeAttributeConverter (esse nome não é obrigatório) e precisamos implementar a interface AttributeConverter<T, E> passando no lugar do T o nosso Enum, e no E o tipo do atributo que iremos utilizar.
Essa interface nos força a sobrescrever dois métodos:
Veja o exemplo da nossa implementação:
package com.artefatox.example;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter(autoApply = true)
public class TypeAttributeConverter implements AttributeConverter<Type, Integer> {
@Override
public Integer convertToDatabaseColumn(Type attribute) {
if(attribute == null) {
return null;
}
return attribute.getCode();
}
@Override
public Type convertToEntityAttribute(Integer dbData) {
if(dbData == null) {
return null;
}
return Type.ofCode(dbData);
}
}
TypeAttributeConverter.javaApós ter sobrescrito os dois métodos precisamos ainda anotar a classe com @Converter(autoApply = true), isso faz com que a implementação fique disponível para ser utilizada toda vez que houver alguma iteração com o banco.
Após essa implementação, utilizando o mesmo fluxo para gravar um cartão no banco, nossa tabela fica com os dados da seguinte forma:
Id | holder_name | number | cvv | expiration | type |
---|---|---|---|---|---|
1 | Kalli Cristina | 5140 6562 3810 2106 | 949 | 2028-04-14 | 422 |
Existem outras abordagens como utilizar o @PrePersist, mas isso envolve ter mais um atributo na sua classe para armazenar o valor que gostaria de gravar no banco, e através de um método anotado com @PrePersist converter o valor.
Entretanto não gosto muito dessa abordagem, devido a ter propriedades a mais na classe que é referência da nossa tabela, além de contribuir para a desorganização do código além de dificultar o entendimento.
Para construir aplicações que sejam escaláveis e possíveis de manutenção, é importante construir nossas classes de forma em que o desenvolvedor possa se familiarizar facilmente com a regra de negócio e consiga ler e identificar as funcionalidades presentes no código de maneira simples.
As enums vieram para isso deixar fácil o entendimento dos desenvolvedores além de auxiliar no entendimento das regras de negócio já que os valores ficam explicito no código.