7.1 Creazione del proprio convertitore di tipi

A volte si verificano situazioni in cui si desidera archiviare un tipo di dati abbastanza complesso in una colonna di una tabella. Se Hibernate sa come convertirlo in una stringa (e viceversa), allora va tutto bene. In caso contrario, dovrai scrivere il tuo convertitore di dati.

Supponiamo che qualcuno decida di memorizzare l'anno di nascita di un utente nel database come YY.MM.DD, ad esempio: 98.12.15. Devi anche convertirlo in una data normale: 15/12/1998. Quindi devi scrivere il tuo convertitore.

Per fare ciò, è necessario implementare un'interfaccia 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, naturalmente, questo convertitore può essere aggiunto a qualsiasi campo (a condizione che i tipi corrispondano):


@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;
}

I convertitori molto spesso devono essere usati se non hai progettato il database. I dati possono essere in "strani formati". Le date possono essere memorizzate come stringhe, booleani come CHAR con valori Y e N e simili.

7.2 Creazione del nostro tipo di dati

Ricordi la tabella con l'elenco dei tipi noti a Hibernate? Sto parlando di tipi specificati insieme all'annotazione @Type. Puoi scrivere il tuo tipo di dati, che può essere utilizzato allo stesso modo degli altri tipi incorporati in Hibernate.

Ad esempio, vogliamo avere il tipo LocalTime, che verrà memorizzato nel database non come TIME, ma come VARCHAR. E, ad esempio, abbiamo accesso a tale database e non siamo autorizzati a modificare i tipi di dati nelle sue colonne. Quindi possiamo scrivere il nostro tipo Hibernate. Chiamiamolo LocalTimeString.

Per prima cosa abbiamo bisogno di una piccola classe che descriva il nostro nuovo 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";
    }
}

È qualcosa di tipo Enum che consiste in un valore. L'insieme di tali singoli enam è di tutti i tipi noti a Hibernate.

Abbiamo anche bisogno di una classe - un analogo del convertitore, che conterrà due metodi - wrap()e unwrap()per convertire i valori del tipo LocalTime in String.

Ecco come apparirà senza implementare i metodi:

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) {

    }

}

Ora scriviamo l'implementazione dei metodi:

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 il secondo metodo:

@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());
}

Pronto. Puoi usare questa classe per memorizzare il tempo come una stringa:


@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 Registrare il proprio tipo

Puoi anche registrare il tuo tipo di dati durante la configurazione di Hibernate. Questo è un po 'non banale.


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();

Dovrai prima ottenere MetadataSources, ottenere MetadataBuilder da esso e usarlo per aggiungere la tua classe. È possibile attraverso hibernate.cfg.xml, ma anche un po 'ingombrante.

Ma dopo la registrazione, puoi scrivere così:


@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;
}