Analizziamo ora un esempio di modello dati utile per esplorare le funzionalità di Kripton come ORM:
Ogni tabella è rappresentata da una classe Java e per ogni classe del modello è necessario definire
l'interfaccia del DAO.
@BindTable(name = "music_albums")
public class Album extends EntityBase {
private Date dataUscita;
@BindColumn(columnType = ColumnType.INDEXED, foreignKey = Artista.class)
private long artistId;
public Date getDataUscita() {
return dataUscita;
}
public void setDataUscita(Date dataUscita) {
this.dataUscita = dataUscita;
}
public long getArtistId() {
return artistId;
}
public void setArtistId(long artistId) {
this.artistId = artistId;
}
}
@BindTable(name = "music_artisti")
public class Artista extends EntityBase {
public boolean singer;
}
@BindTable(name = "music_canzoni")
public class Canzone extends EntityBase {
public long getAlbumId() {
return albumId;
}
public void setAlbumId(long albumId) {
this.albumId = albumId;
}
@BindColumn(columnType = ColumnType.INDEXED, foreignKey = Album.class)
private long albumId;
public Set<String> getTags() {
return tags;
}
public void setTags(Set<String> tags) {
this.tags = tags;
}
private Set<String> tags=new HashSet<>();
}
public class EntityBase {
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@BindColumn(columnType = ColumnType.PRIMARY_KEY)
protected long id;
public String name;
}
Dal modello Java riportato possiamo ricavare le seguenti informazioni:
- qualunque attributo di classe, se non specificato diversamente, viene convertito in colonna.
- Le foreign key sono definite mediante campi di tipo
long
oLong
e con l'attributoforeignKey
dell'annotazioneBindColumn
. - Di default il nome delle tabelle viene ottenuto convertendo i nomi delle classi (da upper camel a lower underscore). Un esempio:
AlbumMusicale
verrebbe resa persistente mediante
la tabellaalbum_musicale
. - Gli attributi complessi come le
collection
(list, set o mappe) sono gestiti senza problemi grazie al fatto che Kripton, per salvarli su SQLite, li
converte
nella loro rappresentazione JSON.
Vediamo ora la definizione delle interfacce DAO:
@BindDao(Album.class)
public interface DaoAlbum extends DaoBase<Album> { }
@BindDao(Artista.class)
public interface DaoArtista extends DaoBase<Artista> { }
@BindDao(Canzone.class)
public interface DaoCanzone extends DaoBase<Canzone> {
@BindSqlSelect(where ="albumId=${albumId}", orderBy = "nome")
List<Canzone> selectByAlbumId(long albumId);
@BindSqlSelect(jql="select id, nome from Canzone where albumId=${albumId} order by nome")
List<Canzone> selectByAlbumId2(long albumId);
}
public interface DaoBase<E extends EntityBase> {
@BindSqlSelect
List<E> selectAll();
@BindSqlInsert
boolean insert(E bean);
@BindSqlUpdate(where="id=${bean.id}")
boolean update(E bean);
@BindSqlDelete(where="id=${bean.id}")
boolean delete(E bean);
@BindSqlDelete
void deleteAll();
}
@BindDataSource(daoSet = {DaoAlbum.class, DaoArtista.class, DaoCanzone.class}, fileName = "esempio.db", version=1, log=true, asyncTask = true)
public interface EsempioDataSource {
}
Dalla definizione delle interfacce DAO possiamo ricavare le seguenti considerazioni:
- come i bean usati per definire il modello, anche le interfacce DAO possono usare l'ereditarietà per portar a fattor comune i metodi comuni a tutti i DAO.
- Le query possono essere definite parzialmente o per intero. In questo ultimo caso si scrive codice SQL con l'accorgimento di utilizzare i nomi di classi ed attributi e non
quelli delle tabelle e delle colonne (JQL, Java Query Language). - In fase di compilazione il codice JQL viene controllato, quindi in caso di errori di sintassi, la segnalazione avviene in fase di compilazione.
Oltre a quanto definito dal diagramma aggiungiamo altre due caratteristiche del modello: ogni tabella avrà il prefisso musica
, vediamo
poi come personalizzare i nomi delle tabelle mediante l'annotazione @BindTable
e come definire le foreign
tra le varie tabelle.
key
Per quanto riguarda la definizione del data source è importate notare che, oltre all'elenco dei DAO associati alle tabelle, mediante alcuni attributi è possibile
definire:
- name per il nome del database SQLite.
- version per il numero di versione dell'applicativo.
- asyncTask per abilitare o meno la generazione di un async-task con cui agevolare il lavoro con la base.
- rx per definire se generare o meno il codice necessario all codice di integrazione tra Kripton e RxJava. Chi non conosce la
programmazione reactive e vuole apprendere le nozioni base di tale argomento, può consultare il progetto RxJava e la documentazione annessa.