7.1 Création de votre propre convertisseur de type

Parfois, des situations surviennent lorsque vous souhaitez stocker un type de données assez complexe dans une colonne d'une table. Si Hibernate sait comment le convertir en chaîne (et inversement), alors tout va bien. Sinon, vous devrez écrire votre propre convertisseur de données.

Supposons que quelqu'un décide de stocker l'année de naissance d'un utilisateur dans la base de données sous la forme YY.MM.DD, par exemple : 98.12.15. Vous devez également le convertir en une date normale : 15/12/1998. Ensuite, vous devez écrire votre propre convertisseur.

Pour ce faire, vous devez implémenter une 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]);
    }
}

Et, bien sûr, ce convertisseur peut être ajouté à n'importe quel champ (à condition que les types correspondent) :


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

Les convertisseurs doivent très souvent être utilisés si vous n'avez pas conçu la base de données. Les données peuvent être dans des "formats étranges". Les dates peuvent être stockées sous forme de chaînes, les booléens sous forme de CHAR avec les valeurs Y et N, etc.

7.2 Créer notre propre type de données

Vous souvenez-vous du tableau avec la liste des types connus d'Hibernate ? Je parle des types qui sont spécifiés avec l'annotation @Type. Vous pouvez écrire votre propre type de données, qui peut être utilisé de la même manière que les autres types intégrés dans Hibernate.

Par exemple, nous voulons avoir le type LocalTime, qui sera stocké dans la base de données non pas en tant que TIME, mais en tant que VARCHAR. Et, par exemple, nous avons accès à une telle base de données et nous ne sommes pas autorisés à modifier les types de données dans ses colonnes. Ensuite, nous pouvons écrire notre propre type Hibernate. Appelons-le LocalTimeString.

Nous avons d'abord besoin d'une petite classe qui décrira notre nouveau 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";
    }
}

C'est quelque chose de type Enum qui se compose d'une valeur. L'ensemble de ces enams uniques est constitué de tous les types connus d'Hibernate.

Nous avons également besoin d'une classe - un analogue du convertisseur, qui contiendra deux méthodes - wrap()et unwrap()pour convertir les valeurs du type LocalTime en String.

Voici à quoi cela ressemblera sans implémenter les méthodes:

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

    }

}

Écrivons maintenant l'implémentation des méthodes :

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

Et la seconde méthode :

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

Prêt. Vous pouvez utiliser cette classe pour stocker l'heure sous forme de chaîne :


@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 Enregistrement de votre type

Vous pouvez également enregistrer votre type de données lors de la configuration d'Hibernate. C'est un peu 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();

Vous devrez d'abord obtenir MetadataSources, obtenir MetadataBuilder à partir de celui-ci et l'utiliser pour ajouter votre classe. C'est possible grâce à hibernate.cfg.xml, mais aussi un peu lourd.

Mais après l'inscription, vous pouvez écrire comme ceci :


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