Charger partiellement une entité avec JPA

Il est intéressant, dans nombre de cas, de ne charger que partiellement une entité en JPA. Prennons l'exemple de JTentative, un agrégateur de flux RSS. Il est souhaitable de charger la liste des entrées appartenant à un flux, mais sans forcément charger l'intégralité de chaque entité (contenu et description sont des champs lourds et souvent inutilisés).

Plusieurs solutions existent pour charger partiellement une entité, de ne sélectionner que certains champs à charger depuis la base de données.

Première solution : FetchType.LAZY

La première solution consiste à définir certains champs de notre entité avec un attribut fetch à LAZY. Cela permet de spécifier que ces champs ne sont pas chargés lors du chargement de l'entité, mais uniquement lors du premier accès à ce champ.

L'annotation @Basic nous permet de spécifier ce lazy-loading.

Voici un exemple de code mettant en avant cette annotation. Le code de l'entité à été réduit pour plus de lisibilité :
@Entity
public class Entry implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String title;

    private String author;

    @Lob
    @Basic(fetch=FetchType.LAZY)
    private String description;

    @Lob
    @Basic(fetch=FetchType.LAZY)
    private String content;

    /* Getters, Setters... */
}

Cette solution présente cependant un inconvénient majeur : si elle fonctionne correctement lorsque l'on charge une entité via l'EntityManager, elle est complètement inefficace lors de la sélection de données via une requête JPA-QL.

Seconde solution : Sélectionner uniquement les champs dont on a besoin

Une seconde solution peut-être mise en place dans une requête JPA-QL. Cependant, elle ne fonctionnera paslors de la sélection lors d'un appel à la méthode find() de l'EntityManager.

Cette solution est basée sur un opérateur méconnu du langage JPA-QL, new. L'opérateur new permet d'intancier des objets au sein d'une requête JPA-QL. Il s'agit donc d'instancier un objet, en fournissant au constructeur uniquement les champs que nous souhaitons récupérer.

Voici un autre exemple, le code de l'entité a là aussi été réduit :
@Entity
@NamedQuery(name="Entry.findAll", query="SELECT new Entry(e.id, e.title, e.author) FROM Entry AS e")
public class Entry implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String title;

    private String author;

    @Lob
    @Basic(fetch=FetchType.LAZY)
    private String description;

    @Lob
    @Basic(fetch=FetchType.LAZY)
    private String content;

    public Entry() {
    }

    public Entry(Long id, String title, String author) {
        this.id = id;
        this.title = title;
        this.author = author;
    }

    /* Getters, Setters... */
}

Quelques précisions cependant :
  • Il faut préciser la classe complète de l'entité dans la requête, incluant le nom du package
  • Le constructeur que l'on souhaite invoquer doit exister
  • Prennez garde avec vos entités chargées de cette manière : certaines propriétés sont absentes, une mise à jour dans la base de données avec une entité chargée partiellement risque d'effacer certaines valeurs


Le code complet de l'entité utilisée pour cette exemple est disponible sur le dépôt SVN de JTentative.

Permalink  |  Commentaires (2)

Aperiquiz #4 : Integer equals Integer

Soit le code suivant :


Quel est la valeur de c ?
  • true
  • false
  • Erreur de compilation
  • Une exception est levée lors de l'exécution
Réponse : (cliquez pour afficher)

Permalink  |  Commentaires (5)

Types primitifs : pièges courants

It's a trap

Je viens de tomber dans la soirée sur deux pièges, non triviaux, mais facile à éviter pour peu de les connaître...

27/10 != 2.7

Le premier bout de code ressemblait à celui-ci :
public class Main {

    public static void main(String... args) {
        float price = 27/10;
        System.out.println("Price: " + price);
    }

}

Alors qu'il est facile de penser que le code suivant va afficher "Price: 2.70" lors de l'exécution, la réalité est tout autre. Effectivement, le résultat une fois ce code compilé / exécuté est "Price: 2.00".

Le piège est le suivant :
  • 27 est, en Java, un entier
  • 10 est, toujours en Java, un entier
  • L'opération 27/10 est la division de deux entiers. Pour la JVM, le résultat est donc un entier. Donc 2.
  • La valeur 2 est assignée à une variagle de type float, la valeur 2 est transformée en 2.00
  • La valeur 2.00 est affichée dans la console.

Il est facile de contourner ce problème : transformer notre division d'entier en division de nombre flottants :
public class Main {

    public static void main(String... args) {
        float price = 27.0/10.0;
        System.out.println("Price: " + price);
    }

}

Dans ce bloc de code, 27.0 et 10.0 sont des nombres à virgule flottante. La division des deux donne une nombre à virgule flottante, donc 2.7. Et le résultat est donc celui attendu.

return (int) 2.7 - 2.4;

Le second bloc de code piégé consistait à comparer deux entiers :
public class FloatComparator implements ComparatoréFloat> {

    public int compare(Float f1, Float f2) {
        return (int) (f2 - f1);
    }

}

A première vue, ce code semble lui aussi entièrement correct. Le raisonnement suivi est celui-ci :
  • Si je renvoie le résultat de f2 - f1, mon objet triera des nombres par ordre croissant
  • La méthode compare doit renvoyer un entier
  • Je caste le résultat de ma soustraction (un float) en int.

Ce code fonctionne presque tout le temps. Le problème tiens dans le presque... Pour comprendre où est le problème, rien ne vaut une simulation :
  • Je souhaite comparer 2.7 et 2.8
  • 2.8 - 2.7 renvoie 0.1, un nombre positif, donc je respecte le contrat de la méthode compare
  • 0.1 est casté en int, le résultat est 0 alors que f2 est suppérieur a f1, je ne respecte plus le contrat de ma méthode

L'erreur se trouve dans le cast. Il faut donc effectuer un changement dans notre méthode pour renvoyer un nombre positif dans tous les cas où f2 est suppérieur à f1. Par exemple :
public class FloatComparator implements ComparatoréFloat> {

    public int compare(Float f1, Float f2) {
        if (f2 > f1) {
            return 1;
        }
        if (f1 < f2) {
            return -1;
        }
        return 0;
    }

}
Le contrat de Comparator est maintenant respecté.

Permalink  |  Commentaires (0)

Aperiquiz #3 : Paramètres de méthodes

Quel est le nombre maximum de paramètres pour une méthode ?

  • 127
  • 255
  • 256
  • Il n'y a pas de limites
  • Cela dépends du compilateur

Réponse : (cliquez pour afficher)

Permalink  |  Commentaires (1)

NetBeans tip : replier un bloc de code

Question assez récurente lorsque des utilisateurs venant d'environnement .NET découvrent NetBeans :
Est-ce qu'il est possible de réduire un bloc de code dans une classe, comme le fait Visual Studio avec ses régions ?


La réponse est oui. cette fonctionnalité de NetBeans est basée sur une balise XML placée dans des commentaires, la balise <editor-fold>.

Petit exemple :
Il est maintenant possible de replier ce bout de code sur lui même, et il sera remplacé par le texte ....

Il est possible de configurer le texte qui sera affiché lorsque le bloc de code sera replié (au lieu de ...). Pour cela, il suffit de spécifier l'attribut desc dans la balise <editor-fold> :

Permalink  |  Commentaires (3)