7.1 Oprettelse af din egen typekonverter

Nogle gange opstår der situationer, hvor du vil gemme en ret kompleks datatype i én kolonne i en tabel. Hvis Hibernate ved, hvordan man konverterer det til en streng (og tilbage), så er alt fint. Hvis ikke, så bliver du nødt til at skrive din egen datakonverter.

Lad os sige, at nogen beslutter sig for at gemme en brugers fødselsår i databasen som YY.MM.DDf.eks.: 98.12.15. Du skal også konvertere den til en almindelig dato: 15/12/1998. Så skal du skrive din egen konverter.

For at gøre dette skal du implementere en grænseflade 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]);
    }
}

Og selvfølgelig kan denne konverter tilføjes til ethvert felt (forudsat at typerne matcher):


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

Konvertere skal meget ofte bruges, hvis du ikke har designet databasen. Dataene der kan være i "mærkelige formater". Datoer kan gemmes som strenge, booleaner som CHARs med Y- og N-værdier og lignende.

7.2 Oprettelse af vores egen datatype

Kan du huske tabellen med listen over typer, der er kendt for Hibernate? Jeg taler om typer, der er angivet sammen med annoteringen @Type. Du kan skrive din egen datatype, som kan bruges på samme måde som andre indbyggede typer i Hibernate.

For eksempel ønsker vi at have typen LocalTime, som vil blive gemt i databasen ikke som TIME, men som VARCHAR. Og vi har for eksempel adgang til sådan en database, og vi må ikke ændre datatyperne i dens kolonner. Så kan vi skrive vores egen Hibernate-type. Lad os kalde det LocalTimeString.

Først skal vi have en lille klasse, der vil beskrive vores nye type:

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

Det er noget af typen Enum som består af én værdi. Sættet af sådanne enkelte enams er alle de typer, der er kendt for Hibernate.

Vi har også brug for en klasse - en analog af konverteren, som vil indeholde to metoder - wrap()og unwrap()til at konvertere værdier af typen LocalTime til String.

Sådan vil det se ud uden at implementere metoderne:

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

    }

}

Lad os nu skrive implementeringen af ​​metoderne:

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

Og den anden metode:

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

Parat. Du kan bruge denne klasse til at gemme tid som en streng:


@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 Registrering af din type

Du kan også registrere din datatype under Hibernate-konfiguration. Dette er lidt ikke-trivielt.


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

Du skal først hente MetadataSources, hente MetadataBuilder fra den og bruge den til at tilføje din klasse. Det er muligt igennem hibernate.cfg.xml, men også lidt besværligt.

Men efter tilmelding kan du skrive sådan her:


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