Sql2o: lavorare con i database, senza stress

Nuova puntata dedicata alla scoperta di strumenti “leggeri” da utilizzare nei nostri progetti più… easy.

Oggi parliamo di Sql2o, una piccola libreria molto simpatica che semplifica l’interazione con i database relazionali: il giusto compromesso tra la noia di JDBC e la pesantezza di Hibernate.

Vediamola in azione con qualche esempio!

Per cominciare

Aggiungiamo la libreria al nostro progetto. Utilizzando Maven, basterà inserire la seguente dipendenza:

<dependency>
    <groupId>org.sql2o</groupId>
    <artifactId>sql2o</artifactId>
    <version>1.6.0</version>
</dependency>

Nota Nel momento in cui sto scrivendo, la documentazione JavaDoc di Sql2o non è aggiornata all’ultima versione e presenta qualche problema di accesso. Potete ovviare a questo inconveniente scaricando in locale la documentazione, tramite Maven, e consultandola usando il vostro IDE.

Configurazione iniziale

Il funzionamento di Sql2o ruota attorno all’omonima classe: per effettuare query ed eseguire qualsiasi operazione sul nostro DB dovremo istanziare un oggetto di tipo Sql2o. Ad esempio:

Sql2o sql2o = new Sql2o("jdbc:mysql://localhost:3306/my-db", "username", "password");

L’oggetto funge da wrapper per il nostro data source e permette di aprire e chiudere connessioni verso la base dati.

Come spiegato nella wiki del progetto possiamo utilizzare un unico oggetto istanziato come singleton, da condividere all’interno della nostra applicazione, oppure creare una nuova istanza all’occorrenza.

Aggiungiamo una classe di dominio

Sql2o non è un ORM, ma permette di lavorare agevolmente con oggetti di dominio che mappano le tabelle del nostro database.

Per provare la libreria andrò ad interagire con la tabella persons del mio DB di test; la classe di dominio che aggiungerò al progetto sarà qualcosa di simile:

public class Person {
    private int id;
    private String name;
    private int age;

    // getters + setters
}

Come potete vedere, la classe Person è un semplicissimo POJO senza alcun tipo di Java annotation applicata, né ereditarietà da rispettare. Una semplice classe con metodi di accesso ai campi (per fare prima e mantenere il codice pulito potete utilizzare l’annotation @Data di Lombok).

Costruiamo il nostro DAO

A questo punto possiamo costruire una classe DAO (Data Access Object) che gestirà l’interazione vera e propria con la base dati, implementando le operazioni richieste dal nostro applicativo.

Il codice completo del mio DAO lo trovate su GitHub. Nei paragrafi seguenti esaminiamo invece alcune delle funzionalità messe a disposizione da Sql2o.

Recupero dei dati

Nel nostro DAO avremo sicuramente bisogno di un metodo che restituisca un oggetto Person dato il suo ID:

public Optional<Person> findById(int id) {
    String sql = "select * from persons where id = :id";
    try (Connection con = sql2o.open()) {
        return Optional.ofNullable(
                con.createQuery(sql)
                        .addParameter("id", id)
                        .executeAndFetchFirst(Person.class)
        );
    }
}

Pochi fronzoli. Tutta sostanza:

  • una semplice query in SQL “liscio”, con una where condition parametrica;
  • la gestione della connessione avviene attraverso un try with resources;
  • la gestione dei parametri è banale…;
  • il metodo executeAndFetchFirst permette di recuperare il risultato della query e di mapparlo ad una classe di dominio, così da non dover lavorare con result-set, ma con oggetti Java.

Facile, no? :-)

Recuperare valori scalari

Supponiamo invece di voler ottenere il conteggio delle righe della nostra tabella:

public int countAll() {
    String sql = "select count(id) from persons";
    try (Connection con = sql2o.open()) {
        return con.createQuery(sql)
                .executeScalar(Integer.class);
    }
}

In tal caso possiamo sfruttare il metodo executeScalar specificando il tipo di ritorno (scalare) della nostra query.

Inserimento e aggiornamento

Inserire un nuovo record nella nostra tabella è altrettanto semplice:

public Person insert(Person person) {
    String insertSql = "insert into persons (name, age) values (:name, :age)";
    try (Connection con = sql2o.open()) {
        int key = con.createQuery(insertSql)
                .addParameter("name", person.getName())
                .addParameter("age", person.getAge())
                .executeUpdate()
                .getKey(Integer.class);
        person.setId(key);
    }
    return person;
}

Il metodo da utilizzare per applicare cambiamenti al DB è executeUpdate. Questo vale per operazioni di INSERT e UPDATE, ma anche per modifiche strutturali alla nostra base dati (CREATE, DROP, ALTER, ecc.)

Inoltre, se abbiamo bisogno di recuperare immediatamente l’ID generato per la nuova riga, possiamo aggiungere all’invocazione il metodo getKey.

Gestione delle transazioni

Un’interessante caratteristica di Sql2o è quella di supportare le transazioni e di farlo in modo molto semplice. Supponiamo di voler implementare nel nostro DAO un metodo per l’inserimento multiplo:

public List<Person> insertAll(List<Person> persons) {
    String insertSql = "insert into persons (name, age) values (:name, :age)";
    try (Connection con = sql2o.beginTransaction()) {
        persons.forEach(p -> {
            int key = con.createQuery(insertSql)
                    .addParameter("name", p.getName())
                    .addParameter("age", p.getAge())
                    .executeUpdate()
                    .getKey(Integer.class);
            p.setId(key);
        });
        con.commit();
    }
    return persons;
}

Nel mio caso ho la necessità che tutte le righe vengano aggiunte alla tabella all’interno della stessa transazione.

Come vedete non si utilizza più il metodo open, ma si esplicita un beginTransaction. In questo caso è compito nostro gestire la chiusura della transazione al termine del blocco d’istruzioni (tramite il metodo commit o rollback).

In conclusione

Sql2o è una libreria minimale, ma piacevole da utilizzare, adatta ovviamente ad applicazioni non troppo corpose.

Fateci un giro e fatemi sapere cosa ne pensate. ;-)

Alla prossima,

David