Hibernate et la sérialisation XML sont dans un bateau

Posté par Bruno Marchesson, le 08/06/2007.

Introduction
Voilà un sujet qui a fait couler beaucoup d’octets. On ne compte plus les pages web, les blogs et les forums emplis de suppliques et de rage à la recherche d’une solution simple pour sérialiser simplement un objet Hibernate partiellement chargé dans un fichier XML. Comme d’habitude, c’est la faute à la LazyInitializationException ;)
A mon tour, je me jette dans l’arène du web, en apportant ma modeste solution à cet épineux problème. Récit d’une après-midi chargée…

Hibernate & dom4J : le couple maudit
Solution intégrée à Hibernate à défaut d’être native, le support dom4J semble la première piste naturelle à explorer. Autant le dire tout net et économiser votre temps et le mien : oubliez-là illico !
Si elle remplit correctement son office (sous réserve d’absence de dépendance circulaire, sinon, c’est la Stack Overflow assurée)_ comprenez un fichier XML décrivant notre objet est bien généré_ cette solution a l’inconvénient majeur de lazy-loader à tour de bras !
En fait, dom4J se comporte comme tout sérialiseur XML standard ayant accès à la session Hibernate : chaque appel à une propriété non chargée génère une requête en base de données, et l’on se retrouve donc avec la totalité du graphe d’objet en mémoire, ce qui est à peu près l’inverse du but recherché.

Le moral en berne face aux 6 secondes et 47 requêtes SQL générées pour sérialiser un objet métier réaliste, j’ai bombardé Google de requêtes combinant « Hibernate », « XML » et « serialization ». Pas mal de pages plus tard, j’ai enfin commencé à apercevoir une lueur d’espoir…

Gaving King saved my day
Et le porteur de lumière est le père d’Hibernate en personne. Non pas qu’il ait donné une solution tout faite, mais la réflexion suivante m’a mis la puce à l’oreille :

“This is *clearly* a problem of the XML serialization framework not having sufficient extension points.
It is absolutely not our fault.

Please people – get off the crackpipe!”

Oubliez la remarque sur le crack ;). Le point important réside dans la possibilité d’étendre la sérialisation XML afin de l’empêcher d’accéder aux propriétés non chargées du graphe d’objet.
C’est exactement le mécanisme que permet BeanLib pour le clonage d’objets (et mis en œuvre sur hibernate4gwt). Ne me restait plus alors qu’à trouver la librairie de sérialisation XML offrant cette même possibilité d’extension.

300 millions de librairies… et moi, et moi, et moi !
Un rapide coup d’œil à cet article montre que le web regorge de libraires de sérialisation XML. Je décidai de me concentrer sur les sérialiseurs runtime afin de ne pas avoir à gérer des DTD et autres fichiers générés.
XStream, JOX ne faisant pas l’affaire, je tombais sur Apache Betwixt, et au détour de la Javadoc, sur la classe ValueSuppressionStrategy. Exactement ce que je cherchais !

And the code is…
Après quelques essais et menues adaptations, le petit bout de code suivant a été mis au point : il permet la sérialisation XML de mon objet partiellement chargée. Pas de lazy-loading, ni de LazyInitializationException.

Par avance, toutes mes excuses pour le formattage, je cherche encore la bonne option pour poste mes bouts de code -/

La classe qui permet d’étendre le comportement de la librairie :

class HibernateValueSuppressionStrategy
           extends ValueSuppressionStrategy
{
  @Override
  public boolean suppressAttribute(AttributeDescriptor descriptor,
                                   String attribut)
  {
    // useless : always return false
    return false;
  }
  @Override
  public boolean suppressElement(ElementDescriptor element,
                                 String namespaceUri,
                                 String localName,
                                 String qualifiedName,
                                 Object value)
  {
    if (Hibernate.isInitialized(value) == false)
    {
      return true;
    }
    try
    {
      PropertyDescriptor property =
        BeanUtils.getPropertyDescriptor(value.getClass(), localName);
      if (property == null)
      {
        return false;
      }
      Object propertyValue =
        property.getReadMethod().invoke(value, (Object[])null);
      return (Hibernate.isInitialized(propertyValue) == false);
    }
    catch (Exception e)
    {
      throw new RuntimeException(e);
    }
  }
}

Il faut aussi redéfinir la stratégie de stockage des ID (sinon l’accès à la méthode hashCode d’une association non chargée génère une exception)

class HibernateIdStoringStrategy extends DefaultIdStoringStrategy
{
  @Override
  public String getReferenceFor(Context context, Object bean)
  {
    if (Hibernate.isInitialized(bean) == false)
    {
      return null;
    }
    return super.getReferenceFor(context, bean);
  }
  @Override
  public void setReference(Context context, Object bean, String id)
  {
    if (Hibernate.isInitialized(bean) == false)
    {
      return;
    }
    super.setReference(context, bean, id);
  }
}

Et enfin la classe principale :

BeanWriter writer = new BeanWriter(new FileWriter(outputPath));

// Hibernate lazy loading stategy adapters
writer.getBindingConfiguration().setIdMappingStrategy(
    new HibernateIdStoringStrategy());
writer.getBindingConfiguration().setValueSuppressionStrategy(
    new HibernateValueSuppressionStrategy());

writer.write(object);
writer.close();

My 2 cents D

Tags: ,

Comments are closed.