< Home
Stampa

Accesso a Database

Sommario

In questa lezione vedremo come accedere ad un RDBMS Mysql con Springboot.

1. Attivazione servizio

Per avere un RDBMS funzionante si può procedere in diversi modi:

  • utilizzare un servizio online, come aiven.io

In questa lezione useremo il servizio online, perché offre il vantaggio di rendere accessibile il database da Internet. Per eseguire l’installazione seguire le istruzioni del sito (qui useremo proprio aiven.io) e si arriverà ad una pagina che fornirà le coordinate di accesso:

  • user
  • password
  • il nome del db,
  • l’host
  • porta da raggiungere
  • certificato per l’accesso tramite SSL (unica connessione ammessa) da salvare in un file ca.pem

2. Predisposizione progetto SpringBoot

Creare il progetto (starter-web) ed aggiungere la dipendenza Maven:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.3.0</version> 
</dependency>

Modificare application.properties:

# Sostituisci il percorso con il cammino assoluto o relativo del tuo file ca.pem
spring.datasource.url=jdbc:mysql://mysql-tuo-progetto.aivencloud.com:XXXXX/defaultdb?sslMode=VERIFY_CA&trustCertificateKeyStoreUrl=file:src/main/resources/ca.pem&trustCertificateKeyStoreType=PEM
spring.datasource.username=avnadmin
spring.datasource.password=tua_password_aiven
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

Eseguire un test. Creare questa classe di prova:

package it.cipiaceinfo.dbtest;

import com.esempio.model.Utente;
import com.esempio.repository.UtenteRepository;
import org.springframework.jdbc.core.JdbcTemplate; 
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    CommandLineRunner controllaSSL(JdbcTemplate jdbcTemplate) {
       return args -> {
           String cipher = jdbcTemplate.queryForObject(
              "SHOW STATUS LIKE 'Ssl_cipher'", 
              (rs, rowNum) -> rs.getString("Value")
           );
          System.out.println("-> Algoritmo SSL in uso dal database: " + cipher);
       };
   }
}

3. Creare il model

Creiamo solo le classi Autore ed Album:

package it.cipiaceinfo.libreriamusicale.model;

public class Autore {
    private Long id;
    private String nome;
    private String cognome;

    public Autore() {}

    public Autore(Long id, String nome, String cognome) {
        this.id = id;
        this.nome = nome;
        this.cognome = cognome;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getNome() { return nome; }
    public void setNome(String nome) { this.nome = nome; }

    public String getCognome() { return cognome; }
    public void setCognome(String cognome) { this.cognome = cognome; }
}

e

package it.cipiaceinfo.libreriamusicale.model;

public class Album {
    private Long id;
    private String titolo;
    private Integer anno;
    private Long autoreId; // Mappa direttamente la FK del database

    // Costruttori
    public Album() {}

    public Album(Long id, String titolo, Integer anno, Long autoreId) {
        this.id = id;
        this.titolo = titolo;
        this.anno = anno;
        this.autoreId = autoreId;
    }

    // Getter e Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getTitolo() { return titolo; }
    public void setTitolo(String titolo) { this.titolo = titolo; }

    public Integer getAnno() { return anno; }
    public void setAnno(Integer anno) { this.anno = anno; }

    public Long getAutoreId() { return autoreId; }
    public void setAutoreId(Long autoreId) { this.autoreId = autoreId; }
}

Come si vede sono classi POJO, ovvero senza logica, ma solo dati.

4. Repository

Il modello a Repository è simile a quello di JPA già visto:

package it.cipiaceinfo.libreriamusicale.repository;

import it.cipiaceinfo.model.Autore;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public class AutoreRepository {

    private final JdbcTemplate jdbcTemplate;

    public AutoreRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // Mapper per trasformare le righe del DB in oggetti Java
    private final RowMapper<Autore> autoreRowMapper = (rs, rowNum) -> new Autore(
            rs.getLong("Id"),
            rs.getString("Nome"),
            rs.getString("Cognome")
    );

    public List<Autore> findAll() {
        String sql = "SELECT Id, Nome, Cognome FROM AUTORE";
        return jdbcTemplate.query(sql, autoreRowMapper);
    }

    public Optional<Autore> findById(Long id) {
        String sql = "SELECT Id, Nome, Cognome FROM AUTORE WHERE Id = ?";
        List<Autore> risultati = jdbcTemplate.query(sql, autoreRowMapper, id);
        return risultati.stream().findFirst();
    }

    public int save(Autore autore) {
        String sql = "INSERT INTO AUTORE (Nome, Cognome) VALUES (?, ?)";
        return jdbcTemplate.update(sql, autore.getNome(), autore.getCognome());
    }

    public int update(Autore autore) {
        String sql = "UPDATE AUTORE SET Nome = ?, Cognome = ? WHERE Id = ?";
        return jdbcTemplate.update(sql, autore.getNome(), autore.getCognome(), autore.getId());
    }

    public int deleteById(Long id) {
        String sql = "DELETE FROM AUTORE WHERE Id = ?";
        return jdbcTemplate.update(sql, id);
    }
}

E Album:

package it.cipiaceinfo.libreriamusicale.repository;

import it.cipiaceinfo.model.Album;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public class AlbumRepository {

    private final JdbcTemplate jdbcTemplate;

    public AlbumRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // Mapper base per la tabella Album
    private final RowMapper<Album> albumRowMapper = (rs, rowNum) -> new Album(
            rs.getLong("Id"),
            rs.getString("Titolo"),
            rs.getInt("Anno"),
            rs.getObject("AutoreId") != null ? rs.getLong("AutoreId") : null
    );

    public List<Album> findAll() {
        String sql = "SELECT Id, Titolo, Anno, AutoreId FROM ALBUM";
        return jdbcTemplate.query(sql, albumRowMapper);
    }

    public Optional<Album> findById(Long id) {
        String sql = "SELECT Id, Titolo, Anno, AutoreId FROM ALBUM WHERE Id = ?";
        List<Album> risultati = jdbcTemplate.query(sql, albumRowMapper, id);
        return risultati.stream().findFirst();
    }

    public List<Album> findByAutoreId(Long autoreId) {
        String sql = "SELECT Id, Titolo, Anno, AutoreId FROM ALBUM WHERE AutoreId = ?";
        return jdbcTemplate.query(sql, albumRowMapper, autoreId);
    }

    public int save(Album album) {
        String sql = "INSERT INTO ALBUM (Titolo, Anno, AutoreId) VALUES (?, ?, ?)";
        return jdbcTemplate.update(sql, album.getTitolo(), album.getAnno(), album.getAutoreId());
    }

    public int update(Album album) {
        String sql = "UPDATE ALBUM SET Titolo = ?, Anno = ?, AutoreId = ? WHERE Id = ?";
        return jdbcTemplate.update(sql, album.getTitolo(), album.getAnno(), album.getAutoreId(), album.getId());
    }

    public int deleteById(Long id) {
        String sql = "DELETE FROM ALBUM WHERE Id = ?";
        return jdbcTemplate.update(sql, id);
    }

    public List<String> findAlbumConDettagliAutore() {
        String sql = "SELECT al.Titolo, al.Anno, au.Nome, au.Cognome " +
                     "FROM ALBUM al " +
                     "LEFT JOIN AUTORE au ON al.AutoreId = au.Id";
        
        return jdbcTemplate.query(sql, (rs, rowNum) -> 
            rs.getString("Titolo") + " (" + rs.getInt("Anno") + ") - " +
            (rs.getString("Nome") != null ? rs.getString("Nome") + " " + rs.getString("Cognome") : "Autore Sconosciuto")
        );
    }
}

I Repository hanno invece un insieme di elementi da analizzare:

  • il Repository implementa i metodi CRUD (è bene, in un progetto reale, creare una interfaccia);
  • classe JdbcTemplate: questa classe consente di eseguire query sul database. Essa implementa due metodi:
    • query(sql, rowMapper, attrs…): per eseguire query in lettura.
      • come primo argomento riceve l’sql string: come si vede la stringa può essere parametrica con segnaposto marcati con ‘?’;
      • il secondo argomento è una funzione chiamata rowMapper (che vedremo sotto)
      • gli altri argomenti sono i parametri dell’sql (es. id)
      • Il risultato è una istanza della classe ResultSet, di fatto un dizionario di stringhe contenenti stringhe, ed un intero che indica il numero di righe restituite;
    • update(sql, attrs.), per eseguire scritture (update, insert, delete):
      • come primo argomento riceve l’sql, parametrico;
      • gli altri argomenti sono i parametri
      • il risultato è un intero che indica le righe modificate
  • rowMapper: è una funzione che riceve il resultSet ed esegue il mapping dei suoi campi sull’entità associata al repository (Autore, Album, ecc.)
  • E’ infine presente un metodo aggiuntivo che esegue una query custom che stampa tutti gli album coi relativi autori. In un progetto più strutturato conviene mappare direttamente su un DTO, che riceve nel suo costruttore direttamente il ResultSet.

5. Main

In questo semplice progetto non creiamo un Web Service ma una semplice applicazione da linea di comando.

package it.cipiaceinfo.libreriamusicale;

import it.cipiaceinfo.model.Album;
import it.cipiaceinfo.model.Autore;
import it.cipiaceinfo.repository.AlbumRepository;
import it.cipiaceinfo.repository.AutoreRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    CommandLineRunner eseguiTest(AutoreRepository autoreRepository, AlbumRepository albumRepository) {
        return args -> {
            System.out.println("\n--- INIZIO TEST DATABASE (JDBC + AIVEN SSL) ---");

            try {
                System.out.println("-> Pulizia tabelle...");
                albumRepository.findAll().forEach(album -> albumRepository.deleteById(album.getId()));
                autoreRepository.findAll().forEach(autore -> autoreRepository.deleteById(autore.getId()));

                System.out.println("-> Inserimento nuovo autore...");
                Autore nuovoAutore = new Autore(null, "Michael", "Jackson");
                autoreRepository.save(nuovoAutore);

                // Recuperiamo l'autore appena inserito per ottenere l'ID generato dal DB (AUTO_INCREMENT)
                Autore autoreSalvato = autoreRepository.findAll().stream()
                        .findFirst()
                        .orElseThrow(() -> new RuntimeException("Autore non trovato dopo il salvataggio"));
                
                System.out.println("   Autore salvato con ID: " + autoreSalvato.getId());

                // 3. Inseriamo due Album legati a quell'autore usando il suo ID
                System.out.println("-> Inserimento album...");
                Album album1 = new Album(null, "Thriller", 1987, autoreSalvato.getId());
                Album album2 = new Album(null, "Bad", 1981, autoreSalvato.getId());
                
                albumRepository.save(album1);
                albumRepository.save(album2);

                // 4. Test della Query di Lettura Semplice
                System.out.println("\n--- Elenco di tutti gli Album nel DB (Dati RAW) ---");
                List<Album> tuttiGliAlbum = albumRepository.findAll();
                for (Album alb : tuttiGliAlbum) {
                    System.out.println("ID: " + alb.getId() + " | Titolo: " + alb.getTitolo() + " | Anno: " + alb.getAnno() + " | AutoreId: " + alb.getAutoreId());
                }

                // 5. Test della Query Custom con JOIN (Dettagli Formattati)
                System.out.println("\n--- Elenco Album con Dettagli Autore (Query JOIN) ---");
                List<String> reportAlbum = albumRepository.findAlbumConDettagliAutore();
                for (String riga : reportAlbum) {
                    System.out.println(riga);
                }

            } catch (Exception e) {
                System.err.println("\nSi è verificato un errore durante l'esecuzione del test:");
                e.printStackTrace();
            }

            System.out.println("\n--- FINE TEST DATABASE ---");
        };
    }
}