7.1 Criando seu próprio conversor de tipo

Às vezes, surgem situações em que você deseja armazenar um tipo de dados bastante complexo em uma coluna de uma tabela. Se o Hibernate souber como convertê-lo em uma string (e vice-versa), está tudo bem. Caso contrário, você terá que escrever seu próprio conversor de dados.

Digamos que alguém decida armazenar o ano de nascimento de um usuário no banco de dados como YY.MM.DD, por exemplo: 98.12.15. Você também precisa convertê-lo em uma data regular: 15/12/1998. Então você tem que escrever seu próprio conversor.

Para fazer isso, você precisa implementar uma interface AttributeConverter<EntityType, DbType>.


@Converter(autoApply = true)
public class DateConverter implements AttributeConverter<java.time.LocalDate, String> {
 
    public String convertToDatabaseColumn(java.time.LocalDate date) {
    	return date.format("YY.MM.DD");
    }
 
    public java.time.LocalDate convertToEntityAttribute(String dbData) {
    	String[] data = dbData.split(".");
    	return LocalDate.of(data[2], data[1], "19"+data[0]);
    }
}

E, claro, este conversor pode ser adicionado a qualquer campo (desde que os tipos correspondam):


@Entity
@Table(name="user")
class User {
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_date")
   @Convert(converter = DateConverter.class)
   public java.time.LocalDate date;
}

Frequentemente, os conversores precisam ser usados ​​se você não projetou o banco de dados. Os dados podem estar em "formatos estranhos". As datas podem ser armazenadas como strings, booleanos como CHARs com valores Y e N e assim por diante.

7.2 Criando nosso próprio tipo de dados

Lembra da tabela com a lista de tipos conhecidos pelo Hibernate? Estou falando de tipos que são especificados junto com a anotação @Type. Você pode escrever seu próprio tipo de dados, que pode ser usado da mesma forma que outros tipos embutidos no Hibernate.

Por exemplo, queremos ter o tipo LocalTime, que será armazenado no banco de dados não como TIME, mas como VARCHAR. E, por exemplo, temos acesso a esse banco de dados e não temos permissão para alterar os tipos de dados em suas colunas. Então podemos escrever nosso próprio tipo Hibernate. Vamos chamá-lo de LocalTimeString.

Primeiro, precisamos de uma pequena classe que descreva nosso novo tipo:

public class LocalTimeStringType extends AbstractSingleColumnStandardBasicType<<LocalTime> {

    public static final LocalTimeStringType  INSTANCE = new LocalTimeStringType ();

    public LocalTimeStringType () {
    	super(VarcharTypeDescriptor.INSTANCE, LocalTimeStringJavaDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
    	return "LocalTimeString";
    }
}

É algo do tipo Enum que consiste em um valor. O conjunto de tais enams únicos são todos os tipos conhecidos pelo Hibernate.

Também precisamos de uma classe - um análogo do conversor, que conterá dois métodos - wrap()e unwrap()para converter valores do tipo LocalTime em String.

É assim que ficará sem implementar os métodos:

public class LocalTimeStringJavaDescriptor extends AbstractTypeDescriptor<LocalTime> {

    public static final LocalTimeStringJavaDescriptor INSTANCE =  new  LocalTimeStringJavaDescriptor();

    public LocalTimeStringJavaDescriptor() {
    	super(LocalTime.class, ImmutableMutabilityPlan.INSTANCE);
    }

    public <X> X unwrap(LocalTime value, Class<X> type, WrapperOptions options) {

    }

    public <X> LocalTime wrap(X value, WrapperOptions options) {

    }

}

Agora vamos escrever a implementação dos métodos:

public <X> X unwrap(LocalTime value, Class<X> type, WrapperOptions options) {

    if (value == null)
    	return null;

    if (String.class.isAssignableFrom(type))
    	return (X) LocalTimeType.FORMATTER.format(value);

    throw unknownUnwrap(type);
}

E o segundo método:

@Override
public <X> LocalTime wrap(X value, WrapperOptions options) {
    if (value == null)
    	return null;

    if(String.class.isInstance(value))
    	return LocalTime.from(LocalTimeType.FORMATTER.parse((CharSequence) value));

    throw unknownWrap(value.getClass());
}

Preparar. Você pode usar esta classe para armazenar o tempo como uma string:


@Entity
@Table(name="user")
class User
{
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_time")
   @Type(type = "com.codegym.hibernate.customtypes.LocalTimeStringType")  
   public java.time.LocalTime time;
}

7.3 Registrando seu tipo

Você também pode registrar seu tipo de dados durante a configuração do Hibernate. Isso é um pouco não trivial.


ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder()
    .applySettings(getProperties()).build();
                                                                                                                                                              	                                        	 
    MetadataSources metadataSources = new MetadataSources(serviceRegistry);
    Metadata metadata = metadataSources
  	.addAnnotatedClass(User.class)
  	.getMetadataBuilder()
  	.applyBasicType(LocalTimeStringType.INSTANCE)
  	.build();
                                                                                                                                                              	                                        	 
    SessionFactory factory =  metadata.buildSessionFactory();

Você primeiro precisará obter MetadataSources, obter MetadataBuilder dele e usá-lo para adicionar sua classe. É possível através de hibernate.cfg.xml, mas também um pouco complicado.

Mas após o registro, você pode escrever assim:


@Entity
@Table(name="user")
class User
{
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_time")
   @Type(type = "LocalTimeString")  
   public java.time.LocalTime time;
}