Mapping hibernate avec @Formula

Il arrive (parfois) que l’on souhaite dans nos beans mapper un attribut, non pas directement avec une colonne de notre base de données ni même avec des colonnes héritées ni de des jointures. C’est le cas par exemple dans une situation ou l’on souhaiterait par exemple associé à notre objet la valeur moyenne d’un paramètre sur une série de mesure.

Prenons le cas dans un système commercial d’un objet client, qui dispose de données statiques (nom, prénom) mais aussi de données dynamiques “métier” (panier moyen, nombre d’achats réalisés dans les 12 derniers mois).

Soit on fait le choix de mapper ces données directement sur des colonnes, et à chaque achat, en plus d’insérer une nouvelle entrée dans la table achat il faudra mettre à jour ces 2 colonnes de la table client. C’est à proscrire car on va potentiellement lancer des traitements lourds sur un objet qui ne participe pas au workflow d’achat (risque de lenteur, d’erreur…)
C’est pour cela que je recommande le traitement au niveau de l’objet client et pour cela s’appuyer dynamiquement sur les entrées d’achats pour calculer à la volée.

A noter pour les puristes à la recherche de la beauté et/ou de la complexité et/ou de la performance. Le fetching n’est pas lazy. Ce qui peut avoir de l’impact sur les performances si on commence à tirer des grappes d’objets contenants des formulas compliqués.
Sachez qu’il est possible de forcer le lazy.loading par l’annotation @Basic(fetch = FetchType.LAZY) et en forçant le bytecode.
Je ne détaille pas ceci aux puristes ce seraient leur faire injure. Dans ce cas le DTO (data transfert object) à base de projections est l’objet le plus indiqué.

@Table(name = "client")
@Entity
public class Client {

        private long idTechnique;
	private String nom;
	private String prenom;
        // c'est ça qu'on veut calculer depuis la table vente
	private float panierMoyen;
        private int nbAchats;
}

et une table des ventes

@Table(name = "vente")
@Entity
public class Vente {

        // l'id du client acheteur
        private long acheteurId;
	private float valeur;
	private Date date;
}

les mappings par annotation des champs autres que panierMoyen et nbAchats sont triviaux et on retrouvera des entrées du style

        // useless mais on le met pour l'exemple
        @Column(name = "nom")
	public String getNom() {
		return nom;
	}

	public void setNom(String nom) {
		this.nom = nom;
	}

pour nbAchats on calculera sur le nombre d’entrée pour notre client sur les 12 derniers mois soit une requete de cette forme (en fonction des SGBD)
query = select count(*) from vente where acheteurId=idTechnique and date > CURDATE() – INTERVAL 12 MONTH

et donc un mapping

        @Formula("select count(*) from vente where acheteurId=idTechnique and date > CURDATE() - INTERVAL 12 MONTH ")
	public String getPanierMoyen() {
		return panierMoyen;
	}

Je laisse aux lecteurs l’utilisation de la fonction de moyenne ( avg() ) pour calculer et mapper le panier moyen. Je donnerai la réponse si certains n’y arrivaient pas.

Bons mappings!

Posted in Database, hibernate | Tagged , , | Leave a comment