public ou
protected. Rappelons que ce constructeur vide existe par défaut si aucun constructeur n'existe dans la classe. Dans le cas contraire, il doit être ajouté explicitement.
final, et aucune de ses méthodes ne peut être
final.
EntityManager est capable de positionner lui-même les valeurs de ces champs. Il le fait par injection, soit directement sur le champ, soit en passant par les
getters
/
setters
. Ce point n'est pas spécifié de façon très satisfaisante en JPA 1.0, et a été revu en JPA 2.0.
Il est possible de définir des entités JPA sur des classes qui héritent les unes des autres. Sur toute une hiérarchie de classes, on doit avoir une unique définition de clé primaire.
@Entity,
@Table et
@Access. Il existe d'autres annotations que nous verrons lorsque nous traiterons l'héritage.
L'annotation
@Entity nous indique que cette classe est une classe persistante. Elle peut prendre un attribut
name, qui fixe le nom de cette entité. Par défaut, le nom d'une entité est le nom complet de sa classe. On peut fixer le nom d'une entité par une constante, en utilisant un import statique.
L'annotation
@Table permet de fixer le nom de la table dans laquelle les instances de cette classe vont être écrites. Cette annotation est particulièrement utile lorsque l'on doit associer un jeu de classes à des tables existantes. L'annotation
@Table supporte plusieurs attributs :
catalog,
schema et
name : permettent de fixer les paramètres de la table utilisée.
@UniqueConstraints permet d'écrire des contraintes d'unicité sur des colonnes ou des groupes de colonnes.
@Access permet de fixer la façon dont l'entity manager va lire et écrire les valeurs des champs de cette classe. Cette annotation ne prend qu'un seul attribut, qui ne peut prendre que deux valeurs :
AccessType.FIELD et
AccessType.PROPERTY. Dans le premier cas, les lectures / modifications se font directement sur les champs, dans le second elles passent par les
getters
/
setters
. Notons que cette annotation peut aussi être placée sur les champs ou
getters
d'une classe. On peut donc particulariser l'injection de valeur champ par champ.
Voyons ceci sur un exemple de classe
Marin.
Exemple 5. Annotations
@Entity et
@Table
package org.paumard.cours.model; import static org.paumard.cours.model.Marin.MARIN ; // reste des imports @Entity(name=MARIN) // le nom de l'entité référence la constante MARIN @Table( name="marin", uniqueConstraints={ @UniqueConstraint(name="nom_prenom", columnNames={"nom", "prenom"}) } ) @Access(AccessType.FIELD) public class Marin implements Serializable { public static final String MARIN = "Marin" ; // reste de la classe }
@UniqueConstraint que l'on veut, séparées par des virgules.
Dans notre exemple, le nom de l'entité est fixée par une constante, importée statiquement. La table dans laquelle les instance de cette classe seront écrites se nomme
marin. Nous avons écrit une contrainte d'unicité, nommée
nom_prenom, qui impose de ne pas avoir deux marins qui portent même nom et même prénom.
Enfin, nous avons annoté notre entité de sorte que les lecture / écriture des valeurs de ses champs se fassent par injection directe sur les champs.
int,
long,
float, etc...
String.
java.sql.Date,
java.sql.Time et
java.sql.TimeStamp. Les types
java.util.Date et
java.util.Calendar ne sont donc pas associés automatiquement.
Serializable, qui sont rangés dans des
blob
.
@Column et
@Lob. Le cas de l'annotation
@Basic, qui s'applique aux champs sera traité dans la suite.
@Access, même si elle peut paraître redondante.
L'annotation
@Column expose les attributs optionnels suivants :
name : permet de fixer le nom de la colonne.
length : pour les chaînes de caractères, cet attribut permet de fixer la taille du champ SQL associé. Notons que cette taille n'est pas vérifiée dans le code Java.
unique : ajoute une contrainte d'unicité sur la colonne.
nullable : empêche cette colonne de porter des valeurs nulles. Cette contrainte est utile pour la définition de clés étrangères.
insertable,
updatable : empêche la modification des valeurs de cette colonne, utilisée pour les clés étrangères.
precision,
scale : fixe les paramètres d'échelle et de précision des colonnes qui portent des nombres.
columnDefinition : permet de donner, en SQL, le code de création d'une colonne. Cet attribut est à utiliser avec précautions, dans la mesure où il impose d'écrire du code SQL natif dans le code Java. Ce code SQL est propre à la base de données cible. C'est précisément ce que l'on ne veut pas faire lorsque l'on fait du JPA !
java.util.Date et
java.util.Calendar ne sont pas associés avec des données en base directement. Pour préciser le type SQL temporel avec lequel on veut stocker ces champs, on dispose d'une annotation spécifique :
@Temporal. Cette annotation prend un unique attribut, qui peut prendre les valeurs
TemporalType.DATE,
TemporalType.TIME ou
TemporalType.TIMESTAMP.
TemporalType.DATE : enregistre les dates en temps que jour / mois / année ;
TemporalType.TIME : enregistre les dates en temps que heure / minute / seconde ;
TemporalType.TIMESTAMP : enregistre les deux informations de date et d'heure dans la jouenée.
@Enumerated, qui peut prendre deux valeurs en attribut :
EnumType.ORDINAL ou
EnumType.STRING.
Locale est un cas un peu particulier. Du point de vue technique cette classe encapsule une chaîne de caractères de petite taille : guère plus de cinq ou six caractères, le plus souvent deux ou trois.
Mais du point de vue de la spécification, cette classe tombe dans la catégorie des champs
Serializable. Elle n'est donc pas prise en compte dans la liste des champs persistés par défaut, et si on l'annote par
@Column, elle doit être mappée dans ... un BLOB !
On atteint ici les limites de la spécification JPA, puisque Hibernate associe un tel champ avec une colonne de type
varchar (ce qui est une bonne idée), et EclipseLink avec une colonne de type
BLOB (ce qui est carrément stupide !).
Exemple 6. Entité JPA avec colonnes annotées
package org.paumard.cours.model; import static org.paumard.cours.model.Marin.MARIN ; // suite des imports @Entity(name=MARIN) @Table( name="marin", uniqueConstraints={ @UniqueConstraint(name="full_name", columnNames={"family_name", "first_name"}) } ) @Access(AccessType.FIELD) public class Marin implements Serializable { public static final String MARIN = "Marin" ; private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name="family_name", length=50) private String nom ; @Column(name="first_name", length=50) private String prenom ; private int age ; @Column(name="civility", length=12) @Enumerated(EnumType.ORDINAL) private Civility civilite ; @Column(name="date_de_naissance") @Temporal(TemporalType.DATE) private Date dateNaissance ; @Column(name="code") @Lob private byte[] key ; // reste de la classe }
@UniqueConstraint référence bien les noms des colonnes et non pas les noms des champs. Si l'on change les noms des colonnes dans les annotations
@Column, alors ils doivent être propagés dans cette contrainte.
nom et
prenom en base sont limités à 50 caractères. Cette limite ne s'applique qu'à la base, ce qui entraînera une troncature du champ s'il est trop long.
age est automatiquement associé à une colonne
age dans la table
marin.
civilite à une colonne de type chaîne de caractères. Cela rendra le contenu de notre table plus lisible, mais surtout indépendant de l'énumération
Civility. Choisir d'écrire en base les numéros d'ordre des éléments énumérés impose effectivement de ne jamais changer ces numéros d'ordre. Cette contrainte, impossible à réaliser dans la pratique, ne se pose pas dans notre cas.
java.util.Date avec
TemporalType.DATE.