Hibernate – [Blog] du Java et du Freelance https://java-freelance.fr Vis ma vie de freelance java : techno et entreprise Mon, 12 Oct 2015 14:45:01 +0000 fr-FR hourly 1 https://wordpress.org/?v=4.4.4 Premiers pas avec OGM + Ehcache https://java-freelance.fr/java/premiers-pas-avec-ogm-ehcache https://java-freelance.fr/java/premiers-pas-avec-ogm-ehcache#respond Fri, 05 Oct 2012 12:16:05 +0000 https://java-freelance.fr/?p=1517 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.

]]>
https://java-freelance.fr/java/premiers-pas-avec-ogm-ehcache/feed 0
Hibernate Anti-Patterns https://java-freelance.fr/java/hibernate-anti-patterns https://java-freelance.fr/java/hibernate-anti-patterns#respond Sat, 19 Nov 2011 03:13:13 +0000 https://java-freelance.fr/?p=1215 J’ai eu l’occasion d’assiter à une conférence sur les anti-patterns Hibernate par Patricia Wegrzynowicz à Devoxx hier. Elle a mis en avant certains effets de bords induit par l’utilisation d’Hibernate (les points suivants peuvent souvent s’appliquer à d’autres ORM, rien de personnel avec Hibernate :) . Cet article ne reprends qu’une partie du talk: les points que j’ai pu rencontrer en entreprise.

1. L’hydre

Hydra est une entité qui a la particularité de contenir une liste immuable de tête (heads.)

@Entity
public class Hydra {
  private Long id;
  private List heads = new ArrayList();
  @Id @GeneratedValue
  public Long getId() {...}
  protected void setId() {...}
  @OneToMany(cascade=CascadeType.ALL)
  public List getHeads() {
    return Collections.unmodifiableList(heads);
  }
  protected void setHeads(List heads) {...}
}
// creates and persists the hydra with 3 heads
// new EntityManager and new transaction
Hydra found = em.find(Hydra.class, hydra.getId());

La question est la suivante, combien d’appel sont fait en base de données lors de  la deuxième transaction (créer lors de em.find).

(a) 1 select
(b) 2 selects
(c) 1+3 selects
(d) 2 selects, 1 delete, 3
inserts
(e) None of the above

Pendant la recherche, em.find entraine un unique select en base de donnée sur l’hydre.
Pendant le commit qui est effectué à la fin de la transaction, hibernate vérifie que la collection n’est pas dirty, c’est à dire que les objets devraient être recréés en comparant les références objects des listes. Un deuxième select est alors effectué sur les têtes. Dans notre cas, les références ne correspondant pas, l’ensemble de la liste est alors recréé, ce qui explique le delete et les 3 inserts.

Contrairement à ce que l’on pourrait penser dans un premier temps, la bonne réponse est donc la réponse d.

Il faut donc être bien conscient que si on a un objet qui contient une collection et qui porte la liaison, si on affecte  une nouvelle liste à l’élément, la collection est recrée entièrement : un delete et n insertions d’éléments. On peut rencontrer également ce genre de problème si on utilise des outils qui suppriment les proxies hibernate sur les objets.

En régle générale, il vaut mieux travailler directement avec les collections retournées par hibernate à moins de savoir ce que l’on fait.

@Entity
public class Developer {
  @Id @GeneratedValue
  private Long id;
  private String mainTechnology;
  public boolean likesMainTechnology() {
    return "hibernate".equalsIgnoreCase(mainTechnology);
  }
}

// creates and persists a developer that uses hibernate as mainTechnology
// new EntityManager and new transaction
Developer dev = em.find(Developer.class, id);
boolean foundCoolStuff = false;
for (String tech : new String[]{"HTML5", "Android", "Scala"}) {
  dev.setMainTechnology(tech);
// othersAreUsingIt entraine select count(*) from Developer where mainTechnology = ? and id != ?
  if (othersAreUsingIt(tech, dev) && dev.likesMainTechnology()) {
    foundCoolStuff = true; break;
  }
}
if (!foundCoolStuff) {
// still use hibernate
  dev.setMainTechnology("hibernate");
}

(a) 2 selects
(b) 4 selects
(c) 4 selects, 1 update
(d) 4 selects, 4 inserts
(e) None of the above

La bonne réponse est la réponse d, 4 selects et 4 inserts. En effet, hibernate doit garantir la bonne valeur des requêtes exécutées et parfois doit effectuer une flush pendant une transaction. Si on n’effectue plus l’appel à othersAreUsingIt (qui entraine un select sur la table Developer), il n’y a plus d’update.

List semantics

@Entity
public class Forest {
  @Id @GeneratedValue
  private Long id;
  @OneToMany
  Collection<Tree> trees = new HashSet<Tree>();
  public void plantTree(Tree tree) {
    trees.add(tree);
  }
}
// creates and persists a forest with 10.000 trees

// new EntityManager and new transaction
Tree tree = new Tree(“oak”);
em.persist(tree);
Forest forest = em.find(Forest.class, id);
forest.plantTree(tree);

(a) 1 select, 2 inserts
(b) 2 selects, 2 inserts
(c) 2 selects, 1 delete,
10.000+2 inserts
(d) Even more 😉

La bonne réponse est la réponse c. La combinaison de l’annotation OneToMany et d’une collection entraine un bag semantic. La collection est donc recrée.

Semantic Java Type Annotation Add 1 element Update 1 element Remove 1 element
Bag Semantic java.utill.Collection
java.util.List
@ElementCollection
||
@OneToMany
||
@ManyToMany
1 delete + n insert 1 delete + n insert 1 update
Set Semantic java.utill.Set @ElementCollection
||
@OneToMany
||
@ManyToMany
1 insert 1 update 1 delete
List Semantic java.util.List (@ElementCollection
||
@OneToMany
||
@ManyToMany)&&(@OrderColumn||@IndexColumnn)
1 insert+ m update 1 delete + m insert 1 update

@OneToMany with no cascade options
La première intuition est de remplacer le Set par une List (List<Tree> trees = new ArrayList<Tree>() ). Néanmoins, cela marche exactement de la même manière.
Le seul moyen de ne pas avoir de bag semantic est d’utiliser orderColumn ou indexColumn

Il faut faire attention à choisir une collection appropriée sur la partie qui contient  la liaison. Ainsi dans notre cas, Set<Tree> trees = new HashSet<Tree>() permet d’éviter toutes les insertions parasites.

Utilisation d’un set sur l’object qui ne contient pas la liaison.

@Entity
public class Forest {
  @Id @GeneratedValue
  private Long id;
  @OneToMany (mappedBy = “forest”)
  Collection<Tree> trees = new HashSet<Tree>();
  public void plantTree(Tree tree) {
    trees.add(tree);
  }
}
@Entity
public class Tree {
  @Id @GeneratedValue
  private Long id;
  private String name;
  @ManyToOne
  Forest forest;
  public void setForest(Forest forest) {
    this.forest = forest;
     this.forest.plantTree(this);
  }
}

// creates and persists a forest with 10.000 trees
// new EntityManager and new transaction
em.remove(forest);

L’appel à em.remove entraine java.sql.BatchUpdateException : cannot delete or update a parent row : a foreign key constraint fails.

Si on garde le modèle, la seule solution est de parcourir l’ensemble des arbres de la forêt et de setter leur forêt à null.
Il est ensuite possible de supprimer la foret. Ce qui entraine 10 000 updates et 1 delete …
D’autres types de collections auraient été plus adéquats.

Il existe beaucoup d’autres anti-patterns. Pour les débusquer dans votre code, il est plus que recommander d’observer attentivement les requêtes ! Les slides sont ici : http://www.yonita.com/2011_11_16_PERFORMANCE_ANTIPATTERNS_DEVOXX.pdf

]]>
https://java-freelance.fr/java/hibernate-anti-patterns/feed 0
Devoxx Day 1 https://java-freelance.fr/java/devoxx-2009-day1 https://java-freelance.fr/java/devoxx-2009-day1#respond Mon, 16 Nov 2009 23:44:41 +0000 https://java-freelance.fr/?p=209 logodevoxx

Qu’est ce que c’est ?

C’est 5 jours de conférences, principalement orientés java à Anvers en Belgique.  La réception des devoxians à lieu au Metropolis, une salle de cinéma dans la banlieue d’Anvers, à environ 20 min de la gare centrale en tramway. Après avoir récupéré nos badges, on nous fournit un sac de goodies (sac/stylo/bloc note/Tee shirt).

Kanban

La première conférence à laquelle j’ai assisté sur Kanban a duré 3 heures. Kanban est une méthode agile avec un fonctionnement plus simple que Scrum, dans le sens où elle est beaucoup moins contraignante. C’est une méthode avec une forte composante visuel où un radiateur d’information constitue un point central de la méthode. Ses trois principes sont la confiance, l’esprit d’équipe et la transparence. Il n’y a pas de notions de sprint ou d’itération . Les demandes arrivent en permanence et sont traitées au fur et à mesure.

JSF

La deuxième conférence sur JSF 2 était assez pointue, organisée en 3 parties : une heure sur la vue, une heure sur le controleur, une heure sur le modèle. L’aspect orientation composant au lieu de contenu comme c’est le cas avec JSP est assez interessant. L’inconvénient de JSF 1.2 était la quantité très importantes d’XML, JSF 2.0 passe aux annotations, ce qui la rend beaucoup plus simple à manipuler. D’autres améliorations : pouvoir passer des paramètres en GET (sans commentaire), gérer des bookmarkables URLs …
Un des points qui a été souvent abordé lors de la présentation est le futur de JSF. Par exemple, le fait d’avoir une API standard pour AJAX mais également les points bloquants (certaines lourdeurs de configurations). J’ai beaucoup aimé la franchise de Peter, qui a bien montré ce que l’on peut faire et ce qui peut être améliorer.

Hadès & NoSQL

Place ensuite à 2 petites présentations qui m’ont laissé sur ma faim. En une quarantaine de minutes, difficile de rentrer dans un sujet technique, tout au moins d’en montrer ses limites.
Une présentation scolaire mais malgrès tout intéressante d’Hades, qui permet de simplifier l’utilisation de JPa n’a cependant pas montré les cas limites d’utilisation.
De même, la présentation NoSQL n’est pas rentrée assez dans les détails pour que cela deviennent vraiment interessant.

Et le meilleur pour la fin

De 19h à 22h, place à 3 BoFs : Une session très intéressante sur le NoSQL qui a abordé beaucoup de points, comme l’efficacité, les différents types d’outils, l’importance de choisir un système qui correspond à nos attentes. Pour choisir un bon type de base de données, il est conseillé de faire ressortir 2 priorités entre : Consistency, Partition Tolerance et Availability. Ainsi, Cassandra  par exemple,  dans le cas de serveurs distribués, va remonter la dernière information mais sans que cela ne soit garantie (choix fait sur Partition Tolerance et Availability ). L’utilisation pour les boutiques en ligne est tout indiquée (c’est le système d’Amazon). Alors que d’autres systèmes tel que HBase garantissent eux une consistance des données, ils seront plutôt utilisés dans des environnements type CMS, où ce qui est visible à l’écran doit correspondre aux dernières données.

Sur JSF2, la BoF est venu compléter la présentation de l’après midi.

La BoF d’hibernate a été assez intéressante. Une présentation assez longue de l’API Criteria (qui n’est pas très sexy au premier abord) ne m’a pas vraiment fait changé d’avis mais néanmoins, le fait que le meta-modèle soit généré automatiquement par l’IDE simplement est quand même important. Le principal intêret de cet specification est le fait que cela soit typé, néanmoins, peut être parceque je ne suis plus habituée à utiliser un ORM (JDBC pur), je trouve cela très (trop) verbeux. Cet aspect verbeux provient du fait que l’API critéria est très puissante et supporte énormément de choses. On peut l’imaginer encapsulée dans un framework plus simple pour ne retenir que les choses ‘utiles’ à son projet dans une forme verbeuse plus simple.

Et voilà le bilan de cette première journée. D’autre articles arriveront sous peu !

]]>
https://java-freelance.fr/java/devoxx-2009-day1/feed 0