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
.