7.1 Erstellen Sie Ihren eigenen Typkonverter

Manchmal treten Situationen auf, in denen Sie einen ziemlich komplexen Datentyp in einer Spalte einer Tabelle speichern möchten. Wenn Hibernate weiß, wie man es in einen String (und zurück) umwandelt, ist alles in Ordnung. Wenn nicht, müssen Sie Ihren eigenen Datenkonverter schreiben.

Nehmen wir an, jemand beschließt, das Geburtsjahr eines Benutzers in der Datenbank zu speichern YY.MM.DD, zum Beispiel als: 98.12.15. Sie müssen es auch in ein reguläres Datum umwandeln: 15/12/1998. Dann müssen Sie Ihren eigenen Konverter schreiben.

Dazu müssen Sie eine Schnittstelle implementieren 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]);
    }
}

Und natürlich kann dieser Konverter zu jedem Feld hinzugefügt werden (sofern die Typen übereinstimmen):


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

Konverter müssen sehr oft verwendet werden, wenn Sie die Datenbank nicht selbst entworfen haben. Die Daten dort können in „seltsamen Formaten“ vorliegen. Datumsangaben können als Zeichenfolgen, Boolesche Werte als CHARs mit Y- und N-Werten usw. gespeichert werden.

7.2 Erstellen unseres eigenen Datentyps

Erinnern Sie sich an die Tabelle mit der Liste der Typen, die Hibernate bekannt sind? Ich spreche von Typen, die zusammen mit der Annotation angegeben werden @Type. Sie können Ihren eigenen Datentyp schreiben, der auf die gleiche Weise wie andere integrierte Typen in Hibernate verwendet werden kann.

Wir möchten beispielsweise den Typ LocalTime haben, der in der Datenbank nicht als TIME, sondern als VARCHAR gespeichert wird. Und wir haben beispielsweise Zugriff auf eine solche Datenbank und dürfen die Datentypen in ihren Spalten nicht ändern. Dann können wir unseren eigenen Hibernate-Typ schreiben. Nennen wir es LocalTimeString.

Zuerst brauchen wir eine kleine Klasse, die unseren neuen Typ beschreibt:

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

Es ist etwas vom Typ Enum, das aus einem Wert besteht. Die Menge solcher Einzelnamen umfasst alle Typen, die Hibernate bekannt sind.

Wir benötigen außerdem eine Klasse – ein Analogon des Konverters, der zwei Methoden enthält – wrap()und unwrap()zum Konvertieren von Werten vom Typ LocalTime in String.

So sieht es ohne Implementierung der Methoden aus:

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

    }

}

Schreiben wir nun die Implementierung der Methoden:

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

Und die zweite Methode:

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

Bereit. Mit dieser Klasse können Sie die Zeit als String speichern:


@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 Registrieren Ihres Typs

Sie können Ihren Datentyp auch während der Hibernate-Konfiguration registrieren. Das ist etwas nicht 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();

Sie müssen zunächst MetadataSources abrufen, daraus MetadataBuilder abrufen und damit Ihre Klasse hinzufügen. Es ist möglich hibernate.cfg.xml, aber auch etwas umständlich.

Aber nach der Registrierung können Sie so schreiben:


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