Estou procurando as diferentes maneiras de mapear uma enumeração usando JPA. Quero especialmente definir o valor inteiro de cada entrada de enum e salvar apenas o valor inteiro.
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
public enum Right {
READ(100), WRITE(200), EDITOR (300);
private int value;
Right(int value) { this.value = value; }
public int getValue() { return value; }
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the enum to map :
private Right right;
}
Uma solução simples é usar a anotação enumerada com EnumType.ORDINAL:
@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;
Mas, neste caso, o JPA mapeia o índice de enumeração (0,1,2) e não o valor que eu quero (100,200,300).
As duas soluções que encontrei não parecem simples ...
Primeira solução
Uma solução, proposta aqui , usa @PrePersist e @PostLoad para converter a enumeração em outro campo e marcar a enumeração como transitória:
@Basic
private int intValueForAnEnum;
@PrePersist
void populateDBFields() {
intValueForAnEnum = right.getValue();
}
@PostLoad
void populateTransientFields() {
right = Right.valueOf(intValueForAnEnum);
}
Segunda solução
A segunda solução proposta aqui propôs um objeto de conversão genérico, mas ainda parece pesado e orientado a hibernação (o @Type não parece existir no Java EE):
@Type(
type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
parameters = {
@Parameter(
name = "enumClass",
value = "Authority$Right"),
@Parameter(
name = "identifierMethod",
value = "toInt"),
@Parameter(
name = "valueOfMethod",
value = "fromInt")
}
)
Existem outras soluções?
Tenho várias idéias em mente, mas não sei se elas existem no JPA:
- use os métodos setter e getter do membro certo da classe Authority ao carregar e salvar o objeto Authority
- uma idéia equivalente seria dizer à JPA quais são os métodos de enum Right para converter enum em int e int enum
- Como estou usando o Spring, há alguma maneira de dizer ao JPA para usar um conversor específico (RightEditor)?
Respostas:
Para versões anteriores ao JPA 2.1, o JPA fornece apenas duas maneiras de lidar com enumerações, por eles
name
ou por elesordinal
. E o JPA padrão não suporta tipos personalizados. Assim:UserType
, EclipseLinkConverter
, etc). (a segunda solução). ~ ou ~int
valor ~ ou ~Ilustrarei a opção mais recente (esta é uma implementação básica, ajuste-a conforme necessário):
fonte
Agora isso é possível com o JPA 2.1:
Detalhes adicionais:
fonte
@Converter
, masenum
devemos ser manuseados com mais elegância!AttributeConverter
BUT, mas cita algum código que não faz nada do tipo e não responde ao OP.AttributeConverter
@Convert(converter = Converter.class) @Enumerated(EnumType.STRING)
No JPA 2.1, você pode usar o AttributeConverter .
Crie uma classe enumerada da seguinte forma:
E crie um conversor como este:
Na entidade, você só precisa de:
Você
@Converter(autoApply = true)
pode variar de acordo com o recipiente, mas testado para funcionar no Wildfly 8.1.0. Se não funcionar, você pode adicionar@Convert(converter = NodeTypeConverter.class)
a coluna da classe de entidade.fonte
A melhor abordagem seria mapear um ID exclusivo para cada tipo de enumeração, evitando as armadilhas de ORDINAL e STRING. Veja este post que descreve 5 maneiras de mapear uma enumeração.
Retirado do link acima:
1 e 2. Usando @Enumerated
Atualmente, existem duas maneiras de mapear enumerações em suas entidades JPA usando a anotação @Enumerated. Infelizmente, EnumType.STRING e EnumType.ORDINAL têm suas limitações.
Se você usar EnumType.String, renomear um de seus tipos de enum fará com que seu valor de enum fique fora de sincronia com os valores salvos no banco de dados. Se você usar EnumType.ORDINAL, excluir ou reordenar os tipos dentro de sua enum fará com que os valores salvos no banco de dados sejam mapeados para os tipos de enumeração incorretos.
Ambas as opções são frágeis. Se a enumeração for modificada sem executar uma migração de banco de dados, você poderá prejudicar a integridade de seus dados.
3. Retornos de chamada do ciclo de vida
Uma solução possível seria usar as anotações de retorno de chamada do ciclo de vida JPA, @PrePersist e @PostLoad. Isso é muito feio, pois agora você terá duas variáveis em sua entidade. Um mapeando o valor armazenado no banco de dados e o outro, a enumeração real.
4. Mapeando o ID Exclusivo para cada Tipo de Enumeração
A solução preferida é mapear sua enumeração para um valor fixo, ou ID, definido na enumeração. O mapeamento para um valor fixo predefinido torna seu código mais robusto. Qualquer modificação na ordem dos tipos de enumerações ou a refatoração dos nomes não causará efeitos adversos.
5. Usando o Java EE7 @Convert
Se você estiver usando o JPA 2.1, terá a opção de usar a nova anotação @Convert. Isso requer a criação de uma classe de conversor, anotada com @Converter, dentro da qual você definiria quais valores são salvos no banco de dados para cada tipo de enumeração. Dentro da sua entidade, você anotaria sua enumeração com @Convert.
Minha preferência: (Número 4)
A razão pela qual prefiro definir meus IDs dentro da enumeração, em vez de usar um conversor, é um bom encapsulamento. Somente o tipo de enumeração deve saber seu ID e somente a entidade deve saber sobre como mapeia a enumeração para o banco de dados.
Veja a postagem original para o exemplo de código.
fonte
O problema é que eu acho que o JPA nunca foi aceito com a idéia de que já poderíamos ter um esquema complexo preexistente.
Eu acho que existem duas principais falhas resultantes disso, específicas para o Enum:
Ajude minha causa e vote no JPA_SPEC-47
Isso não seria mais elegante do que usar um @Converter para resolver o problema?
fonte
Possivelmente código relacionado próximo de Pascal
fonte
Eu faria o seguinte:
Declare separadamente a enum, em seu próprio arquivo:
Declarar uma nova entidade JPA denominada Right
Você também precisará de um conversor para receber esses valores (apenas o JPA 2.1 e não há problema que eu não discuta aqui com essas enums a serem persistidas diretamente usando o conversor, portanto, será apenas uma via de mão única)
A entidade Autoridade:
Desta forma, você não pode definir diretamente para o campo enum. No entanto, você pode definir o campo Direito na autoridade usando
E se você precisar comparar, você pode usar:
O que é bem legal, eu acho. Não está totalmente correto, já que o conversor não se destina a ser usado com enum. Na verdade, a documentação diz para nunca usá-lo para esse fim; você deve usar a anotação @Enumerated. O problema é que existem apenas dois tipos de enumeração: ORDINAL ou STRING, mas o ORDINAL é complicado e não é seguro.
No entanto, se isso não lhe agradar, você pode fazer algo um pouco mais hacky e mais simples (ou não).
Vamos ver.
O RightEnum:
e a entidade Autoridade
Nesta segunda idéia, não é uma situação perfeita, pois invadimos o atributo ordinal, mas é uma codificação muito menor.
Penso que a especificação JPA deve incluir o EnumType.ID onde o campo de valor de enum deve ser anotado com algum tipo de anotação @EnumId.
fonte
Minha própria solução para resolver esse tipo de mapeamento Enum JPA é a seguinte.
Etapa 1 - Escreva a seguinte interface que usaremos para todas as enumerações que queremos mapear para uma coluna db:
Etapa 2 - Implemente um conversor JPA genérico personalizado da seguinte maneira:
Essa classe converterá o valor de enum
E
em um campo de banco de dados do tipoT
(por exemploString
) usandogetDbVal()
on enumE
e vice-versa.Etapa 3 - Deixe o enum original implementar a interface que definimos na etapa 1:
Etapa 4 - Estenda o conversor da etapa 2 para o
Right
enum da etapa 3:Etapa 5 - A etapa final é anotar o campo na entidade da seguinte maneira:
Conclusão
IMHO, esta é a solução mais limpa e elegante se você tiver muitas enumerações para mapear e desejar usar um campo específico da enumeração em si como valor de mapeamento.
Para todas as outras enumerações no seu projeto que precisam de lógica de mapeamento semelhante, você só precisa repetir as etapas de 3 a 5, ou seja:
IDbValue
no seu enum;EnumDbValueConverter
código com apenas três linhas de código (você também pode fazer isso na sua entidade para evitar a criação de uma classe separada);@Convert
fromjavax.persistence
package.Espero que isto ajude.
fonte
primeiro caso (
EnumType.ORDINAL
)segundo caso (
EnumType.STRING
)fonte