Utilizzare Spring nelle web application – parte 13

Nell’articolo n. 10 e precedenti abbiamo già visto come Spring fornisca alcune utilissime classi per gestire le query SQL tramite JDBC, nascondendone al contempo la complessità di gestione. In questo appuntamento approfondiremo la conoscenza di queste classi esaminando altri casi d’uso.

Operazioni SQL come classi

Una delle innovazioni di Spring JDBC è l’approccio innovativo alla gestione delle comuni operazioni di interazione con il database. Nel nostro caso le query di INSERT e UPDATE possono essere realizzate modellando le operazioni con la classe org.springframework.jdbc.object.SqlUpdate. Essendo una classe concreta è possibile utilizzarla in due modi:

  • configurandola tramite i suoi metodi pubblici;
  • creando una sottoclasse.

In entrambi i vantaggi saranno molteplici:

  • utilizzando Spring-JDBC non dovremo preoccuparci di gestire tutti gli aspetti macchinosi, noiosi e propensi all’errore di JDBC;
  • isoleremo ulteriormente la complessità delle query SQL all’interno di classi specializzate;
  • le stesse classi specializzate dovrenno solo preoccuparsi di eseguire un unico compito, ma come si deve, e per fare ciò avrenno tutto l’aiuto possibile dal framework;
  • sarà più semplice, un domani, cambiare database engine, effettuare il refactoring del database o dello strato di accesso ai dati (Data Access Objects – DAO (Wikipedia), dato che ci basterà modificare (o re-implementare) le singole classi, mantenendo inalterata la classe InsertionDAO.

Lo scoglio maggiore è rappresentato dal fatto che bisogna imparare a ragionare in maniera leggermente differente, perché siamo di fronte ad un approccio decisamente object-oriented, contrapposto al tradizionale paradigma imperativo.

INSERT e UPDATE con la classe SqlUpdate

Per realizzare le operazioni utilizzeremo la seconda modalità vista sopra, ovvero definiremo una sottoclasse di SqlUpdate per ogni operazione, sia essa una insert che un update.

Ecco il sorgente per l’operazione di INSERT, la classe it.artera.springtut.dao.SqlInsertInsertion:

package it.artera.springtut.dao;

import java.sql.Types;

import javax.sql.DataSource;

import it.artera.springtut.model.Insertion;

import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;
import org.springframework.jdbc.support.GeneratedKeyHolder;

public class SqlInsertInsertion extends SqlUpdate {

    public SqlInsertInsertion(DataSource dataSource) {
        super(dataSource, "INSERT INTO `insertions` (`id`, `title`, `description`, " +
          "`photo`, `price`, `creationDate`) values(?, ?, ?, ?, ?, ?)");
        declareParameter(new SqlParameter(Types.NUMERIC));
        declareParameter(new SqlParameter(Types.VARCHAR));
        declareParameter(new SqlParameter(Types.LONGVARCHAR));
        declareParameter(new SqlParameter(Types.VARCHAR));
        declareParameter(new SqlParameter(Types.NUMERIC));
        declareParameter(new SqlParameter(Types.DATE));
        setRequiredRowsAffected(1);

        setReturnGeneratedKeys(true);

        compile();
    }

    public void execute(Insertion insertion) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        update(new Object[] {
              insertion.getId(), insertion.getTitle(), insertion.getDescription(),
              insertion.getPhoto(), insertion.getPrice(),
              insertion.getCreationDate()
            }, keyHolder);
        insertion.setId((Long) keyHolder.getKey());
    }
}

Da notare:

 

  • l’inizializzazione avviene nel costruttore, al quale viene passato un DataSource già inizializzato;
  • l’inizializzazione è effettuata utilizzando solamente metodi pubblici. Questo significa che sarebbe possibile ottenere lo stesso effetto senza necessariamente creare una sottoclasse;
  • al costruttore della classe padre vengono passati l’oggetto dataSource e la query SQL da utilizzare;
  • in una fase successiva viene dichiarato il tipo dei parametri posizionali utilizzati;
  • con il metodo setReturnGeneratedKeys(true) stiamo indicando la nostra volontà di recuperare eventuali chiavi generate durante l’inserimento. Nell’articolo n.9 avevamo appunto definito il campo INSERTIONS.ID come id bigint not null auto_increment, ciò significa che non dovremo valorizzare noi questo campo ma ci penserà MySql, assegnandogli un valore incrementale. SqlUpdate ci permette di recuperare tutte le chiavi generate da una INSERT;
  • specificando setRequiredRowsAffected(1) siamo sicuri che in caso di mancato inserimento verrà generato un errore;
  • abbiamo definito un metodo pubblico execute(), al quale viene passata un’istanza di Insertion e che si occuperà di eseguire l’operazione update();
  • notare come l’ID appena generato venga assegnato alla variabile insertion, prelevandolo dal KeyHolder.

Ed ecco la classe SqlUpdateInsertion, che si occupa di aggiornare la tabella:

package it.artera.springtut.dao;

import it.artera.springtut.model.Insertion;

import java.sql.Types;

import javax.sql.DataSource;

import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;

public class SqlUpdateInsertion extends SqlUpdate {
    public SqlUpdateInsertion(DataSource dataSource) {
        super(dataSource,
            "UPDATE `insertions` set `title` = ?, `description` = ?,"
            + " `photo` = ?, `price` = ?,"
            + " `creationDate` = ? where `id` = ?");
        declareParameter(new SqlParameter(Types.VARCHAR));
        declareParameter(new SqlParameter(Types.LONGVARCHAR));
        declareParameter(new SqlParameter(Types.VARCHAR));
        declareParameter(new SqlParameter(Types.NUMERIC));
        declareParameter(new SqlParameter(Types.DATE));
        declareParameter(new SqlParameter(Types.NUMERIC));
        setRequiredRowsAffected(1);

        compile();
    }

    public void execute(Insertion insertion) {
        update(insertion.getTitle(), insertion.getDescription(), insertion.getPhoto(), insertion.getPrice(),
                insertion.getCreationDate(), insertion.getId());
    }
}

Come potete vedere l’operazione di UPDATE è leggermente più semplice perché non
è necessario recuperare chiavi generate.

 

Sviluppi

Nella prossima puntata vedremo come utilizzare concretamente queste due classi
dal nostro InsertionDao

Linkografia