035.3 Μάθημα 1
Πιστοποιητικό: |
Web Development Essentials |
---|---|
Έκδοση: |
1.0 |
Θέμα: |
035 Προγραμματισμός NodeJS Server |
Σκοπός: |
035.3 Βασικά Στοιχεία SQL |
Μάθημα: |
1 απο 1 |
Εισαγωγή
Αν και μπορείτε να γράψετε τα δικά σας functions για την υλοποίηση μόνιμης αποθήκευσης, μπορεί να είναι πιο βολικό να χρησιμοποιήσετε ένα σύστημα διαχείρισης βάσης δεδομένων για να επιταχύνετε την ανάπτυξη και να εξασφαλίσετε καλύτερη ασφάλεια και σταθερότητα για δεδομένα σε μορφή πίνακα. Η πιο δημοφιλής στρατηγική για την αποθήκευση δεδομένων οργανωμένων σε αλληλένδετους πίνακες, ειδικά όταν αυτοί οι πίνακες υποβάλλονται σε μεγάλο βαθμό ερωτημάτων και ενημέρωσης των δεδομένων τους, είναι η εγκατάσταση μιας σχεσιακής βάσης δεδομένων που υποστηρίζει Structured Query Language (SQL), μια γλώσσα προσανατολισμένη σε σχεσιακές βάσεις δεδομένων. Το Node.js υποστηρίζει διάφορα συστήματα διαχείρισης βάσεων δεδομένων SQL. Ακολουθώντας τις αρχές της φορητότητας και της εκτέλεσης προγραμμάτων στο user space, που υιοθετήθηκαν από το Node.js Express, το SQLite είναι η κατάλληλη επιλογή για μόνιμη αποθήκευση δεδομένων που χρησιμοποιούνται από αυτόν τον τύπο HTTP server.
SQL
Η Structured Query Language [Δομημένη Γλώσσα Ερωτημάτων] είναι συγκεκριμένη για βάσεις δεδομένων. Οι πράξεις εγγραφής και ανάγνωσης εκφράζονται σε προτάσεις που ονομάζονται statements και queries. Τόσο τα statements όσο και τα queries αποτελούνται από clauses, τα οποία ορίζουν τις προϋποθέσεις για την εκτέλεση της λειτουργίας.
Τα ονόματα και οι διευθύνσεις email, για παράδειγμα, μπορούν να αποθηκευτούν σε έναν πίνακα βάσης δεδομένων που περιέχει τα πεδία name
και email
. Μια βάση δεδομένων μπορεί να περιέχει πολλούς πίνακες, επομένως κάθε πίνακας πρέπει να έχει ένα μοναδικό όνομα. Εάν χρησιμοποιήσουμε το όνομα contacts
για τον πίνακα ονομάτων και email, μπορεί να εισαχθεί μια νέα εγγραφή με το ακόλουθο statement:
INSERT INTO contacts (name, email) VALUES ("Carol", "carol@example.com");
Αυτό το statement εισαγωγής αποτελείται από το clause INSERT INTO
, το οποίο ορίζει τον πίνακα και τα πεδία όπου θα εισαχθούν τα δεδομένα. Το δεύτερο clause, VALUES
, ορίζει τις τιμές που θα εισαχθούν. Δεν είναι απαραίτητο να γράφονται με καφαλαία τα clauses, αλλά είναι κοινή πρακτική, έτσι ώστε να αναγνωρίζονται καλύτερα οι λέξεις-κλειδιά SQL μέσα σε ένα statement ή query.
Ένα ερώτημα στον πίνακα contacts γίνεται με παρόμοιο τρόπο, αλλά χρησιμοποιώντας το clause SELECT
:
SELECT email FROM contacts;
dave@example.com
carol@example.com
Σε αυτήν την περίπτωση, το clause SELECT email
επιλέγει ένα πεδίο από τις καταχωρήσεις στον πίνακα contacts
. Το clause WHERE
περιορίζει το ερώτημα σε συγκεκριμένες σειρές:
SELECT email FROM contacts WHERE name = "Dave";
dave@example.com
Η SQL έχει πολλά άλλα clauses και θα εξετάσουμε μερικά από αυτά σε επόμενες ενότητες. Αλλά πρώτα είναι απαραίτητο να δούμε πώς να ενσωματώσετε τη βάση δεδομένων SQL με το Node.js.
SQLite
Το SQLite είναι ίσως η απλούστερη λύση για την ενσωμάτωση χαρακτηριστικών της βάσης δεδομένων SQL σε μια εφαρμογή. Σε αντίθεση με άλλα δημοφιλή συστήματα διαχείρισης βάσεων δεδομένων, το SQLite δεν είναι ένας server βάσης δεδομένων στον οποίο συνδέεται ένας πελάτης. Αντίθετα, το SQLite παρέχει ένα σύνολο λειτουργιών που επιτρέπουν στον προγραμματιστή να δημιουργήσει μια βάση δεδομένων όπως ένα συμβατικό αρχείο. Στην περίπτωση ενός HTTP server που υλοποιείται με το Node.js Express, αυτό το αρχείο βρίσκεται συνήθως στον ίδιο φάκελο με το script του server.
Πριν χρησιμοποιήσετε το SQLite στο Node.js, πρέπει να εγκαταστήσετε το module sqlite3
. Εκτελέστε την ακόλουθη εντολή στον φάκελο εγκατάστασης του server· δηλαδή, στο φάκελο που περιέχει το Node.js script που θα εκτελέσετε.
$ npm install sqlite3
Λάβετε υπόψη ότι υπάρχουν πολλά modules που υποστηρίζουν SQLite, όπως το better-sqlite3
, η χρήση των οποίων διαφέρει ελαφρώς από το sqlite3
. Τα παραδείγματα σε αυτό το μάθημα είναι για το module sqlite3
, επομένως ενδέχεται να μην λειτουργούν όπως αναμένεται, εάν επιλέξετε άλλο module.
Άνοιγμα της Βάσης Δεδομένων
Για να δείξουμε πώς ένας Node.js Express server μπορεί να λειτουργήσει με μια βάση δεδομένων SQL, ας γράψουμε ένα script που αποθηκεύει και εμφανίζει μηνύματα που αποστέλλονται από έναν client που προσδιορίζεται από ένα cookie. Τα μηνύματα αποστέλλονται από τον client μέσω της μεθόδου HTTP POST και το response του server μπορεί να μορφοποιηθεί ως JSON ή HTML (από ένα πρότυπο), ανάλογα με τη μορφή που ζητά ο client. Αυτό το μάθημα δεν θα αναφερθεί σε λεπτομέρειες σχετικά με τη χρήση μεθόδων, cookie και προτύπων HTTP. Τα αποσπάσματα κώδικα που εμφανίζονται εδώ προϋποθέτουν ότι έχετε ήδη έναν Node.js Express server όπου αυτές οι δυνατότητες είναι διαμορφωμένες και διαθέσιμες.
Ο απλούστερος τρόπος αποθήκευσης των μηνυμάτων που αποστέλλονται από τον client είναι να τα αποθηκεύσετε σε ένα global array, όπου κάθε μήνυμα που στάλθηκε προηγουμένως συσχετίζεται με ένα μοναδικό κλειδί αναγνώρισης για κάθε client. Αυτό το κλειδί μπορεί να σταλεί στον client ως cookie, το οποίο παρουσιάζεται στον server σε μελλοντικά requests για ανάκτηση των προηγούμενων μηνυμάτων του.
Ωστόσο, αυτή η προσέγγιση έχει μια αδυναμία: επειδή τα μηνύματα αποθηκεύονται μόνο σε ένα global array, όλα τα μηνύματα θα χαθούν όταν τερματιστεί το τρέχον session του server. Αυτό είναι ένα από τα πλεονεκτήματα της χρήσης βάσεων δεδομένων, επειδή τα δεδομένα αποθηκεύονται μόνιμα και δεν χάνονται εάν γίνει επανεκκίνηση του server.
Χρησιμοποιώντας το αρχείο index.js
ως το κύριο script του server, μπορούμε να ενσωματώσουμε το module sqlite3
και να υποδείξουμε το αρχείο που χρησιμεύει ως βάση δεδομένων, ως εξής:
const sqlite3 = require('sqlite3')
const db = new sqlite3.Database('messages.sqlite3');
Εάν δεν υπάρχει ήδη, το αρχείο messages.sqlite3
θα δημιουργηθεί στον ίδιο φάκελο με το αρχείο index.js
. Μέσα σε αυτό το μεμονωμένο αρχείο, θα αποθηκευτούν όλες οι δομές και τα αντίστοιχα δεδομένα. Για όλες τις λειτουργίες της βάσης δεδομένων που εκτελούνται στο script, θα μεσολαβεί η σταθερά db
, η οποία είναι το όνομα που δίνεται στο νέο sqlite3
object που ανοίγει το αρχείο messages.sqlite3
.
Δομή ενός Πίνακα
Δεν είναι δυνατή η εισαγωγή δεδομένων στη βάση δεδομένων έως ότου δημιουργηθεί τουλάχιστον ένας πίνακας. Οι πίνακες δημιουργούνται με το statement CREATE TABLE
:
db.run('CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, uuid CHAR(36), message TEXT)')
Η μέθοδος db.run()
χρησιμοποιείται για την εκτέλεση SQL statements στη βάση δεδομένων. Το ίδιο το statement γράφεται ως παράμετρος για τη μέθοδο. Αν και τα SQL statements πρέπει να τελειώνουν με ερωτηματικό όταν εισάγονται σε έναν επεξεργαστή γραμμής εντολών, το ερωτηματικό είναι προαιρετικό σε statements που μεταβιβάζονται ως παράμετροι σε ένα πρόγραμμα.
Επειδή η μέθοδος run
θα εκτελείται κάθε φορά που το script εκτελείται με το node index.js
, το SQL statement περιλαμβάνει το conditional clause IF NOT EXISTS
για την αποφυγή σφαλμάτων σε μελλοντικές εκτελέσεις όταν υπάρχει ήδη ο πίνακας messages
.
Τα πεδία που συνθέτουν τον πίνακα messages
είναι τα id
, uuid
και message
. Το πεδίο id
είναι ένας μοναδικός ακέραιος που χρησιμοποιείται για την αναγνώριση κάθε καταχώρησης στον πίνακα, επομένως δημιουργείται ως PRIMARY KEY
. Τα πρωτεύοντα κλειδιά δεν μπορούν να είναι null και δεν μπορούν να υπάρχουν δύο ίδια πρωτεύοντα κλειδιά στον ίδιο πίνακα. Επομένως, σχεδόν κάθε πίνακας SQL έχει ένα πρωτεύον κλειδί για την παρακολούθηση των περιεχομένων του πίνακα. Αν και είναι δυνατό να επιλέξετε ρητά την τιμή για το πρωτεύον κλειδί μιας νέας εγγραφής (με την προϋπόθεση ότι δεν υπάρχει ακόμη στον πίνακα), είναι βολικό το κλειδί να δημιουργείται αυτόματα. Για το σκοπό αυτό χρησιμοποιείται το flag AUTOINCREMENT
στο πεδίο id
.
Note
|
Η ρητή ρύθμιση των πρωτευόντων κλειδιών στο SQLite είναι προαιρετική, επειδή το ίδιο το SQLite δημιουργεί αυτόματα ένα πρωτεύον κλειδί. Όπως αναφέρεται στις οδηγίες του SQLite: “Στο SQLite, οι σειρές πίνακα έχουν συνήθως ένα 64-bit signed integer |
Τα πεδία uuid
και message
αποθηκεύουν το αναγνωριστικό του client και το περιεχόμενο του μηνύματος, αντίστοιχα. Ένα πεδίο τύπου CHAR(36)
αποθηκεύει μια σταθερή ποσότητα 36 χαρακτήρων και ένα πεδίο τύπου TEXT
αποθηκεύει κείμενα αυθαίρετου μήκους.
Εισαγωγή Δεδομένων
Η κύρια λειτουργία του server του παραδείγματος μας είναι να αποθηκεύει μηνύματα που συνδέονται με τον client που τα έστειλε. Ο client στέλνει το μήνυμα στο πεδίο message
στο σώμα του request που αποστέλλεται με τη μέθοδο HTTP POST. Το αναγνωριστικό του client βρίσκεται σε ένα cookie που ονομάζεται uuid
. Με αυτές τις πληροφορίες, μπορούμε να γράψουμε το Express route για την εισαγωγή νέων μηνυμάτων στη βάση δεδομένων:
app.post('/', (req, res) => {
let uuid = req.cookies.uuid
if ( uuid === undefined )
uuid = uuidv4()
// Insert new message into the database
db.run('INSERT INTO messages (uuid, message) VALUES (?, ?)', uuid, req.body.message)
// If an error occurrs, err object contains the error message.
db.all('SELECT id, message FROM messages WHERE uuid = ?', uuid, (err, rows) => {
let expires = new Date(Date.now());
expires.setDate(expires.getDate() + 30);
res.cookie('uuid', uuid, { expires: expires })
if ( req.headers.accept == "application/json" )
res.json(rows)
else
res.render('index', {title: "My messages", rows: rows})
})
})
Αυτή τη φορά, η μέθοδος db.run()
εκτελεί ένα statement εισαγωγής, αλλά σημειώστε ότι τα uuid
και req.body.message
δεν γράφονται απευθείας στη γραμμή του statement. Αντίθετα, οι τιμές αντικαταστάθηκαν με ερωτηματικά. Κάθε ερωτηματικό αντιστοιχεί σε μια παράμετρο που ακολουθεί το SQL statement στη μέθοδο db.run()
.
Η χρήση ερωτηματικών ως placeholders στο statement που εκτελείται στη βάση δεδομένων διευκολύνει το SQLite να διακρίνει μεταξύ των στατικών στοιχείων του statement και των μεταβλητών δεδομένων της. Αυτή η στρατηγική επιτρέπει στο SQLite να κάνει escape ή sanitize τα περιεχόμενα της μεταβλητής που αποτελούν μέρος του statement, αποτρέποντας μια κοινή παραβίαση ασφαλείας που ονομάζεται SQL injection. Σε αυτήν την επίθεση, οι κακόβουλοι χρήστες εισάγουν SQL statements στα δεδομένα της μεταβλητής με την ελπίδα ότι τα statements θα εκτελεστούν κατά λάθος· το sanitizing αποτρέπει την επίθεση απενεργοποιώντας επικίνδυνους χαρακτήρες στα δεδομένα.
Ερωτήματα
Όπως φαίνεται στο δείγμα κώδικα, πρόθεσή μας είναι να χρησιμοποιήσουμε το ίδιο route για να εισαγάγουμε νέα μηνύματα στη βάση δεδομένων και να δημιουργήσουμε τη λίστα των μηνυμάτων που είχαν σταλεί προηγουμένως. Η μέθοδος db.all()
επιστρέφει τη συλλογή όλων των εγγραφών στον πίνακα που ταιριάζουν με τα κριτήρια που ορίζονται στο ερώτημα.
Σε αντίθεση με τα statements που εκτελούνται από τη μέθοδο db.run()
, η μέθοδος db.all()
δημιουργεί μια λίστα εγγραφών που χειρίζονται από το function βέλους που ορίζεται στην τελευταία παράμετρο:
(err, rows) => {}
Αυτό το function, με τη σειρά του, παίρνει δύο παραμέτρους: err
και rows
. Η παράμετρος err
θα χρησιμοποιηθεί εάν παρουσιαστεί σφάλμα που εμποδίζει την εκτέλεση του ερωτήματος. Εάν επιτύχει, όλες οι εγγραφές είναι διαθέσιμες στο array rows
, όπου κάθε στοιχείο είναι ένα object που αντιστοιχεί σε μία μόνο εγγραφή από τον πίνακα. Οι ιδιότητες αυτού του object αντιστοιχούν στα ονόματα πεδίων που υποδεικνύονται στο ερώτημα: uuid
και message
.
Το array rows
είναι μια δομή δεδομένων JavaScript. Ως εκ τούτου, μπορεί να χρησιμοποιηθεί για τη δημιουργία responses με μεθόδους που παρέχονται από την Express, όπως res.json()
και res.render()
. Όταν αποδίδεται μέσα σε ένα πρότυπο EJS, ένα συμβατικό loop μπορεί να παραθέσει όλες τις εγγραφές:
<ul>
<% rows.forEach( (row) => { %>
<li><strong><%= row.id %></strong>: <%= row.message %></li>
<% }) %>
</ul>
Αντί να γεμίζετε το array rows
με όλες τις εγγραφές που επιστρέφονται από το ερώτημα, σε ορισμένες περιπτώσεις μπορεί να είναι πιο βολικό να αντιμετωπίζεται κάθε εγγραφή ξεχωριστά με τη μέθοδο db.each()
. Η σύνταξη της μεθόδου db.each()
είναι παρόμοια με τη μέθοδο db.all()
, αλλά η παράμετρος row
στο (err, row) ⇒ {}
ταιριάζει μία μόνο εγγραφή κάθε φορά.
Αλλαγή των Περιεχομένων της Βάσης Δεδομένων
Μέχρι στιγμής, ο client μπορεί μόνο να προσθέσει και να υποβάλει ερωτήματα στον server. Εφόσον ο client γνωρίζει πλέον το id
των μηνυμάτων που είχαν σταλεί προηγουμένως, μπορούμε να εφαρμόσουμε ένα function για την τροποποίηση μιας συγκεκριμένης εγγραφής. Το τροποποιημένο μήνυμα μπορεί επίσης να σταλεί σε ένα route μεθόδου HTTP POST, αλλά αυτή τη φορά με route parameter για να συλλάβει το id
που δίνεται από τον client στο request path:
app.post('/:id', (req, res) => {
let uuid = req.cookies.uuid
if ( uuid === undefined ){
uuid = uuidv4()
// 401 Unauthorized
res.sendStatus(401)
}
else {
// Update the stored message
// using named parameters
let param = {
$message: req.body.message,
$id: req.params.id,
$uuid: uuid
}
db.run('UPDATE messages SET message = $message WHERE id = $id AND uuid = $uuid', param, function(err){
if ( this.changes > 0 )
{
// A 204 (No Content) status code means the action has
// been enacted and no further information is to be supplied.
res.sendStatus(204)
}
else
res.sendStatus(404)
})
}
})
Αυτό το route δείχνει πώς να χρησιμοποιήσετε τα clauses UPDATE
και WHERE
για να τροποποιήσετε μια υπάρχουσα εγγραφή. Μια σημαντική διαφορά από τα προηγούμενα παραδείγματα είναι η χρήση των named parameters, όπου οι τιμές ομαδοποιούνται σε ένα μεμονωμένο object (param
) και μεταβιβάζονται στη μέθοδο db.run()
αντί να καθορίζεται κάθε τιμή από μόνη της. Σε αυτήν την περίπτωση, τα ονόματα πεδίων (προηγούνται από το $
) είναι οι ιδιότητες του αντικειμένου. Οι επώνυμες παράμετροι επιτρέπουν τη χρήση ονομάτων πεδίων (που προηγούνται από το $
) ως placeholders αντί για ερωτηματικά.
Ένα statement όπως αυτό στο παράδειγμα δεν θα προκαλέσει καμία τροποποίηση στη βάση δεδομένων εάν η συνθήκη που επιβάλλεται από το clause WHERE
δεν ταιριάζει με κάποια εγγραφή στον πίνακα. Για να αξιολογηθεί εάν τροποποιήθηκαν τυχόν εγγραφές από το statement, ένα callback function μπορεί να χρησιμοποιηθεί ως η τελευταία παράμετρος της μεθόδου db.run()
. Μέσα στο function, ο αριθμός των αλλαγμένων εγγραφών μπορεί να ζητηθεί από το this.changes
. Σημειώστε ότι τα functions βέλους δεν μπορούν να χρησιμοποιηθούν σε αυτήν την περίπτωση, επειδή μόνο τα κανονικά functions της μορφής function(){}
ορίζουν το object this
.
Η κατάργηση μιας εγγραφής μοιάζει πολύ με την τροποποίησή της. Μπορούμε, για παράδειγμα, να συνεχίσουμε να χρησιμοποιούμε το route parameter :id
για να προσδιορίσουμε το μήνυμα που θα διαγραφεί, αλλά αυτή τη φορά σε ένα route που επικαλείται η μέθοδος HTTP DELETE του client:
app.delete('/:id', (req, res) => {
let uuid = req.cookies.uuid
if ( uuid === undefined ){
uuid = uuidv4()
res.sendStatus(401)
}
else {
// Named parameters
let param = {
$id: req.params.id,
$uuid: uuid
}
db.run('DELETE FROM messages WHERE id = $id AND uuid = $uuid', param, function(err){
if ( this.changes > 0 )
res.sendStatus(204)
else
res.sendStatus(404)
})
}
})
Οι εγγραφές διαγράφονται από έναν πίνακα με το clause DELETE FROM
. Χρησιμοποιήσαμε ξανά το callback function για να αξιολογήσουμε πόσες καταχωρήσεις έχουν αφαιρεθεί από τον πίνακα.
Κλείσιμο της Βάσης Δεδομένων
Αφού οριστεί, το object db
μπορεί να γίνει αναφορά ανά πάσα στιγμή κατά την εκτέλεση του script, επειδή το αρχείο της βάσης δεδομένων παραμένει ανοιχτό σε όλη το τρέχον session. Δεν είναι σύνηθες να κλείνουμε τη βάση δεδομένων ενώ εκτελείται το script.
Ωστόσο, ένα function για το κλείσιμο της βάσης δεδομένων είναι χρήσιμο, για να αποφευχθεί το απότομο κλείσιμο της βάσης δεδομένων όταν τελειώσει το server process. Αν και απίθανο, ο απότομος τερματισμός της βάσης δεδομένων μπορεί να οδηγήσει σε ασυνέπειες εάν τα δεδομένα στη μνήμη δεν έχουν ακόμη δεσμευτεί στο αρχείο. Για παράδειγμα, ένας απότομος τερματισμός της βάσης δεδομένων με απώλεια δεδομένων μπορεί να προκύψει εάν το script τερματιστεί από τον χρήστη πατώντας τη συντόμευση πληκτρολογίου Ctrl+C.
Στο υποθετικό σενάριο Ctrl+C που μόλις περιγράφηκε, η μέθοδος process.on()
μπορεί να υποκλέψει σήματα που αποστέλλονται από το λειτουργικό σύστημα και να εκτελέσει έναν σωστό τερματισμό τόσο της βάσης δεδομένων όσο και του server:
process.on('SIGINT', () => {
db.close()
server.close()
console.log('HTTP server closed')
})
Η συντόμευση Ctrl+C καλεί το σήμα SIGINT
του λειτουργικού συστήματος, το οποίο τερματίζει ένα foreground πρόγραμμα στο terminal. Πριν τερματιστεί το process κατά τη λήψη του σήματος SIGINT
, το σύστημα καλεί το callback function (η τελευταία παράμετρος στη μέθοδο process.on()
). Μέσα στο callback function, μπορείτε να βάλετε οποιονδήποτε κώδικα εκκαθάρισης, ιδιαίτερα τη μέθοδο db.close()
για να κλείσετε τη βάση δεδομένων και τη server.close()
, που κλείνει με σωστό τρόπο το ίδιο το παράδειγμα Express.
Καθοδηγούμενες Ασκήσεις
-
Ποιος είναι ο σκοπός ενός πρωτεύοντος κλειδιού σε έναν πίνακα βάσης δεδομένων SQL;
-
Ποια είναι η διαφορά μεταξύ της υποβολής ερωτήματος με τη χρήση
db.all()
καιdb.each()
; -
Γιατί είναι σημαντικό να χρησιμοποιείτε placeholders και να μην συμπεριλαμβάνετε δεδομένα που αποστέλλονται απευθείας από τον client σε ένα SQL statement ή ερώτημα;
Ασκήσεις Εξερεύνησης
-
Ποια μέθοδος στο module
sqlite3
μπορεί να χρησιμοποιηθεί για την επιστροφή μόνο μιας καταχώρησης απο ένα πίνακα, ακόμα κι αν το ερώτημα ταιριάζει με πολλές καταχωρήσεις; -
Ας υποθέσουμε ότι το array
rows
μεταβιβάστηκε ως παράμετρος σε ένα callback function και περιέχει το αποτέλεσμα ενός ερωτήματος που έγινε μεdb.all()
. Πώς μπορεί ένα πεδίο που ονομάζεταιprice
, το οποίο υπάρχει στην πρώτη θέση τωνrows
, να αναφέρεται μέσα στο callback function; -
Η μέθοδος
db.run()
εκτελεί statements τροποποίησης της βάσης δεδομένων, όπως τοINSERT INTO
. Μετά την εισαγωγή μιας νέας εγγραφής σε έναν πίνακα, πώς θα μπορούσατε να ανακτήσετε το πρωτεύον κλειδί της νέας εγγραφής;
Σύνοψη
Αυτό το μάθημα καλύπτει τη βασική χρήση βάσεων δεδομένων SQL εντός των εφαρμογών Node.js Express. Το module sqlite3
προσφέρει έναν απλό τρόπο αποθήκευσης μόνιμων δεδομένων σε μια βάση δεδομένων SQLite, όπου ένα μόνο αρχείο περιέχει ολόκληρη τη βάση δεδομένων και δεν απαιτεί εξειδικευμένο server βάσης δεδομένων. Αυτό το μάθημα περιλαμβάνει τις ακόλουθες έννοιες και διαδικασίες:
-
Πώς να δημιουργήσετε μια σύνδεση βάσης δεδομένων από το Node.js.
-
Πώς να δημιουργήσετε έναν απλό πίνακα και το ρόλο των πρωτευόντων κλειδιών.
-
Χρησιμοποίηση του SQL statement
INSERT INTO
για να προσθέσετε νέα δεδομένα μέσα από το script. -
SQL ερωτήματα χρησιμοποιώντας τυπικές μεθόδους SQLite και callback functions.
-
Αλλαγή δεδομένων στη βάση δεδομένων χρησιμοποιώντας τα SQL statements
UPDATE
καιDELETE
.
Απαντήσεις στις Καθοδηγούμενες Ασκήσεις
-
Ποιος είναι ο σκοπός ενός πρωτεύοντος κλειδιού σε έναν πίνακα βάσης δεδομένων SQL;
Το πρωτεύον κλειδί είναι το μοναδικό πεδίο αναγνώρισης για κάθε εγγραφή σε έναν πίνακα βάσης δεδομένων.
-
Ποια είναι η διαφορά μεταξύ της υποβολής ερωτήματος με τη χρήση
db.all()
καιdb.each()
;Η μέθοδος
db.all()
καλεί το callback function με έναν μόνο array που περιέχει όλες τις εγγραφές που αντιστοιχούν στο ερώτημα. Η μέθοδοςdb.each()
καλεί το callback function για κάθε σειρά αποτελέσματος. -
Γιατί είναι σημαντικό να χρησιμοποιείτε placeholders και να μην συμπεριλαμβάνετε δεδομένα που αποστέλλονται απευθείας από τον client σε ένα SQL statement ή ερώτημα;
Με τα placeholders, τα δεδομένα που υποβάλλονται από τον χρήστη γίνονται escape πριν συμπεριληφθούν στο ερώτημα ή το statement. Αυτό εμποδίζει τις επιθέσεις SQL injection, όπου τα SQL statements τοποθετούνται μέσα σε μεταβλητά δεδομένα σε μια προσπάθεια να εκτελεστούν αυθαίρετες λειτουργίες στη βάση δεδομένων.
Απαντήσεις στις Ασκήσεις Εξερεύνησης
-
Ποια μέθοδος στο module
sqlite3
μπορεί να χρησιμοποιηθεί για την επιστροφή μόνο μιας καταχώρησης απο ένα πίνακα, ακόμα κι αν το ερώτημα ταιριάζει με πολλές καταχωρήσεις;Η μέθοδος
db.get()
έχει την ίδια σύνταξη με τηdb.all()
, αλλά επιστρέφει μόνο την πρώτη καταχώρηση που αντιστοιχεί στο ερώτημα. -
Ας υποθέσουμε ότι το array
rows
μεταβιβάστηκε ως παράμετρος σε ένα callback function και περιέχει το αποτέλεσμα ενός ερωτήματος που έγινε μεdb.all()
. Πώς μπορεί ένα πεδίο που ονομάζεταιprice
, το οποίο υπάρχει στην πρώτη θέση τωνrows
, να αναφέρεται μέσα στο callback function;Κάθε στοιχείο στo
rows
είναι ένα object του οποίου οι ιδιότητες αντιστοιχούν σε ονόματα πεδίων βάσης δεδομένων. Επομένως, η τιμή του πεδίουprice
στο πρώτο αποτέλεσμα είναι μέσα σεrows[0].price
. -
Η μέθοδος
db.run()
εκτελεί statements τροποποίησης της βάσης δεδομένων, όπως τοINSERT INTO
. Μετά την εισαγωγή μιας νέας εγγραφής σε έναν πίνακα, πώς θα μπορούσατε να ανακτήσετε το πρωτεύον κλειδί της νέας εγγραφής;Ένα κανονικό function του τύπου
function(){}
μπορεί να χρησιμοποιηθεί ως callback function της μεθόδουdb.run()
. Μέσα σε αυτό, η ιδιότηταthis.lastID
περιέχει την τιμή του πρωτεύοντος κλειδιού της τελευταίας εγγραφής που εισήχθη.