7.1 Crearea propriului convertor de tip

Uneori apar situații când doriți să stocați un tip de date destul de complex într-o coloană a unui tabel. Dacă Hibernate știe cum să-l convertească într-un șir (și înapoi), atunci totul este în regulă. Dacă nu, atunci va trebui să vă scrieți propriul convertor de date.

Să presupunem că cineva decide să stocheze anul de naștere al unui utilizator în baza de date ca YY.MM.DD, de exemplu: 98.12.15. De asemenea, trebuie să îl convertiți într-o dată obișnuită: 15/12/1998. Apoi trebuie să-ți scrii propriul convertor.

Pentru a face acest lucru, trebuie să implementați o interfață 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]);
    }
}

Și, desigur, acest convertor poate fi adăugat la orice câmp (cu condiția ca tipurile să se potrivească):


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

Convertoarele trebuie folosite foarte des dacă nu ați proiectat baza de date. Datele de acolo pot fi în „formate ciudate”. Datele pot fi stocate ca șiruri de caractere, booleenii ca CHAR-uri cu valori Y și N și altele asemenea.

7.2 Crearea propriului tip de date

Vă amintiți tabelul cu lista de tipuri cunoscute de Hibernate? Vorbesc despre tipuri care sunt specificate împreună cu adnotarea @Type. Puteți scrie propriul tip de date, care poate fi folosit în același mod ca și alte tipuri încorporate în Hibernate.

De exemplu, dorim să avem tipul LocalTime, care va fi stocat în baza de date nu ca TIME, ci ca VARCHAR. Și, de exemplu, avem acces la o astfel de bază de date și nu avem voie să schimbăm tipurile de date din coloanele acesteia. Apoi putem scrie propriul nostru tip Hibernate. Să-i spunem LocalTimeString.

Mai întâi avem nevoie de o clasă mică care să descrie noul nostru tip:

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

Este ceva de tip Enum care constă dintr-o singură valoare. Setul de astfel de enam unice este toate tipurile cunoscute de Hibernate.

De asemenea, avem nevoie de o clasă - un analog al convertorului, care va conține două metode - wrap()și unwrap()pentru conversia valorilor de tip LocalTime în String.

Iată cum va arăta fără a implementa metodele:

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

    }

}

Acum să scriem implementarea metodelor:

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

Și a doua metodă:

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

Gata. Puteți folosi această clasă pentru a stoca timpul ca șir:


@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 Înregistrarea tipului dvs

De asemenea, vă puteți înregistra tipul de date în timpul configurării Hibernate. Acest lucru este un pic non-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();

Mai întâi va trebui să obțineți MetadataSources, să obțineți MetadataBuilder de la acesta și să îl utilizați pentru a vă adăuga clasa. Este posibil prin hibernate.cfg.xml, dar și puțin greoi.

Dar după înregistrare, puteți scrie astfel:


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