Développement des beans imbriqués pour les requêtes SQL contenant des jointures de table

Utilisez des annotations pureQuery dans un ensemble de beans imbriqués afin de créer des ensembles de beans qui renvoient des données issues de requêtes SQL contenant des jointures de table. pureQuery Runtime génère l'implémentation de l'ensemble des beans imbriqués. Cette implémentation se base sur des jointures de table dans les instructions SQL et les annotations pureQuery ajoutées aux beans.

Créez un ensemble de beans imbriqués définissant la structure hiérarchique des données à renvoyer. Lorsque vous spécifiez une annotation @JoinPoint dans la propriété d'un bean correspondant à la colonne d'un objet ResultSet, pureQuery Runtime génère l'implémentation adéquate. pureQuery Runtime analyse la structure hiérarchique du bean de niveau supérieur, qui correspond au bean renvoyé après avoir terminé le traitement de l'objet ResultSet et des beans imbriqués. Seuls ces beans ou listes de beans mis à jour par la colonne d'un objet ResultSet et disposant d'une annotation @JoinPoint sont générés et complétés. Tout bean ou liste de beans qui n'est pas complété(e) par la colonne d'un objet ResultSet ou qui ne dispose d'aucune annotation @JoinPoint affiche la valeur null ou une valeur d'initialisation du constructeur de beans.

Exemple de beans imbriqués

Voici un exemple montrant deux beans, le bean de niveau supérieur correspond à un bean Department et les beans enfant correspondent à une liste des beans Employee.

Cet exemple utilise la requête SQL suivante avec une jointure entre les tables DEPARTMENT et EMPLOYEE. La requête utilise un paramètre, le numéro de service, et renvoie tous les employés appartenant à ce service :
Select D.DEPTNO, D.DEPTNAME, D.MGRNO, D.ADMRDEPT, E.EMPNO, E.FIRSTNME, E.MIDINIT, 
     E.LASTNAME, E.JOB, E.SEX, E.BIRTHDATE, E.SALARY 
   from (DEPARTMENT AS D LEFT JOIN EMPLOYEE AS E ON D.DEPTNO=E.WORKDEPT) 
   WHERE D.DEPTNO=? ORDER BY D.DEPTNO, E.LASTNAME
La méthode de l'interface DAO suivante spécifie la requête SQL :
  @Select(sql = "Select D.DEPTNO, D.DEPTNAME, D.MGRNO, D.ADMRDEPT, E.EMPNO, 
     E.FIRSTNME, E.MIDINIT, E.LASTNAME, E.JOB, E.SEX, E.BIRTHDATE, E.SALARY 
    from (DEPARTMENT AS D LEFT JOIN EMPLOYEE AS E ON D.DEPTNO=E.WORKDEPT) 
    WHERE D.DEPTNO=? ORDER BY D.DEPTNO, E.LASTNAME")
  public Department joinTest (String deptNo);

Les deux beans suivants ont été développés pour la requête SQL. Le bean Department est un bean de niveau supérieur qui est renvoyé après avoir terminé le traitement de l'ensemble des résultats. Le bean Department correspond au bean parent de la liste des beans deptEmployees. La liste des beans deptEmployees correspond à un ensemble de beans enfant. La colonne d'identité, qui correspond à la propriété d'un bean affichant l'annotation @Id, est deptNo.
@Table(name = "DEPARTMENT")
public class Department
{
  @Id
  @GeneratedKey
  public String deptNo; // this is the ID of the bean and its value is generated by the database
  public String deptName;
  public String mgrNo;
  public String admrDept;
  @JoinPoint(@JoinColumn(name = "EMPNO", table = "EMPLOYEE", propertyName = "empNo"))
  public List<Employee> deptEmployees;
} 
Les données renvoyées par la liste des beans deptEmployees est définie par la classe Employee.
@Table(name = "EMPLOYEE")
public class Employee
{
  @Id
  @GeneratedKey
  public String empNo; // this is the ID of the bean and its value is generated by the database
  public String firstNme;
  public String midInit;
  public String lastName;
  public String workDept;
  public String job;
  public String sex;
  public java.sql.Date birthdate;
  public String salary;
}

En fonction de la requête SQL et des beans, pureQuery Runtime génère un code d'implémentation pour les beans.

Les objets Java renvoyés correspondent à un bean Department contenant une liste java.util.list de 11 beans deptEmployees.

Le résultat formaté est généré suite à l'appel de la méthode joinTest("D11"). Les quatre premières colonnes correspondent aux données issues du bean Department, les autres colonnes de données d'employés sont issues des beans deptEmployees :
dept                             admr                    mid
No  deptName              mgrNo  Dept empNo    firstNme  Init lastName  job      sex birthdate  salary
D11 MANUFACTURING SYSTEMS 000060 D01  000150   BRUCE          ADAMSON   DESIGNER M   1977-05-17 55280.00
                                      000200   DAVID          BROWN     DESIGNER M   1971-05-29 57740.00
                                      200220   REBA      K    JOHN      DESIGNER F   1978-03-19 69840.00
                                      000210   WILLIAM   T    JONES     DESIGNER M   2003-02-23 68270.00
                                      000220   JENNIFER  K    LUTZ      DESIGNER F   1978-03-19 49840.00
                                      000160   ELIZABETH R    PIANKA    DESIGNER F   1980-04-12 62250.00
                                      000180   MARILYN   S    SCOUTTEN  DESIGNER F   1979-02-21 51340.00
                                      000060   IRVING    F    STERN     MANAGER  M   1975-07-07 72250.00
                                      000190   JAMES     H    WALKER    DESIGNER M   1982-06-25 50450.00
                                      200170   KIYOSHI        YAMAMOTO  DESIGNER M   1981-01-05 64680.00
                                      000170   MASATOSHI J    YOSHIMURA DESIGNER M   1981-01-05 44680.00 

Types de beans dans un ensemble de beans imbriqués

Lorsque vous créez un ensemble de beans imbriqués pour les requêtes SQL contenant des jointures de table, vous créez les types de beans suivants :
Bean de niveau supérieur
Le bean de niveau supérieur est un bean correspondant à la valeur de retour de la méthode ou de l'interface API intégrée. Ce bean doit disposer au moins d'une annotation @Id lorsqu'il est utilisé avec les annotations pureQuery dans un ensemble de beans imbriqués. En tant que bean parent, il doit aussi contenir au moins une annotation @JoinPoint. Ce bean peut être un bean parent ou enfant. Dans certains cas, un bean peut être à la fois un bean parent et enfant dans la même structure d'imbrication.
Bean parent
Un bean parent doit contenir au moins une annotation @JoinPoint qui référence un bean ou une liste de beans, et des beans enfant. Aucune annotation @Id n'est obligatoire dans un bean parent à moins qu'il s'agisse d'un bean de niveau supérieur. Si un bean parent (par exemple : BeanB) ne contient aucune annotation @Id, il doit aussi être un bean enfant. Le bean parent de BeanB, BeanA, doit contenir des annotations @JoinColumn dans une annotation @JoinPoint. Les annotations @JoinColumn figurant dans BeanA indiquent des colonnes d'identité pour son bean enfant, BeanB.
Bean enfant
Un bean enfant est référencé par son bean parent avec une annotation @JoinPoint. Un bean enfant n'a pas besoin de contenir une annotation @Id à moins qu'il s'agisse également d'un bean de niveau supérieur. Si un bean enfant ne contient aucune annotation @Id, alors son bean parent doit spécifier les colonnes d'identité du bean avec une ou plusieurs annotations @JoinColumn figurant dans une annotation @JoinPoint.

Colonnes d'identité dans un ensemble de beans imbriqués

Lors de la génération et du remplissage d'un ensemble de beans imbriqués, pureQuery Runtime utilise l'annotation @JoinPoint dans la référence au bean figurant dans le parent du bean ou une annotation @Id dans le bean même. Les annotations définissent les colonnes d'identité qui lient les beans enfant au bean parent. Par exemple, pureQuery Runtime utilise des colonnes d'identité pour déterminer le moment auquel une liste de beans doit être générée pour un bean parent.

Par exemple, une table EMPLOYEE peut contenir plusieurs colonnes pouvant être des colonnes d'identité. Les colonnes qui peuvent être définies comme colonne d'identité sont le matricule ou le numéro de sécurité sociale de l'employé. La colonne d'identité peut être la clé principale de la table, mais pas nécessairement une clé de table. Plusieurs colonnes peuvent être combinées dans une clé composée, tout comme une clé composée peut être créée sur une table de base de données. Le nom d'une colonne d'identité de la table EMPLOYEE peut refléter un mauvais choix, étant donné que les employés peuvent porter le même nom.

pureQuery Runtime utilise les informations de la colonne d'identité et d'autres informations relatives aux beans lors du traitement de l'objet ResultSet. Pour une requête SQL contenant des jointures de table, les données de l'objet ResultSet peuvent rendre plus difficile le mappage de ces données dans une structure de beans hiérarchique. Les caractéristiques suivantes peuvent causer des problèmes au cours du traitement :
  • Les données peuvent ne pas être triées.
  • L'ordre des données dans les tables de base de données peut être conservé.
  • Les données figurant dans ResultSet sont dupliquées. Ceci se produit lorsque les données contiennent des relations un à plusieurs dans la base de données. Par exemple, un service se compose de nombreux employés. Un ResultSet peut contenir des données issues d'une jointure entre une table Department et une table Employee. Les informations sur le service sont dupliquées pour chaque employé de ce service.

pureQuery Runtime vérifie que les beans sont renseignés avec l'ensemble de données correctes et dans l'ordre adéquat. Par exemple, les données ResultSet peuvent ne pas être triées pour effectuer un mappage exact vers la structure de beans imbriqués. pureQuery Runtime peut traiter des données ResultSet appartenant aux beans qui ont été créés antérieurement lors du traitement des données ResultSet et renseigner les beans avec les données adéquates dans l'ordre correct.

Règles de création d'ensembles de beans imbriqués

Vous pouvez créer un ensemble de beans imbriqués renvoyant des résultats issus d'instructions SQL avec des prédicats de jointure complexes en créant une structure d'imbrication plus complexe avec les annotations adéquates. Pour créer un ensemble de beans renvoyant des résultats corrects, les règles suivantes doivent être suivies. La requête SQL ou les beans peuvent exiger des modifications afin de générer un ensemble de beans utiles.

  1. Le nom de chaque colonne de l'objet ResultSet doit effectuer un mappage vers une seule propriété du bean parent ou d'un bean enfant.
    Remarque : Si plusieurs colonnes effectuent un mappage vers une propriété donnée et que les colonnes renvoient toujours les mêmes données, ce mappage n'est pas problématique. Des problèmes peuvent apparaître si une ou plusieurs de ces colonnes affichent une valeur null. Par exemple, une colonne peut renvoyer une valeur null suite à une requête SQL contenant une jointure de table. Cette requête peuvent contenir une clause WHERE ou ON qui ne trouve aucune clé correspondante entre les deux tables.

    Ce problème peut être résolu en spécifiant des alias dans la requête SQL. Les annotations @Column, @Table ou @JoinColumn peuvent aussi être spécifiées avec le nom de table dans les propriétés des beans disposant de plusieurs colonnes.

  2. Si la structure des beans imbriqués contient plusieurs fois le même bean, toutes les références à ce bean seront ignorées après la première instance. Le fait d'utiliser la première instance seule empêche d'éventuelles récursivités infinies dans le traitement de l'ensemble de beans imbriqués par pureQuery Runtime. Utilisez un élément facultatif @JoinPoint columnPrefix ou une sous-classe du bean d'origine et des alias de colonnes dans la requête SQL ou des annotations @JoinPoint ou @JoinColumn pour créer plusieurs beans semblables.
  3. L'élément requis @JoinColumn name doit correspondre au nom de la propriété de base du bean enfant. Sinon, l'élément facultatif propertyName doit être spécifié avec le nom de la propriété de base correspondante du bean enfant.

    Le nom de la propriété de base correspond au nom de la propriété d'un bean sans annotation existante qui modifie son mappage vers l'objet ResultSet. Pour une zone, le nom de la propriété de base correspond au nom de la variable Java. Pour une méthode get, set ou is, il s'agit du nom de la méthode sans le préfixe get, set ou is et le premier caractère défini en minuscules. Dans les exemples suivants, l'étiquette WORKDEPT de la colonne ResultSet n'est pas sensible à la casse.

    Dans l'exemple suivant de la méthode get, le nom de la propriété de base workDepartment est sensible à la casse.
    @Column(name = "WORKDEPT")
    public String getWorkDepartment (); 

    Une annotation @JoinColumn peut être spécifiée dans une annotation @JoinPoint sur un bean ou une liste de beans. Dans ce cas, les éléments name et table spécifiés écrasent le libellé de colonne et le nom de table facultatif définis dans les annotations @Id du bean enfant, y compris toute annotation @Column associée qui est spécifiée.

    Un bean enfant peut disposer de plusieurs annotations @Id. Dans ce cas, l'élément requis name de l'annotation @JoinColumn doit correspondre au nom de la propriété de base du bean enfant. Si les noms ne correspondent pas, alors l'élément facultatif propertyName doit être spécifié avec le nom de la propriété de base correspondante du bean enfant.

    Si vous modifiez l'exemple précédent afin qu'un alias EMPNO de la requête SQL affiche EMPNUM, l'élément propertyName n'est pas requis dans une annotation @JoinColumn. Le bean enfant ne dispose que d'une seule annotation @Id et pureQuery Runtime détermine la propriété devant faire l'objet d'un mappage. Il est conseillé de modifier l'annotation @JoinPoint suivante du bean Department :
    @JoinPoint(@JoinColumn(name = "EMPNO", table = "EMPLOYEE", propertyName = "empNo"))
      public List<Employee> deptEmployees;
    L'annotation @JoinPoint peut être spécifiée comme suit :
      @JoinPoint(@JoinColumn(name = "EMPNUM", table = "EMPLOYEE", propertyName = "empNo"))
       public List<Employee> deptEmployees;
    Si vous voulez changer la colonne d'identité empNo du bean enfant Employee pour la nommer workDept, spécifiez l'élément PropertyName comme suit :
      @JoinPoint(@JoinColumn(name = "WORKDEPT", table = "EMPLOYEE", propertyName = "workDept"))
      public List<Employee> deptEmployees;
  4. L'annotation @Table est utilisée au niveau de la classe. L'annotation spécifie un nom de table par défaut à ajouter à toutes les propriétés qui n'indiquent aucun nom de table. Le nom de la table peut être écrasé pour spécifier une propriété correspondant à n'importe quel nom de table.
  5. Les beans enfant n'exigent aucune annotation @Id pour être considérés comme faisant partie de l'ensemble de beans imbriqués. L'annotation @JoinPoint peut spécifier les colonnes d'identité du bean enfant. La spécification de colonnes d'identité à l'aide de l'annotation @Id concerne uniquement ce bean parent dans cet appel.

    Un bean enfant doit être un bean unique ou une liste de beans et doit afficher l'annotation @JoinPoint. Cette dernière doit se trouver dans la propriété du bean parent qui définit le bean enfant ou la liste de beans.

  6. Chaque colonne de l'objet ResultSet doit uniquement effectuer un mappage vers les propriétés du bean parent et les beans enfant qui sont corrects.

    Un exemple de ce type de mappage est une table DEPARTMENT qui dispose d'une colonne DEPTNO correspondant à une clé principale et une table PROJECT qui dispose aussi d'une colonne DEPTNO. La colonne DEPTNO de la table PROJECT est une clé externe de la table DEPARTMENT. La correspondance par défaut entre les colonnes de l'objet ResultSet et les propriétés est le libellé de la colonne. Dans cet exemple, une jointure des tables DEPARTMENT et PROJECT peut générer deux colonnes avec le libellé de colonne DEPTNO. Si la structure du bean parent et enfant exige que ces colonnes affichent des informations différentes, vous pouvez ajouter des annotations aux beans. Ces annotations peuvent utiliser un nom de table afin de rendre les colonnes uniques. Vous pouvez aussi modifier l'instruction SQL en ajoutant un alias pour créer un libellé de colonne unique. Toutefois, il n'est pas rare pour les requêtes SQL disposant d'une jointure de renvoyer uniquement une colonne lorsque plusieurs colonnes contiennent des données identiques. Dans ce cas, elles sont valides pour une colonne pour la mise à jour de plusieurs propriétés.

    Grâce aux méthodes de type DAO (Data Access Object), des avertissements issus de plusieurs propriétés en cours de mise à jour s'affichent dans la même colonne à partir de l'objet ResultSet de la manière suivante :
    • Un avertissement s'affiche en décrivant les méthodes générées utilisant la même colonne. Cet avertissement s'affiche une fois l'implémentation générée si vous utilisez IBM® Data Studio. Cet avertissement figure également dans le fichier journal du générateur s'il existe et le niveau de trace est défini sur java.util.logging.Level.WARNING ou à un emplacement plus précis.
    • Le générateur pureQuery crée un bloc de commentaires au-dessus du ResultHandler de bean imbriqué généré. Le commentaire affiche les gestionnaires générés qui utilisent la colonne ResultSet. Il affiche également les colonnes ResultSet qui ne sont jamais utilisées.

    Avec ce type de méthode intégrée, des avertissements issus de plusieurs propriétés en cours de mise à jour par cette même colonne ResultSet s'affichent. Ces avertissements sont consignés dans le fichier journal pureQuery Runtime s'il existe et le niveau de trace est défini sur java.util.logging.Level.WARNING ou à un emplacement plus précis.

  7. Une annotation @JoinPoint est requise chaque fois qu'un bean enfant est joint à un bean parent. Une annotation @JoinColumn doit être spécifiée dans l'annotation @JoinPoint, sauf lorsque l'une des instructions suivante est vraie :
    • Le bean enfant ou la liste de beans contient une ou plusieurs annotations @Id qui définissent de façon exhaustive les colonnes d'identité du bean enfant.
    • Les propriétés de la colonne d'identité du bean enfant (quel que soit le libellé de colonne et le nom de table facultatifs) indiquent de façon correcte les colonnes de l'instruction SQL exécutées pour créer l'objet ResultSet. Dans ce cas, vous pouvez spécifier uniquement l'annotation @JoinPoint sans aucun élément.

Traitement de l'exécution d'un bean imbriqué

Les étapes suivantes décrivent comment pureQuery Runtime traite un ensemble de beans imbriqués lors de la génération d'objets avec les données issues d'un objet ResultSet.
  1. L'analyse syntaxique commence par le bean renvoyé, c'est-à-dire le bean parent de niveau supérieur. Chaque propriété du bean est examinée afin de déterminer si cette propriété est mise à jour par l'objet ResultSet. Si une propriété est mise à jour et dispose d'une annotation @JoinPoint, alors cette propriété est examinée pour déterminer si elle référence un bean ou une liste de beans. Si la propriété référence un bean ou une liste de beans, ce bean est ajouté à une liste de beans enfant pour le bean parent. Toutes les propriétés figurant dans le bean parent sont traitées avant que pureQuery Runtime ne traite un bean enfant ayant été trouvé. L'ordre d'examen des propriétés d'un bean n'est pas déterminant.
  2. Une fois que toutes les propriétés d'un bean parent ont été examinées, la liste des beans enfant est vérifiée. Si elle contient un ou plusieurs beans enfant, alors le premier bean de la liste est traité comme s'il s'agissait d'un bean parent.
  3. Après avoir trouvé le premier bean enfant dans le bean parent d'origine et créé une liste de ses beans enfant, pureQuery Runtime traite le bean enfant suivant du bean parent d'origine. S'il existe un autre bean, ce dernier est traité comme dans l'étape précédente.

    Tous les beans enfant du bean parent d'origine sont traités avant de traiter n'importe quel bean enfant pour déterminer s'ils contiennent des beans enfant.

pureQuery Runtime peut déterminer si une classe du bean enfant est traitée pour la première fois ou s'il existe des références récursives dans les beans. Si l'analyse syntaxique pureQuery Runtime de la structure du bean imbriqué détecte un bean ayant déjà été analysé, elle arrête l'analyse syntaxique de ce bean enfant.

Remarque : Un bean parent contenant deux ou plusieurs beans enfant identiques n'est pas pris en charge. Dans ce cas, pureQuery Runtime place les informations dans un seul des beans enfant. Vous pouvez indiquer une sous-classe avec des annotations de propriétés uniques du bean enfant. Vous pouvez aussi utiliser l'élément columnPrefix dans l'annotation @JoinPoint du bean parent pour chacune des classes en double.

Des références récursives à un bean affectent également la manière dont l'exécution de pureQuery renseigne les propriétés des beans enfant. Seule la première instance du bean est renseignée. Les références ultérieures à ce bean sont ignorées à moins que la référence ne dispose d'une annotation @JoinColumn avec un autre élément columnPrefix défini. Etant donné que l'ordre de traitement des propriétés dans un bean unique n'est pas déterminant, il en est de même pour l'ordre de traitement des beans enfant. Toutefois, les beans enfant sont traités dans l'ordre du niveau d'imbrication. Par exemple, un bean imbriqué directement dans le bean parent est traité avant un bean au prochain niveau d'imbrication.

Pour plus d'informations sur l'élément columnPrefix, reportez-vous à la documentation relative à l'annotation @JoinPoint.


Commentaires