Prima parte
Continuiamo a creare il nostro semplice emulatore di borsa. Ecco cosa faremo:
- Creiamo un diagramma di organizzazione del database.
- Descriveremo cosa, come e dove viene archiviato.
- Scopriamo come i dati sono correlati tra loro.
- Iniziamo ad apprendere le basi di SQL utilizzando l'esempio del comando di creazione di tabelle SQL CREATE TABLE , Data Definition Language ( DDL ) del linguaggio SQL.
- Continuiamo a scrivere il programma Java. Implementiamo le principali funzioni del DBMS in termini di java.sql per creare il nostro database a livello di programmazione, utilizzando JDBC e un'architettura a tre livelli.
Queste due parti si sono rivelate più voluminose, poiché dobbiamo familiarizzare con le basi di SQL e con l'organizzazione di un DBMS dall'interno e tracciare analogie con Java. Per non annoiarvi con i listati dei codici, alla fine ci sono i collegamenti al corrispondente repository github di commit con il programma.
Progettazione DBMS
Descrizione dell'applicazione
Hai già sentito che l'organizzazione dell'archiviazione dei dati è parte integrante della programmazione. Permettimi di ricordarti che lo scopo della nostra applicazione è la più semplice emulazione di scambio:
- Ci sono azioni il cui valore può variare durante la giornata di negoziazione secondo determinate regole;
- ci sono trader con capitale iniziale;
- i trader possono acquistare e vendere azioni in base al loro algoritmo.
Lo scambio opera
in tick - periodi di tempo fissi (nel nostro caso - 1 minuto). Durante un tick, il prezzo delle azioni può cambiare e quindi il trader può acquistare o vendere azioni.
Scambia la struttura dei dati di emulazione
Chiamiamo modelli di entità di scambio individuali. Per evitare errori di arrotondamento, lavoreremo con gli importi finanziari attraverso una classe
BigDecimal
(i dettagli sono disponibili nel collegamento alla fine dell'articolo). Descriviamo più nel dettaglio la struttura di ciascun modello:
Promozione:
Attributo |
Tipo |
Descrizione |
name |
Srting |
Nome |
changeProbability |
int |
Probabilità di variazione del tasso in percentuale su ciascun tick |
startPrice |
BigDecimal |
Costo iniziale |
delta |
int |
L'importo massimo in percentuale di cui può modificare il valore corrente |
Prezzo delle azioni:
Attributo |
Tipo |
Descrizione |
operDate |
LocalDateTime |
Tempo (tick) per l'impostazione della tariffa |
share |
Promozione |
Collegamento alla promozione |
rate |
BigDecimal |
Prezzo delle azioni |
Commerciante:
Attributo |
Tipo |
Descrizione |
name |
Corda |
Tempo (tick) per l'impostazione della tariffa |
sfreqTick |
int |
Frequenza delle transazioni. Specificato dal periodo, in tick, dopo il quale il trader esegue le operazioni |
cash |
BigDecimal |
Importo di denaro diverso dalle azioni |
traidingMethod |
int |
L'algoritmo utilizzato dal trader. Impostiamolo come numero costante, l'implementazione dell'algoritmo sarà (nelle parti successive) in codice Java |
changeProbability |
int |
Probabilità di portare a termine l'operazione, percentuale |
about |
Corda |
Probabilità di variazione del tasso, in percentuale, su ciascun tick |
Azioni del commerciante:
Attributo |
Tipo |
Descrizione |
operation |
int |
Tipo di transazione (acquisto o vendita) |
traider |
Commerciante |
Collegamento commerciante |
shareRate |
Prezzo delle azioni |
Collegamento al prezzo delle azioni (rispettivamente, il titolo stesso, il suo corso e l'ora in cui è stato emesso) |
amount |
Lungo |
Numero di azioni coinvolte nella transazione |
Per garantire l'unicità di ciascun modello, aggiungeremo un attributo
id
di tipo
long . Questo attributo sarà
univoco all'interno delle istanze del modello e lo identificherà in modo univoco. Gli attributi che fanno riferimento ad altri modelli (trader, azioni, prezzo delle azioni) possono utilizzare questo
id
per identificare in modo univoco il modello corrispondente. Viene subito in mente il pensiero che potremmo utilizzare
Map<Long, Object>
per archiviare tali dati, dov'è
Object
il modello corrispondente. Tuttavia, prova a implementarlo nel codice nelle seguenti condizioni:
- la dimensione dei dati supera notevolmente la quantità di RAM disponibile;
- è previsto l'accesso ai dati da una dozzina di luoghi diversi;
- è richiesta la capacità di modificare e leggere simultaneamente i dati;
- è necessario garantire regole per la formazione e l'integrità dei dati;
...e ti troverai ad affrontare compiti che richiedono qualifiche adeguate e tempo per essere implementati. Non è necessario “reinventare la ruota”. Molto è già stato pensato e scritto per noi. Quindi utilizzeremo quanto già sperimentato negli anni.
Memorizzazione dei dati in Java
Consideriamo l'azione. In Java, abbiamo creato una classe specifica per questo modello
Share
con i campi
name
,
changeProbability
,
startPrice
,
delta
. E molte condivisioni sono state archiviate come
Map<Long, Share>
, dove la chiave è un identificatore univoco per ciascuna condivisione.
public class Share {
private String name;
private BigDecimal startPrice;
private int changeProbability;
private int delta;
}
Map<Long, Share> shares = new HashMap<>();
shares.put(1L, new Share("ibm", BigDecimal.valueOf(20.0), 15, 10));
shares.put(2L, new Share("apple", BigDecimal.valueOf(14.0), 25, 15));
shares.put(3L, new Share("google", BigDecimal.valueOf(12.0), 20, 8));
...
shares.put(50L, new Share("microsoft", BigDecimal.valueOf(17.5), 10,4 ));
Per accedere alla promozione desiderata tramite ID utilizzare il metodo
shares.get(id)
. Per trovare un titolo in base al nome o al prezzo, dovremmo scorrere tutti i record cercando quello di cui abbiamo bisogno e così via. Ma andremo dall'altra parte e memorizzeremo i valori nel DBMS.
Archiviazione dei dati in un DBMS
Formuliamo un insieme iniziale di regole di archiviazione dei dati per un DBMS:
- I dati in un DBMS sono organizzati in tabelle ( TABLE ), che sono un insieme di record.
- Tutti i record hanno gli stessi set di campi. Vengono impostati durante la creazione della tabella.
- Il campo può essere impostato su un valore predefinito ( DEFAULT ).
- Per una tabella è possibile impostare dei vincoli ( CONSTRAINT ) che descrivono i requisiti affinché i suoi dati ne garantiscano l'integrità. Questo può essere fatto nella fase di creazione della tabella ( CREATE TABLE ) o aggiunto successivamente ( ALTER TABLE ... ADD CONSTRAINT ).
- I VINCOLI più comuni :
- La chiave primaria è PRIMARIA (Id nel nostro caso).
- Campo valore univoco UNICO (VIN per la tabella veicoli).
- Verifica del campo CHECK (il valore percentuale non può essere maggiore di 100). Una delle restrizioni private su un campo è NOT NULL o NULL , che proibisce/consente di memorizzare NULL in un campo di tabella.
- Collegamento a una tabella di terze parti FOREIGN KEY (collegamento a un'azione nella tabella dei prezzi delle azioni).
- Index INDEX (indicizzazione di un campo per velocizzare la ricerca dei valori al suo interno).
- La modifica di un record ( INSERT , UPDATE ) non avverrà se i valori dei suoi campi contraddicono le restrizioni (CONSTRAINT).
- Ogni tabella può avere uno o più campi chiave che possono essere utilizzati per identificare in modo univoco un record. Tale campo (o campi, se formano una chiave composta) costituisce la chiave primaria della tabella - PRIMARY KEY .
- La chiave primaria garantisce l'unicità di un record nella tabella; su di essa viene creato un indice che dà accesso rapido all'intero record in base al valore della chiave.
- Avere una chiave primaria rende molto più semplice creare collegamenti tra tabelle. Successivamente utilizzeremo una chiave primaria artificiale: per il primo record
id = 1
, ogni record successivo verrà inserito nella tabella con il valore id aumentato di uno. Questa chiave è spesso chiamata AutoIncrement o AutoIdentity .
In realtà, una tabella di azioni:
è possibile utilizzare il nome delle azioni come chiave in questo caso? In generale sì, ma esiste la possibilità che alcune società emettano azioni diverse e le chiami solo con il proprio nome. In questo caso non ci sarà più unicità. In pratica, una chiave primaria artificiale viene utilizzata abbastanza spesso. D'accordo, l'utilizzo di un nome completo come chiave univoca in una tabella contenente record di persone non garantirà l'unicità. Oltre a utilizzare una combinazione di nome completo e data di nascita.
Tipi di dati nel DBMS
Come qualsiasi altro linguaggio di programmazione, SQL prevede la tipizzazione dei dati. Ecco i tipi di dati SQL più comuni:
Tipi interi
tipo SQL |
Sinonimi SQL |
Corrispondenza in Java |
Descrizione |
INT |
INT4,INTERO |
java.lang.Integer |
Intero a 4 byte, -2147483648 … 2147483647 |
BOOLEANO |
BOOL, BIT |
java.lang.Boolean |
Vero falso |
PICCOLOINT |
|
java.lang.Byte |
Intero a 1 byte, -128 … 127 |
PICCOLOINT |
INT2 |
java.lang.Short |
Intero a 2 byte, -32768 … 32767 |
GRANDE |
INT8 |
java.lang.Long |
Intero a 8 byte, -9223372036854775808 … 9223372036854775807 |
INCREMENTO AUTOMATICO |
INCREMENTO |
java.lang.Long |
Un contatore incrementale univoco per la tabella. Se al suo interno viene inserito un nuovo valore, questo viene incrementato di 1. I valori generati non si ripetono mai. |
Vero
tipo SQL |
Sinonimi SQL |
Corrispondenza in Java |
Descrizione |
DECIMALE(N,M) |
DIC, NUMERO |
java.math.BigDecimal |
Decimale a precisione fissa (N cifre intere e M cifre frazionarie). Progettato principalmente per lavorare con dati finanziari. |
DOPPIO |
GALLEGGIANTE8 |
java.lang.Double |
Numero reale in doppia precisione (8 byte). |
VERO |
GALLEGGIANTE4 |
java.lang.Real |
Numero reale a precisione singola (4 byte). |
Corda
tipo SQL |
Sinonimi SQL |
Corrispondenza in Java |
Descrizione |
VARCHAR(N) |
NVARCHAR |
java.lang.String |
Stringa UNICODE di lunghezza N. Lunghezza limitata a 2147483647 Carica in memoria l'intero contenuto della stringa. |
data e ora
tipo SQL |
Sinonimi SQL |
Corrispondenza in Java |
Descrizione |
TEMPO |
|
java.time.LocalTime, java.sql.Time |
Tempo di memorizzazione (fino a nanosecondi), quando si converte in DATETIME, la data viene impostata al 1 gennaio 1970. |
DATA |
|
java.time.LocalDate, java.sql.Timestamp |
Memorizzando le date nel formato aaaa-mm-gg, l'ora è impostata su 00:00 |
APPUNTAMENTO |
TIMESTAMP |
java.time.LocalDateTime, java.sql.Timestamp |
Data + ora di memorizzazione (senza tener conto dei fusi orari). |
Archiviazione di grandi volumi di dati
tipo SQL |
Corrispondenza in Java |
Descrizione |
BLOB |
java.io.InputStream, java.sql.Blob |
Memorizzazione di dati binari (immagini, file...). |
CLOB |
java.io.Reader, java.sql.Clob |
La memorizzazione di dati di testo di grandi dimensioni (libri, articoli...), a differenza di VARCHAR, carica i dati in memoria in porzioni. |
Stile di scrittura SQL
Per molte lingue esistono linee guida per la formattazione del codice. Tipicamente, tali documenti contengono regole per nominare variabili, costanti, metodi e altre strutture linguistiche. Quindi, per Python c'è PEP8, per
Java - Oracle Code Conventions for Java . Sono stati creati diversi set per SQL, leggermente diversi tra loro. Indipendentemente da ciò, dovresti sviluppare l'abitudine di seguire le regole durante la formattazione del codice, soprattutto se lavori in gruppo. Le regole potrebbero essere, ad esempio, le seguenti (ovviamente puoi sviluppare un diverso insieme di regole per te stesso, l'importante è attenervisi in futuro):
- Le parole chiave e le parole riservate, inclusi comandi e operatori, devono essere scritte in maiuscolo: CREATE TABLE, CONSTRAINT...
- I nomi di tabelle, campi e altri oggetti non devono coincidere con le parole chiave del linguaggio SQL (vedi collegamento a fine articolo), ma possono contenerle.
- I nomi delle tabelle dovrebbero riflettere il loro scopo. Sono scritti in lettere minuscole. Le parole nel nome sono separate l'una dall'altra da un trattino basso. La parola alla fine deve essere al plurale : traders (commercianti), share_rates (tasso azionario).
- I nomi dei campi della tabella dovrebbero riflettere il loro scopo. Devono essere scritti in lettere minuscole, le parole nel nome devono essere formattate in stile Camel Case e la parola alla fine deve essere utilizzata al singolare : name (nome), share_rates (tasso di partecipazione).
- I campi chiave artificiali devono contenere la parola id.
- I nomi VINCENTI devono seguire le convenzioni di denominazione delle tabelle. Devono inoltre includere i campi e le tabelle in essi coinvolti, che iniziano con un prefisso semantico: check_ (controllo del valore del campo), pk_ (chiave primaria), fk_ (chiave esterna), uniq_ (unicità del campo), idx_ (indice). Esempio: pk_traider_share_actions_id (chiave primaria nel campo id per la tabella trader_share_actions).
- E così via, mentre studi SQL, l'elenco delle regole verrà riempito/modificato.
Progettazione DBMS
Immediatamente prima di creare un DBMS, è necessario progettarlo. Lo schema finale contiene tabelle, un insieme di campi, VINCOLI, chiavi, condizioni predefinite per i campi, relazioni tra tabelle e altre entità del database. Su Internet puoi trovare molti designer gratuiti online/offline per la progettazione di piccoli DBMS. Prova a digitare qualcosa come "Database Designer gratuito" in un motore di ricerca. Tali applicazioni hanno utili proprietà aggiuntive:
- Può generare comandi SQL per creare un DBMS.
- Visualizzare visivamente le impostazioni sul diagramma.
- Consente di spostare le tabelle per una migliore visualizzazione.
- Mostra chiavi, indici, relazioni, valori predefiniti e simili nel diagramma.
- Possono archiviare in remoto lo schema DBMS.
Ad esempio,
dbdiffo.com evidenzia le chiavi, mostra i campi non vuoti e i contatori AI (AutoIncrement) con l'etichetta NN:
Creazione di tabelle in un DBMS
Quindi abbiamo un diagramma. Passiamo ora alla creazione delle tabelle (CREATE TABLE). Per fare ciò, è consigliabile disporre di dati preliminari:
- nome della tabella
- nomi e tipi di campo
- restrizioni (VINCOLI) sui campi
- valori predefiniti per i campi (se disponibili)
- chiave primaria (PRIMARY KEY) se disponibile
- connessioni tra tabelle (CHIAVE ESTERA)
Non studieremo in dettaglio tutte le opzioni del comando CREATE TABLE, esamineremo le basi di SQL usando l'esempio della creazione di una tabella per i trader:
CREATE TABLE traiders(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
freqTiсk INTEGER NOT NULL,
cash DECIMAL(15,2) NOT NULL DEFAULT 1000,
tradingMethod INTEGER NOT NULL,
changeProbability INTEGER NOT NULL DEFAULT 50,
about VARCHAR(255) NULL
);
ALTER TABLE traiders ADD CONSTRAINT check_traiders_tradingMethod
CHECK(tradingMethod IN (1,2,3));
ALTER TABLE traiders ADD CONSTRAINT check_traiders_changeProbability
CHECK(changeProbability <= 100 AND changeProbability > 0)
Diamo uno sguardo più da vicino:
CREATE TABLE traiders
(descrizione campo) - crea una tabella con il nome specificato; nella descrizione i campi sono separati da una virgola. Qualsiasi comando termina con un punto e virgola.
- La descrizione del campo inizia con il nome, seguito dal tipo, CONSTRAINT e dal valore predefinito.
id BIGINT AUTO_INCREMENT PRIMARY KEY
– il campo id di tipo intero è una chiave primaria e un contatore incrementale (per ogni nuovo record per il campo id verrà generato un valore che è uno in più rispetto a quello precedentemente creato per questa tabella).
cash DECIMAL(15,2) NOT NULL DEFAULT 1000
– campo cassa, decimale, 15 cifre prima della virgola e due dopo (dati finanziari, ad esempio, dollari e centesimi). Non è possibile accettare valori NULL. Se non viene fornito alcun valore, otterrà il valore 1000.
about VARCHAR(255) NULL
– il campo about, una stringa lunga fino a 255 caratteri, può accettare valori vuoti.
Tieni presente che possiamo impostare parte delle condizioni
CONSTRAINT dopo aver creato la tabella. Consideriamo la costruzione per modificare la struttura della tabella e i suoi campi:
ALTER TABLE nome_tabella ADD CONSTRAINT nome_vincolo CHECK (condizione) utilizzando gli esempi:
CHECK(tradingMethod IN (1,2,3))
– il campo tradingMethod può assumere solo valori 1,2,3
CHECK(changeProbability <= 100 AND changeProbability > 0)
– il campo changeProbability può assumere valori interi compresi tra 1 e 100
Relazioni tra tabelle
Per analizzare la descrizione delle relazioni tra le tabelle, diamo un'occhiata alla creazione di share_rates:
CREATE TABLE share_rates(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
operDate datetime NOT NULL,
share BIGINT NOT NULL,
rate DECIMAL(15,2) NOT NULL
);
ALTER TABLE share_rates ADD FOREIGN KEY (share) REFERENCES shares(id)
Un riferimento ai valori di un'altra tabella può essere impostato come segue:
ALTER TABLE
tabella_da_quale_riferito
ADD FOREIGN KEY
(campo_quello_riferito)
REFERENCES
tabella_a_quale_riferito (campo_che_riferito a) Lasciamo in
share abbiamo record sulle azioni, ad esempio, per id=50 memorizziamo azioni Microsoft con un prezzo iniziale di 17,5, un delta di 20 e una possibilità di variazione del 4%. Per la tabella
share_rates otteniamo tre proprietà principali:
- Dobbiamo solo memorizzare nel campo share solo il valore della chiave id della tabella share per poterlo utilizzare per ottenere le restanti informazioni (nome, ecc.) dalla tabella share.
- Non possiamo creare una tariffa per una promozione inesistente. Non è possibile inserire un valore inesistente nel campo condivisione (per il quale non esiste alcun record nella tabella condivisioni con questo id), poiché non ci sarà corrispondenza tra le tabelle.
- Non possiamo eliminare una voce azionaria nelle azioni per le quali i tassi sono impostati in share_rates.
Gli ultimi due punti servono a garantire l'integrità dei dati memorizzati. Puoi vedere la creazione di tabelle SQL della nostra emulazione ed esempi di query SQL nell'implementazione Java dei metodi delle classi corrispondenti utilizzando il collegamento al repository github alla fine dell'articolo.
La terza parte
GO TO FULL VERSION