[Hibernate] – Persistance de types personnalisés

Dans ce billet, nous allons voir comment Hibernate nous permet simplement, et de façon transparente, de persister des types Java qui ne sont pas gérés nativement par la base de données. Nous verrons aussi l'utilisation avec JPA.

Dans une application, la persistance des données et leur gestion par la couche DAO sont transparentes tant que les types manipulés sont gérés nativement par Hibernate (les nombres en général, chaînes de caractères, dates, données binaires). S'il y a besoin d'utiliser d'autres types de données, il y a généralement des "mappers" mis en place pour faire la conversion entre le type voulu et un type géré par Hibernate et le SGBD.

Hibernate met à disposition l'interface UserType, permettant de spécifier un mapper qu'il utilisera pour persister et transformer les types voulus.

Nous allons voir un exemple avec une classe possédant un attribut de type Color.

Commençons par décrire notre classe, sans information Hibernate pour l'instant :

/** Classe de test symbolisant un groupe.
 * @author Netapsys
 */
public class Groupe {
    /** Identifiant du groupe. */
    private Integer id;

    /** Couleur du groupe. */
    private Color couleur;

    // Getters et setters...
}

ColorUserType.java

Sans la classe fournie par Hibernate, il aurait fallu gérer la couleur avec des Integer, et faire le mapping vers la classe Color à divers endroits du code.
Dans le contrat imposé par l'interface UserType, seules quatre méthodes vont nous intéresser :

  • int sqlTypes();
    • Indique sous quel type SQL sera stocké notre type personnalisé. Le type retourné doit correspondre au type de colonne de la base de données.
/** {@inheritDoc} */
@Override
public int[] sqlTypes() {
    return new int[] {Types.INTEGER }; // Notre couleur sera stockée sous forme d'entier.
}

  • Class returnedClass();
    • Indique la classe de notre type personnalisé.
/** {@inheritDoc} */
@Override
public Class<Color> returnedClass() {
    return Color.class;
}

  • void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException;
    • En charge de convertir notre type personnalisé en type de donnée SQL, indiqué dans la méthode sqlTypes().
/** {@inheritDoc} */
@Override
public void nullSafeSet(final PreparedStatement st, final Object value, final int index)
  throws HibernateException, SQLException {
    if (null == value) {
        st.setNull(index, Types.INTEGER);
    } else {
        final Color color = (Color) value;
        st.setInt(index, color.getRGB()); // on stocke en base la valeur RGB de la couleur.
    }
}

  • Object nullSafeGet(ResultSet rs, String names, Object owner) throws HibernateException, SQLException;
    • En charge de convertir la donnée récupérée de la base dans le type souhaité, indiqué dans la méthode returnedClass().
/** {@inheritDoc} */
@Override
public Object nullSafeGet(final ResultSet rs, final String[] names, final Object owner)
  throws HibernateException, SQLException {
    final Integer value = (Integer) Hibernate.INTEGER.nullSafeGet(rs, names[0]); // on récupère la valeur RGB de la couleur.
    if (value != null) {
        return new Color(value);
    } else {
        return null;
    }
}

Une classe d'exemple, jointe à ce billet, contient les autres méthodes.

Déclaration Hibernate

Si le mapping Hibernate est fait avec des fichiers XML, il faut rajouter l'attribut type à la balise property de l'attribut que nous voulons mapper. Il faut indiquer à cet attribut le nom complet de la classe de mapping :

<property name="couleur" column="grp_color" type="com.netapsys.ColorUserType" />

Déclaration JPA

Si le mapping est fait grâce aux annotations JPA, il suffit de rajouter l'annotation @Type avec le nom complet de la classe de mapping sur l'attribut à mapper :

@Type(type = "com.netapsys.ColorUserType")


L'utilisation de ce mapping "automatique" permet de grandement simplifier le reste du code en évitant :

  1. de mélanger deux types différents pour la même donnée ;
  2. d'avoir à passer d'un type à l'autre à différents endroits du code, souvent source de duplication.


Vous trouverez en pièce jointe une archive contenant le code :

  • de la classe Groupe sans annotation : Groupe.java ;
  • du fichier de mapping Hibernate de la classe Groupe : groupe.hbm.xml ;
  • de la classe GroupeJpa, correspondant au bean Groupe avec les annotations JPA : GroupeJpa.java ;
  • de la classe ColorUserType réalisant le mapping : ColorUserType.java.

3 commentaires

  1. Très intéressant ton article Benoît.

    J’ai une question concernant notamment la signature de la méthode sqlTypes(), qui renvoie un tableau d’entiers (et non pas un seul entier) (ce qui va de paire avec la méthode nullSafeGet(…) qui a pour paramètre un tableau de String) : est-ce que ça veut dire qu’un type personnalisé Java peut être sauvegardé via plusieurs colonnes dans le SGBD ?

  2. Un ptit truc que j’ai testé et qui fonctionne très étrangement :
    Dans un fichier CSS

    .class {
    margin-top: 4px; /* propriété pour les autres navigateurs que IE */
    <!–[if !IE]>
    margin-top: -11px;
    <!–>
    margin-top: -11px;
    <![endif]–>

    /* Il faut écrire dans les deux endroits la meme chose pour que la propriété soit appliquée à IE8,IE7 pas testé pour IE6 */

    }

    C’est assez étrange, mais ca m’a permis d’améliorer la compatibilité d’un forum entre les différents navigateurs un peu plus facilement.

    Si ca peut aider quelqu’un

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.