novembre
2009
JDBC n’est pas mort : la version 4.0, qui sera intégré dans Mustang (Java SE 6.0), utilisera les Annotations (Metadata) introduite dans Tiger (Java SE 5.0) afin de proposer un mapping Object/Relationnel qui va grandement simplifier l’accès aux bases de données. Cette fonctionnalité est incluse dans les Binary Snapshot du JDK 6.0. Il est donc d’ores et déjà possible de la tester, même avec un driver JDBC 3.0…
Je vous propose donc un petit aperçu des possibilités que cela va apporter.
[edit] Le support des annotations dans JDBC a finalement été annulé et reporté à une version ultérieur. Plus d’info : Pas d’annotation pour JDBC 4.0 !?
Le mapping O/R de JDBC 4.0 est on ne peut plus simple : les champs de la classe Java sont associés aux colonnes du même nom de la table SQL (Il est toutefois possible d’utiliser l’annotation @java.sql.Column sur ces champs afin de les associer à une colonne différente).
Prenons comme exemple une table qui contient des informations pour un compte utilisateur, ce qui pourrait donner :
CREATE TABLE `user_info2` ( `user_id` int(11) NOT NULL auto_increment, `name` varchar(20) NOT NULL default '', `email` varchar(50), `password` varchar(20) NOT NULL default '', `last_visit` date, PRIMARY KEY (`user_id`) )
La classe Java associé pourrait se résumer à ceci :
import java.sql.*; public class User { private @Column("user_id") int id; private String name; private String email; private String password; private @Column("last_visit") Date lastVisit; }
On utilise l’annotation @Column pour les champs id et lastVisit puisqu’ils ne correspondent pas aux noms de la table SQL, ceci permet d’utiliser des règles de nommages différentes pour le code Java et les tables SQL (ce qui est généralement le cas)…
Vous avez sans doute remarqué que tous les champs sont privés et qu’aucune méthode mutateur n’est présente. En effet les champs seront renseignés par réflection et seront directement renseignés. Il n’y a donc aucun impact sur le code métier de la classe Java (libre à vous de déterminer les accesseurs/mutateurs disponibles).
L’étape suivante consiste à définir une interface qui contiendra toutes les requêtes. Cette interface doit étendre l’interface java.sql.BaseQuery, et chacune de ses méthodes doit être annotée soit par l’annotation @java.sql.Query, soit par @java.sql.Update. Ces deux annotations permettent d’associer une requête SQL à la méthode. Par exemple, pour définir une méthode qui retournera tous les éléments de la table user_info, il suffit d’écrire :
import java.sql.*; public interface UserQuery extends BaseQuery { @Query("select * from user_info") public DataSet<User> getAllUsers() throws SQLException; }
Une méthode annotée par @java.sql.Query retourne un @java.sql.DataSet qui n’est rien d’autre qu’une collection comportant tous les éléments retournés par la requête SQL, qui seront mappés dans des objets du type générique du DataSet (dans cet exemple, c’est la classe User définit plus haut qui sera utilisée).
Enfin, et c’est sûrement l’élément le plus l’intéressant : il n’y a pas besoin d’écrire la classe implémentant cette interface, la classe java.sql.QueryObjectFactory générera dynamiquement des implémentations de cette interface lors de l’exécution (d’ailleurs ce traitement peut être fait directement par le driver JDBC afin de l’optimiser).
L’exécution de la requête se résume ainsi au code suivant (traitement des erreurs inclus) :
UserQuery query = null; try { query = QueryObjectFactory.createQueryObject(UserQuery.class, dataSource); for (User user : query.getAllUsers()) { // Traitements } } catch (SQLException lException) { lException.printStackTrace(); } finally { if (query!=null) { query.close(); } }
On est très loin de la gestion complexe des Statement et autre ResultSet, et on peut donc se concentrer sur le code métier…
Bien entendu, il est possible d’utiliser des requêtes paramétrés par les arguments de la méthode de l’interface. Tous les termes entre accolades de la requête SQL seront remplacés par la valeur du paramètre du même nom. Ainsi, pour rechercher un utilisateur par son numéro d’identifiant, il suffit de définir la méthode suivante :
@Query("select * from user_info where user_id = {id}") public DataSet<User> getUserById(int id) throws SQLException;
Et l’annotation @java.sql.Update fonctionne de manière similaire pour les requêtes de mise à jours de la base de données, par exemple pour supprimer une ligne de la table :
@Update("delete from user_info where user_id = {id}") public int delete(int id) throws SQLException;
Pour plus de détail je vous invite à consulter la documentation du package java.sql du JDK 6.0 (attention toutefois, car cela ne correspond pas à l’API définitive et peut donc encore subir des changements…).
Tutoriels
Discussions
- Recuperation du nom des parametres
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- jre 1.5, tomcat 6.0 et multi processeurs
- Classes, méthodes private
- Définition exacte de @Override
- Difference de performances Unix/Windows d'un programme?
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Possibilité d'accéder au type générique en runtime
- [ fuite ] memoire