I. Informations▲
Les sources de ce tutoriel sont présents dans le repository Git suivant :
https://github.com/eau-de-la-seine/tutorial-spring-database.git
Vous aurez besoin de Git, Java 8, Maven 3 si vous souhaitez récupérer et lancer l'application. Les requêtes SQL présentés sont suffisamment générique pour que vous puissiez utiliser le SGBDR de votre choix mais il faudra alors changer le driver dans les dépendances du pom.xml pour vous adapter, j'utiliserais PostgreSQL 11.
N'hésitez donc pas à cloner le repository tutorial-spring-database sur votre machine à partir de la commande Git ci-dessous :
git clone https://github.com/eau-de-la-seine/tutorial-spring-database.git
Et à utiliser la commande mvn clean install spring-boot:run pour démarrer le projet. N'hésitez pas à lire le tutoriel Maven 3 si vous souhaitez en apprendre plus sur ce dernier.
Enfin, le projet utilise la dernière version stable de Spring Boot à l'heure où j'écris ce tutoriel, soit la version 2.1.3.
II. Présentation des API▲
Un CRUD (« Create Read Update Delete » en anglais) permet de manipuler un objet afin de lire ou modifier une donnée en base. Nous verrons quatre implémentations différentes d'API pour mettre en place notre CRUD :
- JDBC (Java DataBase Connectivity) : L'API de base Java pour se connecter au bases de données relationnelle, permet de réaliser toutes les opérations SQL de votre base de données relationnelle.
- JdbcTemplate : Une classe Spring qui reprend JDBC et l'intègre au framework Spring.
- JPA (Java Peristence API) : L'API Java pour manipuler des objets Java avec une grande simplicité, cet API nous permet de s'abstraire des requêtes SQL pour réaliser des opérations de base. On peut utiliser des requêtes JPQL (Java Persistence Query Language) pour réaliser des requêtes
SELECT
plus avancées. Il est aussi possible des requêtes SQL natives en dernier recours mais JPA perd alors de son intérêt. - Spring Data : Un concept Spring qui reprend JPA et l'intègre au framework Spring.
Dans une première étape nous créerons une classe entité ProductEntity à l'image de notre table dans la base de données, puis une interface IProductDAO pour manipuler nos objets de type ProductEntity. Nous créerons quatre implémentations pour IProductDAO : ProductDAOviaJDBC, ProductDAOviaJdbcTemplate, ProductDAOviaJPA, ProductDAOviaSpringData, que nous activerons à tour de rôle grâce aux profils Spring (observez la property « spring.profiles.active » dans le fichier application.properties). Nous verrons à travers ce tutoriel les avantages et les inconvénients de chacune de ces implémentations.
III. La classe entité et l'interface DAO▲
Pour notre classe entité « produit », créons une classe ProductEntity à l'image de notre table « products » dans la base :
public
class
ProductEntity {
private
Long id;
private
String name;
private
String urlImage;
private
Integer price;
private
String description;
//
Generate
getters
and
setters
}
Cette classe « produit » contient un identifiant, un nom, une url pour donner accès à son image, un prix (en centimes) et une description. Voici la requête SQL que vous pouvez exécuter pour créer la table products dans votre base :
CREATE
TABLE
products (
id integer
,
name
varchar
,
name
varchar
,
url_image varchar
,
price integer
,
description varchar
,
CONSTRAINT
pk_products PRIMARY
KEY
(
id)
)
;
Reprenez l'interface ci-dessous, les implémentations de cette interface nous permettront de manipuler les données en base via des objets Java :
public
interface
IProductDAO {
ProductEntity insert
(
ProductEntity product) throws
SQLException;
void
update
(
ProductEntity product) throws
SQLException;
void
delete
(
ProductEntity product) throws
SQLException;
ProductEntity select
(
ProductEntity product) throws
SQLException;
List<
ProductEntity>
selectAll
(
) throws
SQLException;
}
Cette interface DAO (Data Access Object) contient les méthodes abstraites pour insérer, mettre à jour, supprimer, et récupérer un ou des éléments de la base sous forme d'objet Java.
IV. Pool de connexion avec la « DataSource »▲
La DataSource est un objet Java permettant d'optimiser les connexions effectuées avec la base de données, elle vous permet par exemple de gérer un certain nombre de connexion avec la base pour éviter de la saturer. Créer une classe de configuration DatabaseConfiguration pour créer cette DataSource :
@
Configuration
@
EnableTransactionManagement
public
class
DatabaseConfiguration {
@
Bean
@
ConfigurationProperties
(
prefix=
"
spring.datasource
"
)
public
DataSource dataSource
(
) {
return
DataSourceBuilder.create
(
).build
(
);
}
}
Ici, l'annotation @
ConfigurationProperties
va récupérer toutes les properties commençant par « spring.datasource » dans le fichier application.properties de Spring :
dbms.port=5432
dbms.database.name=public
spring.datasource.url=jdbc:postgresql://localhost:${dbms.port}/${dbms.database.name}
spring.datasource.jdbc-url=${spring.datasource.url}
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
Vous ferez attention à ne pas laisser des informations sensibles en dur dans le code comme je le fais dans ce tutoriel.
V. L'implémentation JDBC▲
Voici la première implémentation JDBC pour notre interface DAO (cliquez pour dérouler le code) :
Vous remarquerez que cette implémentation est longue et fastidieuse à écrire, nous devons gérer les requêtes SQL, l'initialisation de chacun des champs de notre objet, l'ouverture et la fermeture des connexions.
La seule dépendance dont nous avons eu besoin pour construire cette implémentation est la DataSource.
VI. L'implémentation JdbcTemplate▲
Voici la deuxième implémentation JdbcTemplate pour notre interface DAO (cliquez pour dérouler le code) :
Vous remarquerez que cette implémentation est presque tout aussi longue et fastidieuse à écrire que JDBC, par contre nous déléguons certaines requêtes à JdbcTemplate et ne gérons plus l'ouverture et la fermeture des connexions.
La seule dépendance dont nous avons eu besoin pour construire cette implémentation est un JdbcTemplate (initialisé avec une DataSource), voici le code de DatabaseConfiguration à jour :
@
Configuration
@
EnableTransactionManagement
public
class
DatabaseConfiguration {
@
Bean
@
ConfigurationProperties
(
prefix=
"
spring.datasource
"
)
public
DataSource dataSource
(
) {
return
DataSourceBuilder.create
(
).build
(
);
}
@
Bean
(
"
jdbcTemplate
"
)
@
Profile
(
"
jdbcTemplate
"
)
public
JdbcTemplate jdbcTemplate
(
DataSource dataSource) {
return
new
JdbcTemplate
(
dataSource);
}
}
VII. L'implémentation JPA▲
Les choses intéressantes commencent avec cette troisième implémentation. En effet, pour effectuer les opérations de base d'un CRUD, nous n'avons plus besoin de requête SQL, mais nous devons tout de même ajouter des informations à notre classe ProductEntity grâce aux annotations JPA :
@
Entity
@
Table
(
schema =
"
public
"
, name =
"
products
"
)
public
class
ProductEntity {
@
Id
private
Long id;
private
String name;
@
Column
(
name=
"
url_image
"
)
private
String urlImage;
private
Integer price;
private
String description;
//
Generate
getters
and
setters
}
Voici quelques informations concernant ces annotations JPA :
|
Déclare un EJB Entity Bean, soit une classe entité. Cette annotation est obligatoire |
|
Indique le nom du schéma et de la table de notre classe entité |
|
Indique la clé primaire de notre classe entité. Cette annotation est obligatoire |
|
Indique le nom de la colonne en base, peut être utile si vous utilisez des underscores pour les noms de vos colonnes par exemple |
Voici la troisième implémentation JPA pour notre interface DAO :
@
Repository
@
Profile
(
"
jpa
"
)
public
class
ProductDAOviaJPA implements
IProductDAO {
@
PersistenceContext
(
unitName =
DatabaseConfiguration.JPA_PERSISTANCE_UNIT_NAME)
private
EntityManager em;
@
Override
public
ProductEntity insert
(
ProductEntity product) {
product.setId
(
null
);
return
em.merge
(
product);
}
@
Override
public
void
update
(
ProductEntity product) {
em.merge
(
product);
}
@
Override
public
void
delete
(
ProductEntity product) throws
SQLException {
em.remove
(
product);
}
@
Override
public
ProductEntity select
(
ProductEntity product) {
return
em.find
(
ProductEntity.class
, product.getId
(
));
}
@
Override
public
List<
ProductEntity>
selectAll
(
) {
return
em.createQuery
(
"
SELECT
p
FROM
Product
p
"
, ProductEntity.class
).getResultList
(
);
}
}
Vous remarquerez que cette implémentation est bien plus simple à mettre en place que JDBC ou JdbcTemplate, nous ne gérons ni les requêtes SQL, ni l'initialisation des champs, ni l'ouverture et la fermeture des connexions.
La seule dépendance dont nous avons eu besoin pour construire cette implémentation est un EntityManager, voici le code de à jour (sans JdbcTemplate) :
@
Configuration
@
EnableTransactionManagement
//
Analyze
packages
containing
the
Entity
classes
and
Reposity
interfaces/classes
@
EnableJpaRepositories
(
basePackageClasses =
{
ProductEntity.class
,
ProductRepository.class
}
)
public
class
DatabaseConfiguration {
private
static
final
String JPA_PACKAGES_TO_SCAN =
"
fr.ekinci.demojpa.model
"
;
public
static
final
String JPA_PERSISTANCE_UNIT_NAME =
"
demo-jpa-unit
"
;
@
Bean
@
ConfigurationProperties
(
prefix=
"
spring.datasource
"
)
public
DataSource dataSource
(
) {
return
DataSourceBuilder.create
(
).build
(
);
}
@
Bean
@
Profile
(
"
jpa
"
)
public
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean
(
DataSource dataSource
) {
LocalContainerEntityManagerFactoryBean emfb =
new
LocalContainerEntityManagerFactoryBean
(
);
emfb.setDataSource
(
dataSource);
emfb.setPackagesToScan
(
JPA_PACKAGES_TO_SCAN);
emfb.setPersistenceUnitName
(
JPA_PERSISTANCE_UNIT_NAME);
emfb.setJpaVendorAdapter
(
new
HibernateJpaVendorAdapter
(
));
return
emfb;
}
}
VIII. L'implémentation Spring Data▲
L'implémentation Spring Data reprend le meilleur de JPA et l'intègre au framework Spring. Pour mettre en place ce dernier rien de plus simple, créez l'interface ci-dessous :
import
org.springframework.data.repository.CrudRepository;
public
interface
ProductRepository extends
CrudRepository<
ProductEntity, Long>
{
}
Cette interface « ProductRepository » hérite de l'interface CrudRepository de Spring :
- L'interface possède deux paramètres génériques, le premier représente le type de la classe entité et le second le type de la clé primaire dans cette classe entité
- L'interface hérite d'une autre interface Repository
<
T, ID>
annotée avec@
Indexed
pour être détectée par Spring. Nous ne créerons pas de classe héritant de notre interface ProductRepository, c'est la « magie » de Spring qui se chargera de nous fournir une implémentation lors de l'injection de dépendance
Voici la quatrième implémentation Spring Data de notre interface DAO :
@
Repository
@
Profile
(
"
springData
"
)
public
class
ProductDAOviaSpringData implements
IProductDAO {
private
final
ProductRepository productRepository;
public
ProductDAOviaSpringData
(
ProductRepository productRepository) {
this
.productRepository =
productRepository;
}
@
Override
public
ProductEntity insert
(
ProductEntity product){
return
productRepository.save
(
product);
}
@
Override
public
void
update
(
ProductEntity product){
productRepository.save
(
product);
}
@
Override
public
void
delete
(
ProductEntity product){
productRepository.delete
(
product);
}
@
Override
public
ProductEntity select
(
ProductEntity product){
return
productRepository.findById
(
product.getId
(
)).orElse
(
null
);
}
@
Override
public
List<
ProductEntity>
selectAll
(
){
return
(
List<
ProductEntity>
) productRepository.findAll
(
);
}
}
Vous remarquerez que cette implémentation est aussi simple à mettre en place que JPA. Pensez à importer la dépendance « spring-boot-starter-data-jpa » dans votre pom.xml et à initialiser vos properties « spring.datasource » dans le fichier application.properties
IX. Remerciements▲
Je tiens à remercier XXX pour la relecture orthographique de ce tutoriel.