Premiers pas avec OGM + Ehcache

Hibernate OGM se veut une surcouche sur les datastore noSQL, un moyen de persister et d’accéder aux données dans des bases noSQL, quel que soit leurs type. C’est le hibernate des bases de noSQL. L’un des buts, à terme, est de supporter les APIs native d’hibernate. Notamment :

  • les annotations de mapping
  • les apis
  • la sémantique (cascade …)
  • et le  JP-QL

Plus d’infos sur le blog d’Emmanuel Bernard

Alors qu’au début, il n’y avait que Infinispan, on peut désormais y trouver MongoDB et Ehcache. D’autres types viendront surement. Ici, Ehcache est utilisé non pas comme un cache mais comme un datastore noSQL clé/valeur.

Configuration

Pour effectuer des insertions dans Ehcache via OGM, c’est vraiment simple. Après avoir ajouter la dépendance hibernate-ogm-ehcache à votre POM, il faut ensuite faut un fichier persistence.xml.

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">

  <persistence-unit name="ogm-ehcache" transaction-type="JTA">
    <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
    <properties>
      <property name="hibernate.ogm.datastore.provider"
      value="ehcache"/>
      <property name="hibernate.transaction.jta.platform"
      value="org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform"/>
    </properties>
  </persistence-unit>
</persistence>

Note : Actuellement, les transactions, qui sont gérées de manière native par Ehcache, ne fonctionnent pas avec OGM.

Il faut ensuite instancier l’entityManager.

   Configuration cfg = new OgmConfiguration().
        setProperty("hibernate.ogm.datastore.provider", "ehcache").
        addAnnotatedClass(Appli.class).addAnnotatedClass(Platform.class);

    TransactionManager tm = getTransactionManager();
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("ogm-ehcache");

    try {
      EntityManager em = emf.createEntityManager();

Many-to-One

Le modèle est le suivant : 2 classes, Appli et Perform.

 
@Entity @Indexed
public class Appli {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "appli")
  @TableGenerator(
      name = "appli",
      table = "sequences",
      pkColumnName = "key",
      pkColumnValue = "apply",
      valueColumnName = "seed"
  )
  public Long getId() { return id; }
  public void setId(Long id) { this.id = id; }
  private Long id;

  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
  private String name;

  @ManyToOne
  @IndexedEmbedded
  public Platform getPlatform() { return platform; }
  public void setPlatform(Platform platform) { this.platform = platform; }
  private Platform platform;
}
 
@Entity  @Indexed
public class Platform {

  @Id
  @GeneratedValue(generator = "uuid")
  @GenericGenerator(name="uuid", strategy="uuid2")
  public String getId() { return id; }
  public void setId(String id) { this.id = id; }
  private String id;

  @Field
  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
  private String name;
}

On crée ensuite un objet learnAnimals de type Appli qui a pour plateforme itunes.

 Platform itunes = new Platform();
 itunes.setName("iTunes");
 Appli learnAnimals = = new Appli();
 learnAnimals.setName("J apprends les animaux" + i);
 learnAnimals.setPlatform(itunes);

On persiste de la meme manière qu’avec hibernate.

 tm.begin();
 em.persist(learnAnimals);
 Long leanAnimalsId = learnAnimals.getId();
 em.flush();
 em.close();
 tm.commit();

Et voilà, l’objet learnAnimals est persisté. Mais à la différence d’une persistance classique avec Ehcache, qui fonctionne par sérialisation/déserialisation des objets, le modèle est déshydraté.

tl;dr

Pour une entité, elle est donc stockée dans une store ENTITIES avec le modèle clé/valeurs suivant :
Clé
Chaque clé est de type EntityKey, classe qui contient entre autre :

private final String table;
private String[] columnNames;
private Object[] columnValues;

On aura par exemple : {table=’Appli’,columnNames=[‘id’],columValues=[‘1’]} pour l’appli qui a pour identifiant la valeur 1.

Pour la valeur, elle est construite sous la forme d’une map.
Ainsi pour un object Appli qui contient une Plateforme, la map aura cette forme :

Clé           |  Valeur
id            | 1
name          | monAppli
platform_id   | 65d2183a-3a73-4079-83fb-57f9072e0915

A l’insertion c’est un peu plus compliqué. On passera par des objets transitoires, des aggregats nommé TupleOperation qui contiennent le nom de la colonne, sa valeur et son type TupleOperationType, celui-ci pouvant prendre 3 valeurs, PUT, PUT_NULL et REMOVE.
On a donc en fait :

Key         | Value
id          | {columnName='id',columnValue='1'.columnType=TupleOperationType.PUT}
name        | {columnName='name',columnValue='monAppli'.columnType=TupleOperationType.PUT}
platform_id | {columnName='platform_id',columnValue='2886a75c-11ae-4f3d-a132-8d58010382b3'.columnType=TupleOperationType.PUT}

La valeur PUT indiquera qu’il faut faire un

  map.put( action.getColumn(), action.getValue() );

C’est à dire pour le premier exemple :

  map.put('id',1)

Un remove aurait entrainé une suppression de la paire clé/valeur. Un PUT_NULL fait la même chose qu’un PUT.

On a donc bien inséré en base  :

Clé           |  Valeur
id            | 1
name          | monAppli
platform_id   | 65d2183a-3a73-4079-83fb-57f9072e0915

On aura une entrée similaire dans le meme cache ENTITIES pour les objets de types Platform. Pour résumer, on a donc :

Map<EntityKey,Map<String,Object>>

La deuxième store est la store ASSOCIATION. Elle n’est utile que dans les relations plus complexes.On y reviendra par la suite. Dans ce cas, elle reste vide.

La troisième store est la store des IDENTIFIERS, qui stocke les informations relatives aux séquences, notamment toutes celles qui permettent de gérer de manière automatique les identifiants.

Many-to-Many

On définit maintenant une application comme pouvant être associée à N plateforme. Le modèle de Platform ne change pas, celui d’Appli légerement.

 
@Entity @Indexed
public class AppliManyToMany {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "appli")
  @TableGenerator(
      name = "appli",
      table = "sequences",
      pkColumnName = "key",
      pkColumnValue = "apply",
      valueColumnName = "seed"
  )
  public Long getId() { return id; }
  public void setId(Long id) { this.id = id; }
  private Long id;

  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
  private String name;

  @ManyToMany
  @IndexedEmbedded
  public List getPlatforms() { return platforms; }
  public void setPlatforms(List platforms) { this.platforms = platforms; }
  private List platforms=null;

}

En ce qui concerne la table ENTITY, elle est un peu modifié.
La clé reste identique, par contre l’enregistrement ayant pour clé platform_id n’existe plus. On a uniquement :

Clé           |  Valeur
id            | 1
name          | monAppli

L’association est désormais portée par un enregistrement dans la table ASSOCIATION.
La clé, de type AssociationKey, similaire à une EntityKey, représentant l’id 5 :
{table=’AppliManyToMany_Platform’, columnNames=[AppliManyToMany_id], columnValues=[5]}

La valeur est toujours une Map :
Clé (de type RowKey) : {table=’AppliManyToMany_Platform’, columnNames=[AppliManyToMany_id, platforms_id], columnValues=[5, 6301e8be-307f-4884-b3a1-5ec0dad7c3e5]}
Valeur (sous la forme d’une Map): {AppliManyToMany_id=5, platforms_id=2886a75c-11ae-4f3d-a132-8d58010382b3}}
On a donc une structure de la forme :

  Map<AssociationKey,Map<RowKey,Map<String,Object>>>

Pour cet exemple, il n’y a qu’une seule plateforme, celle qui a l’id 6301e8be-307f-4884-b3a1-5ec0dad7c3e5, ainsi, on a donc un unique enregistrement dans la map.

Si on prends un exemple où une application est associée à deux personnes (appli avec id= 6):
On pourrait avoir comme clé principale :
table=’AppliManyToMany_Platform’, columnNames=’AppliManyToMany_id’, columnValues=’6′

Puis comme value les couples clés/valeurs suivants :
RowKey 1 :
Clé (RowKey) : {table=’AppliManyToMany_Platform’, columnNames=[AppliManyToMany_id, platforms_id], columnValues=[6, b705d241-b6dd-4a81-9fb2-2f9f732530d7]}
Valeur associée (sous forme de map): {AppliManyToMany_id=6, platforms_id=b705d241-b6dd-4a81-9fb2-2f9f732530d7}
RowKey 2 :
Clé (RowKey) : {table=’AppliManyToMany_Platform’, columnNames=[AppliManyToMany_id, platforms_id], columnValues=[6, 2886a75c-11ae-4f3d-a132-8d58010382b3]}
Valeur associée (sous forme de map): {AppliManyToMany_id=6, platforms_id=2886a75c-11ae-4f3d-a132-8d58010382b3}

On a donc bien pour l’application 6 deux plateformes, la plateforme b705d241-b6dd-4a81-9fb2-2f9f732530d7 et la 2886a75c-11ae-4f3d-a132-8d58010382b3 . Le framework se servira des identifiants dans un deuxième temps pour retrouver dans la table ENTITIES les plateformes correspondantes et les ‘ajoutera’ dans la liste des plateformes de l’application id=6. Pour avoir une idée encore plus concrète, il y a des schémas dans la documentation d’OGM.

A venir : Hibernate Search, Lucene et autres recherches avec Hibernate OGM.

Leave a Reply