Tutoriel pour créer un CRUD avec Spring

Ce tutoriel s'adresse à tous ceux qui souhaitent créer un CRUD pour manipuler des données avec une base de données relationnelle. Ce tutoriel repose sur le framework Spring.

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum.

1 commentaire Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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 :

 
Sélectionnez
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 :

 
Sélectionnez
 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 :

 
Sélectionnez
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 :

 
Sélectionnez
@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 :

 
Sélectionnez
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) :

 
CacherSélectionnez

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) :

 
CacherSélectionnez

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 :

 
Sélectionnez
@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 :

 
Sélectionnez
@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 :

@Entity

Déclare un EJB Entity Bean, soit une classe entité. Cette annotation est obligatoire

@Table

Indique le nom du schéma et de la table de notre classe entité

@Id

Indique la clé primaire de notre classe entité. Cette annotation est obligatoire

@Column

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 :

 
Sélectionnez
@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) :

 
Sélectionnez
@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 :

 
Sélectionnez
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 :

 
Sélectionnez
@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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Gokan EKINCI. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.