One place for hosting & domains

      verwenden

      So verwenden Sie Go mit MongoDB mithilfe des MongoDB Go-Treibers


      Der Autor wählte die Free Software Foundation, um eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Nachdem MongoDB sich viele Jahre lang auf von der Community entwickelte Lösungen verlassen hatte, gab MongoDB bekannt, dass sie an einem offiziellen Treiber für Go arbeiten. Im März 2019 erreichte dieser neue Treiber mit der Veröffentlichung von v1.0.0 den Status „Produktionsbereit“ und wurde seitdem kontinuierlich aktualisiert.

      Wie die anderen offiziellen MongoDB-Treiber ist der Go-Treiber für die Go-Programmiersprache typisch und bietet eine einfache Möglichkeit, MongoDB als Datenbanklösung für ein Go-Programm zu verwenden. Er ist vollständig in die MongoDB-API integriert und stellt alle Abfrage-, Indexierungs- und Aggregationsfunktionen der API sowie andere erweiterte Funktionen zur Verfügung. Im Gegensatz zu Bibliotheken von Drittanbietern wird er von MongoDB-Ingenieuren vollständig unterstützt, sodass Sie sicher sein können, dass er weiterentwickelt und gewartet wird.

      In diesem Tutorial lernen Sie den offiziellen MongoDB Go-Treiber kennen. Sie installieren den Treiber, stellen eine Verbindung zu einer MongoDB-Datenbank her und führen mehrere CRUD-Vorgänge aus. Dabei erstellen Sie ein Task-Manager-Programm zum Verwalten von Aufgaben über die Befehlszeile.

      Voraussetzungen

      Für dieses Tutorial benötigen Sie Folgendes:

      • Go auf Ihrem Computer installiert und einen Go-Arbeitsbereich, der wie folgt konfiguriert wird: Installieren von Go und Einrichten einer lokalen Programmierumgebung. In diesem Tutorial wird das Projekt als tasker bezeichnet. Sie müssen Go v1.11 oder höher auf Ihrem Computer mit aktivierten Go-Modulen installiert haben.
      • MongoDB für Ihr Betriebssystem gemäß der Installation von MongoDB installiert. MongoDB 2.6 oder höher ist die Mindestversion, die vom MongoDB Go-Treiber unterstützt wird.

      Wenn Sie Go v1.11 oder 1.12 verwenden, stellen Sie sicher, dass Go Modules aktiviert ist, indem Sie die Umgebungsvariable GO111MODULE wie folgt auf on setzen:

      Weitere Informationen zum Implementieren von Umgebungsvariablen finden Sie in diesem Tutorial zum Lesen und Festlegen von Umgebungs- und Shell-Variablen.

      Die in diesem Leitfaden gezeigten Befehle und Codes wurden mit Go v1.14.1 und MongoDB v3.6.3 getestet.

      Schritt 1 — Installieren des MongoDB Go-Treibers

      In diesem Schritt installieren Sie das Paket Go Driver für MongoDB und importieren es in Ihr Projekt. Außerdem stellen Sie eine Verbindung zu Ihrer MongoDB-Datenbank her und überprüfen den Status der Verbindung.

      Fahren Sie fort und erstellen Sie ein neues Verzeichnis für dieses Tutorial in Ihrem Dateisystem:

      Sobald Ihr Projektverzeichnis eingerichtet ist, ändern Sie es mit dem folgenden Befehl:

      Initialisieren Sie als Nächstes das Go-Projekt mit einer go.mod-Datei. Diese Datei definiert die Projektanforderungen und die Abhängigkeiten ihrer richtigen Versionen:

      Wenn Ihr Projektverzeichnis außerhalb von $GOPATH ist, müssen Sie den Importpfad für Ihr Modul wie folgt angeben:

      • go mod init github.com/<your_username>/tasker

      Nun sieht Ihre Datei go.mod wie folgt aus:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      

      Fügen Sie mit dem folgenden Befehl den MongoDB Go-Treiber als eine Abhängigkeit für Ihr Projekt hinzu:

      • go get go.mongodb.org/mongo-driver

      Sie sehen eine Ausgabe wie die folgende:

      Output

      go: downloading go.mongodb.org/mongo-driver v1.3.2 go: go.mongodb.org/mongo-driver upgrade => v1.3.2

      Nun sieht Ihre Datei go.mod wie folgt aus:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      
      require go.mongodb.org/mongo-driver v1.3.1 // indirect
      

      Erstellen Sie als Nächstes eine main.go-Datei in Ihrem Projektstamm und öffnen Sie sie in Ihrem Texteditor:

      Importieren Sie die folgenden Pakete in Ihre main.go-Datei, um mit dem Treiber zu beginnen:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      

      Hier fügen Sie die Pakete mongo und options hinzu, die der MongoDB Go-Treiber bereitstellt.

      Erstellen Sie nach Ihren Importen einen neuen MongoDB-Client und stellen Sie eine Verbindung zu Ihrem laufenden MongoDB-Server her:

      main.go

      . . .
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      mongo.Connect() akzeptiert ein Objekt Context und ein options.ClientOptions-Objekt, mit denen die Verbindungszeichenfolge und andere Treibereinstellungen festgelegt werden. In der Dokumentation zum Optionspaket finden Sie Informationen zu den verfügbaren Konfigurationsoptionen.

      Context ist wie eine Zeitüberschreitung oder eine Frist, die angibt, wann ein Vorgang nicht mehr ausgeführt und zurückgegeben werden soll. Dies hilft, Leistungseinbußen auf Produktionssystemen zu vermeiden, wenn bestimmte Vorgänge langsam ausgeführt werden. In diesem Code übergeben Sie context.TODO(), um anzuzeigen, dass Sie nicht sicher sind, welchen Kontext Sie derzeit verwenden sollen, aber Sie planen, in Zukunft einen hinzuzufügen.

      Stellen Sie als Nächstes sicher, dass Ihr MongoDB-Server mithilfe der Ping-Methode gefunden und erfolgreich verbunden wurde. Fügen Sie den folgenden Code in die init-Funktion ein:

      main.go

      . . .
          log.Fatal(err)
        }
      
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      }
      

      Wenn beim Herstellen einer Verbindung zur Datenbank Fehler auftreten, sollte das Programm abstürzen, während Sie versuchen, das Problem zu beheben, da es keinen Sinn macht, das Programm ohne aktive Datenbankverbindung auszuführen.

      Fügen Sie den folgenden Code hinzu, um eine Datenbank zu erstellen:

      main.go

      . . .
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      
        collection = client.Database("tasker").Collection("tasks")
      }
      

      Sie erstellen eine tasker-Datenbank und eine task-Sammlung, um die zu erstellenden Aufgaben zu speichern. Sie richten collection auch als Variable auf Paketebene ein, damit Sie die Datenbankverbindung im gesamten Paket wiederverwenden können.

      Speichern und schließen Sie die Datei.

      Das vollständige main.go ist nun wie folgt:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      
          err = client.Ping(ctx, nil)
          if err != nil {
              log.Fatal(err)
          }
      
          collection = client.Database("tasker").Collection("tasks")
      }
      

      Sie haben Ihr Programm eingerichtet, um über den Go-Treiber eine Verbindung zu Ihrem MongoDB-Server zu erhalten. Im nächsten Schritt erstellen Sie Ihr Task-Manager-Programm.

      Schritt 2 — Erstellen eines CLI-Programms

      In diesem Schritt installieren Sie das bekannte cli-Paket, um die Entwicklung Ihres Task-Manager-Programms zu unterstützen. Es bietet eine Schnittstelle, über die Sie schnell moderne Befehlszeilentools erstellen können. Dieses Paket bietet beispielsweise die Möglichkeit, Unterbefehle für Ihr Programm zu definieren, um eine git-ähnliche Befehlszeilenerfahrung zu erzielen.

      Führen Sie den folgenden Befehl aus, um das Paket als Abhängigkeit hinzuzufügen:

      • go get github.com/urfave/cli/v2

      Öffnen Sie als Nächstes Ihre main.go-Datei erneut:

      Fügen Sie Ihrer main.go-Datei den folgenden hervorgehobenen Code hinzu:

      main.go

      package main
      
      import (
          "context"
          "log"
          "os"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Sie importieren das cli-Paket wie erwähnt. Außerdem importieren Sie das Paket os, das Sie verwenden, um Befehlszeilenargumente an Ihr Programm zu übergeben:

      Fügen Sie nach Ihrer init-Funktion den folgenden Code hinzu, um Ihr CLI-Programm zu erstellen und den Code zu kompilieren:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:     "tasker",
              Usage:    "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{},
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Dieses Snippet erstellt ein CLI-Programm namens tasker und fügt eine kurze Verwendungsbeschreibung hinzu, die beim Ausführen des Programms ausgedruckt wird. Im Befehlsfenster fügen Sie Befehle für Ihr Programm hinzu. Der Befehl Run analysiert die Argumente auf den entsprechenden Befehl.

      Speichern und schließen Sie Ihre Datei.

      Hier ist der Befehl, den Sie zum Erstellen und Ausführen des Programms benötigen:

      Sie sehen die folgende Ausgabe:

      Output

      NAME: tasker - A simple CLI program to manage your tasks USAGE: main [global options] command [command options] [arguments...] COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false)

      Das Programm wird ausgeführt und zeigt einen Hilfetext an, in dem Sie erfahren, was das Programm kann und wie es verwendet wird.

      In den nächsten Schritten verbessern Sie die Nützlichkeit Ihres Programms, indem Sie Unterbefehle hinzufügen, um Ihre Aufgaben in MongoDB zu verwalten.

      Schritt 3 — Erstellen einer Aufgabe

      In diesem Schritt fügen Sie Ihrem CLI-Programm mithilfe des cli-Pakets einen Unterbefehl hinzu. Am Ende dieses Abschnitts können Sie Ihrer MongoDB-Datenbank eine neue Aufgabe hinzufügen, indem Sie einen neuen add-Befehl in Ihrem CLI-Programm verwenden.

      Öffnen Sie zunächst Ihre main.go-Datei:

      Importieren Sie als Nächstes die Pakete go.mongodb.org/mongo-driver/bson/primitive, time und errors:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Erstellen Sie dann eine neue Struktur, um eine einzelne Aufgabe in der Datenbank darzustellen, und fügen Sie sie unmittelbar vor der Hauptfunktion ein:

      main.go

      . . .
      type Task struct {
          ID        primitive.ObjectID `bson:"_id"`
          CreatedAt time.Time          `bson:"created_at"`
          UpdatedAt time.Time          `bson:"updated_at"`
          Text      string             `bson:"text"`
          Completed bool               `bson:"completed"`
      }
      . . .
      

      Sie verwenden das primitive Paket, um den Typ der ID jeder Aufgabe festzulegen, da MongoDB standardmäßig ObjectIDs für das Feld _id verwendet. Ein weiteres Standardverhalten von MongoDB besteht darin, dass der Feldname in Kleinbuchstaben als Schlüssel für jedes exportierte Feld verwendet wird, wenn es serialisiert wird. Dies kann jedoch mithilfe von bson struct-Tags geändert werden.

      Erstellen Sie als Nächstes eine Funktion, die eine Instanz der Aufgabe empfängt und in der Datenbank speichert. Fügen Sie dieses Snippet nach der Hauptfunktion hinzu:

      main.go

      . . .
      func createTask(task *Task) error {
          _, err := collection.InsertOne(ctx, task)
        return err
      }
      . . .
      

      Die Methode collection.InsertOne() fügt die bereitgestellte Aufgabe in die Datenbanksammlung ein und gibt die ID des eingefügten Dokuments zurück. Da Sie diese ID nicht benötigen, verwerfen Sie sie, indem Sie sie dem Unterstrichoperator zuweisen.

      Der nächste Schritt besteht darin, Ihrem Task-Manager-Programm einen neuen Befehl zum Erstellen neuer Aufgaben hinzuzufügen. Nennen wir ihn add:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Jeder neue Befehl, der Ihrem CLI-Programm hinzugefügt wird, wird im Fenster Befehle platziert. Jeder besteht aus einem Namen, einer Verwendungsbeschreibung und einer Aktion. Dies ist der Code, der bei der Befehlsausführung ausgeführt wird.

      In diesem Code sammeln Sie das erste add-Argument und verwenden es, um die Texteigenschaft einer neuen Aufgabeninstanz festzulegen, während Sie die entsprechenden Standardeinstellungen für die anderen Eigenschaften zuweisen. Die neue Aufgabe wird anschließend an createTask weitergeleitet, die die Aufgabe in die Datenbank einfügt und nil zurückgibt, wenn alles gut geht und der Befehl beendet wird.

      Speichern und schließen Sie Ihre Datei.

      Testen Sie es, indem Sie mit dem Befehl add einige Aufgaben hinzufügen. Bei Erfolg werden keine Fehler auf Ihrem Bildschirm angezeigt:

      • go run main.go add "Learn Go"
      • go run main.go add "Read a book"

      Nachdem Sie nun erfolgreich Aufgaben hinzufügen können, implementieren wir eine Möglichkeit, alle Aufgaben anzuzeigen, die Sie der Datenbank hinzugefügt haben.

      Schritt 4 — Auflisten aller Aufgaben

      Das Auflisten der Dokumente in einer Sammlung kann mit der Methode collection.Find() erfolgen, die einen Filter sowie einen Zeiger auf einen Wert erwartet, in den das Ergebnis dekodiert werden kann. Der Rückgabewert ist ein Cursor, der eine Reihe an Dokumenten bereitstellt, die einzeln durchlaufen und dekodiert werden können. Der Cursor wird dann geschlossen, sobald er erschöpft ist.

      Öffnen Sie Ihre main.go-Datei:

      Stellen Sie sicher, dass das Paket bson importiert wird:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Erstellen Sie dann unmittelbar nach createTask die folgenden Funktionen:

      main.go

      . . .
      func getAll() ([]*Task, error) {
        // passing bson.D{{}} matches all documents in the collection
          filter := bson.D{{}}
          return filterTasks(filter)
      }
      
      func filterTasks(filter interface{}) ([]*Task, error) {
          // A slice of tasks for storing the decoded documents
          var tasks []*Task
      
          cur, err := collection.Find(ctx, filter)
          if err != nil {
              return tasks, err
          }
      
          for cur.Next(ctx) {
              var t Task
              err := cur.Decode(&t)
              if err != nil {
                  return tasks, err
              }
      
              tasks = append(tasks, &t)
          }
      
          if err := cur.Err(); err != nil {
              return tasks, err
          }
      
        // once exhausted, close the cursor
          cur.Close(ctx)
      
          if len(tasks) == 0 {
              return tasks, mongo.ErrNoDocuments
          }
      
          return tasks, nil
      }
      

      Mit BSON (Binary-coded JSON) werden Dokumente in einer MongoDB-Datenbank dargestellt, und das bson-Paket hilft uns bei der Arbeit mit BSON-Objekten in Go. Der in der Funktion getAll() verwendete Typ bson.D stellt ein BSON-Dokument dar und wird dort verwendet, wo die Reihenfolge der Eigenschaften von Bedeutung ist. Indem Sie bson.D{{}} als Filter an filterTasks() übergeben, geben Sie an, dass Sie mit allen Dokumenten in der Sammlung übereinstimmen möchten.

      In der Funktion filterTasks() iterieren Sie über den von der collection.Find()-Methode zurückgegebenen Cursor und dekodieren jedes Dokument in eine Instanz der Aufgabe. Jede Aufgabe wird dann an den zu Beginn der Funktion erstellten Aufgabenbereich angehängt. Sobald der Cursor erschöpft ist, wird er geschlossen und das Aufgabenfenster zurückgegeben.

      Bevor Sie einen Befehl zum Auflisten aller Aufgaben erstellen, erstellen wir eine Hilfsfunktion, die einen Teil der Aufgaben übernimmt und in die Standardausgabe druckt. Sie verwenden das Paket color, um die Ausgabe zu färben.

      Bevor Sie dieses Paket verwenden können, installieren Sie es mit:

      • go get gopkg.in/gookit/color.v1

      Sie sehen die folgende Ausgabe:

      Output

      go: downloading gopkg.in/gookit/color.v1 v1.1.6 go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

      Und importieren Sie sie zusammen mit dem fmt-Paket in Ihre main.go-Datei:

      main.go

      package main
      
      import (
          "context"
          "errors"
        "fmt"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
          "gopkg.in/gookit/color.v1"
      )
      . . .
      

      Erstellen Sie als Nächstes eine neue printTasks-Funktion, die Ihrer Hauptfunktion folgt:

      main.go

      . . .
      func printTasks(tasks []*Task) {
          for i, v := range tasks {
              if v.Completed {
                  color.Green.Printf("%d: %sn", i+1, v.Text)
              } else {
                  color.Yellow.Printf("%d: %sn", i+1, v.Text)
              }
          }
      }
      . . .
      

      Diese printTasks-Funktion übernimmt eine Reihe von Aufgaben, durchläuft jede einzelne und druckt sie in der Standardausgabe aus. Dabei wird die grüne Farbe verwendet, um abgeschlossene Aufgaben anzuzeigen, und gelb für unvollständige Aufgaben.

      Fahren Sie fort und fügen Sie die folgenden hervorgehobenen Zeilen hinzu, um einen neuen all-Befehl im Fenster Befehle zu erstellen. Dieser Befehl druckt alle hinzugefügten Aufgaben in die Standardausgabe:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Der Befehl all ruft alle in der Datenbank vorhandenen Aufgaben ab und druckt sie in die Standardausgabe. Wenn keine Aufgaben vorhanden sind, wird stattdessen eine Eingabeaufforderung zum Hinzufügen einer neuen Aufgabe gedruckt.

      Speichern und schließen Sie Ihre Datei.

      Erstellen und führen Sie Ihr Programm mit dem Befehl all aus:

      Er wird alle Aufgaben, die Sie bisher hinzugefügt haben, auflisten:

      Output

      1: Learn Go 2: Read a book

      Nachdem Sie nun alle Aufgaben in der Datenbank anzeigen können, können Sie im nächsten Schritt die Möglichkeit hinzufügen, eine Aufgabe als erledigt zu markieren.

      Schritt 5 — Abschließen einer Aufgabe

      In diesem Schritt erstellen Sie einen neuen Unterbefehl namens done, mit dem Sie eine vorhandene Aufgabe in der Datenbank als erledigt markieren können. Um eine Aufgabe als abgeschlossen zu markieren, können Sie die Methode collection.FindOneAndUpdate() verwenden. Sie ermöglicht es Ihnen, ein Dokument in einer Sammlung zu lokalisieren und einige oder alle seine Eigenschaften zu aktualisieren. Diese Methode erfordert einen Filter zum Auffinden des Dokuments und ein Aktualisierungsdokument zum Beschreiben des Vorgangs. Beide werden mit den Typen bson.D erstellt.

      Beginnen Sie durch Öffnen Ihrer main.go-Datei:

      Fügen Sie den folgenden Snippet nach Ihrer Funktion filterTasks ein:

      main.go

      . . .
      func completeTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          update := bson.D{primitive.E{Key: "$set", Value: bson.D{
              primitive.E{Key: "completed", Value: true},
          }}}
      
          t := &Task{}
          return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
      }
      . . .
      

      Die Funktion entspricht dem ersten Dokument, in dem die Texteigenschaft dem Textparameter entspricht. Das Dokument update gibt an, dass die Eigenschaft completed auf true gesetzt wird. Wenn beim Vorgang FindOneAndUpdate() ein Fehler auftritt, wird dieser von completeTask() zurückgegeben. Andernfalls wird nil zurückgegeben.

      Als Nächstes fügen wir Ihrem CLI-Programm einen neuen done-Befehl hinzu, der eine Aufgabe als erledigt markiert:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Sie verwenden das an den Befehl done übergebene Argument, um das erste Dokument zu finden, dessen Texteigenschaft übereinstimmt. Wenn es gefunden wurde, wird die Eigenschaft completed im Dokument auf true gesetzt.

      Speichern und schließen Sie Ihre Datei.

      Führen Sie dann Ihr Programm mit dem Befehl done aus:

      • go run main.go done "Learn Go"

      Wenn Sie den Befehl all erneut verwenden, werden Sie feststellen, dass die als erledigt markierte Aufgabe jetzt grün gedruckt wird.

      Screenshot der Terminalausgabe nach dem Ausführen einer Aufgabe

      Manchmal möchten Sie nur Aufgaben anzeigen, die noch nicht erledigt sind. Wir fügen diese Eigenschaft als Nächstes hinzu.

      Schritt 6 — Nur ausstehende Aufgaben anzeigen

      In diesem Schritt integrieren Sie einen Code zum Abrufen ausstehender Aufgaben aus der Datenbank mithilfe des MongoDB-Treibers. Ausstehende Aufgaben sind Aufgaben, deren Eigenschaft completed auf true gesetzt ist.

      Lassen Sie uns eine neue Funktion hinzufügen, die Aufgaben abruft, die noch nicht abgeschlossen sind. Öffnen Sie Ihre main.go-Datei:

      Fügen Sie dann dieses Snippet nach der Funktion completeTask hinzu:

      main.go

      . . .
      func getPending() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: false},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Sie erstellen einen Filter mit den Paketen bson und primitive aus dem MongoDB-Treiber, der mit Dokumenten übereinstimmt, deren Eigenschaft completed auf true gesetzt ist. Das Segment ausstehender Aufgaben wird dann an den Anrufer zurückgegeben.

      Anstatt einen neuen Befehl zum Auflisten ausstehender Aufgaben zu erstellen, sollten Sie ihn zur Standardaktion machen, wenn Sie das Programm ohne Befehle ausführen. Sie können dies tun, indem Sie dem Programm eine Action-Eigenschaft wie folgt hinzufügen:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
      . . .
      

      Die Action-Eigenschaft führt eine Standardaktion aus, wenn das Programm ohne Unterbefehle ausgeführt wird. Hier wird eine Logik für das Auflisten ausstehender Aufgaben platziert. Die Funktion getPending() wird aufgerufen und die resultierenden Aufgaben werden mit printTasks() in die Standardausgabe gedruckt. Wenn keine ausstehenden Aufgaben vorhanden sind, wird stattdessen eine Eingabeaufforderung angezeigt, in der der Benutzer aufgefordert wird, mit dem Befehl add eine neue Aufgabe hinzuzufügen.

      Speichern und schließen Sie Ihre Datei.

      Wenn Sie das Programm jetzt ausführen, ohne Befehle hinzuzufügen, werden alle ausstehenden Aufgaben in der Datenbank aufgelistet:

      Sie sehen die folgende Ausgabe:

      Output

      1: Read a book

      Nachdem Sie unvollständige Aufgaben aufgelistet haben, fügen wir einen weiteren Befehl hinzu, mit dem Sie nur abgeschlossene Aufgaben anzeigen können.

      Schritt 7 — Anzeigen von abgeschlossenen Aufgaben

      In diesem Schritt fügen Sie einen neuen Unterbefehl finished hinzu, der erledigte Aufgaben aus der Datenbank abruft und auf dem Bildschirm anzeigt. Dies beinhaltet das Filtern und Zurückgeben von Aufgaben, deren Eigenschaft completed auf true gesetzt ist.

      Öffnen Sie Ihre main.go-Datei:

      Fügen Sie dann am Ende Ihrer Datei den folgenden Code hinzu:

      main.go

      . . .
      func getFinished() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: true},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Ähnlich wie bei der Funktion getPending() haben Sie eine Funktion getFinished() hinzugefügt, die einen Teil der abgeschlossenen Aufgaben zurückgibt. In diesem Fall hat der Filter die Eigenschaft completed auf true gesetzt, sodass nur die Dokumente zurückgegeben werden, die dieser Bedingung entsprechen.

      Erstellen Sie als Nächstes einen Befehl finished, der alle erledigten Aufgaben druckt:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Der Befehl finished ruft Aufgaben ab, deren Eigenschaft completed über die hier erstellte Funktion getFinished() auf true gesetzt ist. Anschließend werden sie an die Funktion printTasks übergeben, sodass sie in der Standardausgabe gedruckt werden.

      Speichern und schließen Sie Ihre Datei.

      Führen Sie den folgenden Befehl aus:

      Sie sehen die folgende Ausgabe:

      Output

      1: Learn Go

      Im letzten Schritt geben Sie Benutzern die Option, Aufgaben aus der Datenbank zu löschen.

      Schritt 8 — Löschen einer Aufgabe

      In diesem Schritt fügen Sie einen neuen Unterbefehl delete hinzu, um Benutzern zu ermöglichen, eine Aufgabe aus der Datenbank zu löschen. Um eine einzelne Aufgabe zu löschen, verwenden Sie die Methode collection.DeleteOne() vom MongoDB-Treiber. Außerdem stützt er sich auf einen Filter, der dem Dokument entspricht, um das Dokument zu löschen.

      Öffnen Sie Ihre main.go-Datei erneut:

      Fügen Sie diese Funktion deleteTask hinzu, um Aufgaben direkt nach Ihrer Funktion getFinished aus der Datenbank zu löschen:

      main.go

      . . .
      func deleteTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          res, err := collection.DeleteOne(ctx, filter)
          if err != nil {
              return err
          }
      
          if res.DeletedCount == 0 {
              return errors.New("No tasks were deleted")
          }
      
          return nil
      }
      . . .
      

      Diese deleteTask-Methode verwendet ein Zeichenfolgenargument, das das zu löschende Aufgabenelement darstellt. Ein Filter wird so erstellt, dass er mit dem Aufgabenelement übereinstimmt, dessen Texteigenschaft auf das Zeichenfolgenargument festgelegt ist. Sie übergeben den Filter an die DeleteOne()-Methode, die dem Element in der Auflistung entspricht, und löschen es.

      Sie können die DeletedCount-Eigenschaft für das Ergebnis der DeleteOne-Methode überprüfen, um zu bestätigen, ob Dokumente gelöscht wurden. Wenn der Filter nicht mit einem zu löschenden Dokument übereinstimmen kann, ist DeletedCount Null und Sie können in diesem Fall einen Fehler zurückgeben.

      Fügen Sie nun einen neuen Befehl rm wie hervorgehoben hinzu:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:  "rm",
                      Usage: "deletes a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          err := deleteTask(text)
                          if err != nil {
                              return err
                          }
      
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Wie bei allen anderen zuvor hinzugefügten Unterbefehlen verwendet der Befehl rm sein erstes Argument, um eine Aufgabe in der Datenbank abzugleichen und zu löschen.

      Speichern und schließen Sie Ihre Datei.

      Sie können ausstehende Aufgaben auflisten, indem Sie Ihr Programm ausführen, ohne Unterbefehle zu übergeben:

      Output

      1: Read a book

      Wenn Sie den Unterbefehl rm für die Aufgabe „Buch lesen“ ausführen, wird er aus der Datenbank gelöscht:

      • go run main.go rm "Read a book"

      Wenn Sie alle ausstehenden Aufgaben erneut auflisten, werden Sie feststellen, dass die Aufgabe „Buch lesen“ nicht mehr angezeigt wird, sondern stattdessen eine Aufforderung zum Hinzufügen einer neuen Aufgabe:

      Output

      Nothing to see here Run `add 'task'` to add a task

      In diesem Schritt haben Sie eine Funktion hinzugefügt, um Aufgaben aus der Datenbank zu löschen.

      Zusammenfassung

      Sie haben erfolgreich ein Task-Manager-Befehlszeilenprogramm erstellt und dabei die Grundlagen der Verwendung des MongoDB Go-Treibers erlernt.

      Lesen Sie unbedingt die vollständige Dokumentation zum MongoDB Go-Treiber bei GoDoc, um mehr über die Funktionen zu erfahren, die die Verwendung des Treibers bietet. Die Dokumentation, in der die Verwendung von Aggregationen oder Transaktionen beschrieben wird, könnte für Sie von besonderem Interesse sein.

      Der endgültige Code für dieses Tutorial kann in diesem GitHub repo betrachtet werden.



      Source link

      Verwenden von Puffern in Node.js.


      Der Autor hat den COVID-19 Relief Fund dazu ausgewählt, eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Ein Puffer ist ein Speicherplatz (normalerweise RAM), in dem Binärdaten gespeichert werden. In Node.js können wir mit der integrierten Puffer-Klasse auf diese Speicherbereiche zugreifen. Puffer speichern eine Folge von Ganzzahlen, ähnlich einem Array in JavaScript. Im Gegensatz zu Arrays können Sie die Größe eines Puffers nach seiner Erstellung nicht mehr ändern.

      Möglicherweise haben Sie implizit Puffer verwendet, wenn Sie bereits Node.js-Code geschrieben haben. Wenn Sie beispielsweise mit fs.readFile() aus einer Datei lesen, sind die an den Rückruf oder das Versprechen zurückgegebenen Daten ein Pufferobjekt. Wenn HTTP-Anforderungen in Node.js gestellt werden, geben sie außerdem Datenströme zurück, die vorübergehend in einem internen Puffer gespeichert sind, wenn der Client den Stream nicht auf einmal verarbeiten kann.

      Puffer sind nützlich, wenn Sie mit Binärdaten interagieren, normalerweise auf niedrigeren Netzwerkebenen. Sie bieten Ihnen auch die Möglichkeit, feinkörnige Datenmanipulationen in Node.js durchzuführen.

      In diesem Tutorial verwenden Sie Node.js REPL, um verschiedene Beispiele für Puffer durchzugehen, z. B. das Erstellen von Puffern, das Lesen aus Puffern, das Schreiben in und das Kopieren aus Puffern sowie die Verwendung von Puffern zum Konvertieren zwischen binären und codierten Daten. Am Ende des Tutorials haben Sie gelernt, wie Sie mit der Puffer-Klasse mit Binärdaten arbeiten.

      Voraussetzungen

      Schritt 1 — Erstellen eines Puffers

      Dieser erste Schritt zeigt Ihnen die beiden wichtigsten Möglichkeiten zum Erstellen eines Pufferobjekts in Node.js.

      Um zu entscheiden, welche Methode verwendet werden soll, müssen Sie die folgende Frage beantworten: Möchten Sie einen neuen Puffer erstellen oder einen Puffer aus vorhandenen Daten extrahieren? Wenn Sie Daten im Speicher speichern möchten, die Sie noch nicht empfangen haben, sollten Sie einen neuen Puffer erstellen. In Node.js verwenden wir dazu die Funktion alloc() der Puffer-Klasse.

      Öffnen wir die Node.js REPL, um uns selbst davon zu überzeugen. Geben Sie in Ihrem Terminal den Befehl node ein:

      Sie sehen, dass die Eingabeaufforderung mit > beginnt.

      Die Funktion alloc() verwendet die Größe des Puffers als erstes und einziges erforderliches Argument. Die Größe ist eine Ganzzahl, die angibt, wie viele Speicherbytes das Pufferobjekt verwendet. Wenn wir beispielsweise einen Puffer mit einer Größe von 1 KB (Kilobyte) erstellen möchten, der 1024 Byte entspricht, geben Sie dies in die Konsole ein:

      • const firstBuf = Buffer.alloc(1024);

      Um einen neuen Puffer zu erstellen, haben wir die global verfügbare Puffer-Klasse verwendet, die über die Methode alloc() verfügt. Durch die Angabe von 1024 als Argument für alloc() haben wir einen Puffer mit einer Größe von 1 KB erstellt.

      Wenn Sie einen Puffer mit alloc() initialisieren, wird der Puffer standardmäßig mit binären Nullen als Platzhalter für festgelegte Datenmengen gefüllt. Wir können dennoch den Standardwert ändern, wenn wir möchten. Wenn wir einen neuen Puffer mit Einsen anstelle von Nullen erstellen möchten, setzen wir den zweiten Parameter der Funktion alloc()fill.

      Erstellen Sie in Ihrem Terminal an der REPL-Eingabeaufforderung einen neuen Puffer, der mit Einsen gefüllt ist:

      • const filledBuf = Buffer.alloc(1024, 1);

      Wir haben gerade ein neues Pufferobjekt erstellt, das auf einen Speicherplatz im Speicher verweist, in dem 1 KB Einsen gespeichert sind. Obwohl wir eine Ganzzahl eingegeben haben, sind alle in einem Puffer gespeicherten Daten Binärdaten.

      Binärdaten können in vielen verschiedenen Formaten vorliegen. Betrachten wir beispielsweise eine Binärsequenz, die ein Datenbyte darstellt: 01110110. Wenn diese Binärsequenz eine Zeichenfolge in Englisch unter Verwendung des ASCII-Codierungsstandards darstellen würde, wäre dies der Buchstabe v. Wenn unser Computer jedoch ein Bild verarbeitet, könnte diese Binärsequenz Informationen über die Farbe eines Pixels enthalten.

      Der Computer kann sie unterschiedlich verarbeiten, da die Bytes unterschiedlich codiert sind. Die Bytecodierung ist das Format des Bytes. Ein Puffer in Node.js verwendet standardmäßig das UTF-8-Codierungsschema, wenn er mit Zeichenfolgendaten initialisiert wird. Ein Byte in UTF-8 repräsentiert eine Zahl, einen Buchstaben (in Englisch und in anderen Sprachen) oder ein Symbol. UTF-8 ist eine Obermenge von ASCII, dem amerikanischen Standardcode für den Informationsaustausch. ASCII kann Bytes mit englischen Groß- und Kleinbuchstaben, den Zahlen 0-9 und einigen anderen Symbolen wie dem Ausrufezeichen (!) codieren oder mit dem kaufmännischen Und-Zeichen (&).

      Wenn wir ein Programm schreiben würden, das nur mit ASCII-Zeichen arbeiten könnte, könnten wir die von unserem Puffer verwendete Codierung mit dem dritten Argument der Funktion alloc() ändern – der Codierung.

      Erstellen wir einen neuen Puffer, der fünf Byte lang ist und nur ASCII-Zeichen speichert:

      • const asciiBuf = Buffer.alloc(5, 'a', 'ascii');

      Der Puffer wird mit fünf Bytes des Zeichens a unter Verwendung der ASCII-Darstellung initialisiert.

      Hinweis: Node.js unterstützt standardmäßig die folgenden Zeichencodierungen:

      • ASCII, dargestellt als ascii
      • UTF-8, dargestellt als utf-8 oder utf8
      • UTF-16, dargestellt als utf-16le oder utf16le
      • UCS-2, dargestellt als ucs-2 oder ucs2
      • Base64, dargestellt als base64
      • Hexadezimal, dargestellt als hex
      • ISO/IEC 8859-1, dargestellt als latin1 oder binär

      Alle diese Werte können in Pufferklassenfunktionen verwendet werden, die einen Codierungsparameter akzeptieren. Daher sind diese Werte alle für die Methode alloc() gültig.

      Bisher haben wir mit der Funktion alloc() neue Puffer erstellt. Manchmal möchten wir jedoch einen Puffer aus bereits vorhandenen Daten erstellen, z. B. einer Zeichenfolge oder einer Anordnung.

      Um einen Puffer aus bereits vorhandenen Daten zu erstellen, verwenden wir die Methode from(). Wir können diese Funktion verwenden, um Puffer zu erstellen aus:

      • Eine Anordnung von Ganzzahlen: die ganzzahligen Werte können zwischen 0 und 255 liegen.
      • Ein ArrayBuffer: Dies ist ein JavaScript-Objekt, das eine festgesetzte Länge von Bytes speichert.
      • Eine Zeichenfolge.
      • Ein weiterer Puffer.
      • Andere JavaScript-Objekte mit einer Symbol.toPrimitive-Eigenschaft. Diese Eigenschaft teilt JavaScript mit, wie das Objekt in einen primitiven Datentyp konvertiert werden soll: Boolescher Wert, Nullwert, undefiniert, Zahl, Zeichenfolge oder Symbol. Weitere Informationen zu Symbolen finden Sie in der JavaScript-Dokumentation von Mozilla.

      Sehen wir uns an, wie wir einen Puffer aus einer Zeichenfolge erstellen können. Geben Sie in der Node.js Folgendes ein:

      • const stringBuf = Buffer.from('My name is Paul');

      Wir haben jetzt ein Pufferobjekt aus der Zeichenfolge My name is Paul​​​ erstellt. Erstellen wir einen neuen Puffer aus einem anderen Puffer, den wir zuvor erstellt haben:

      • const asciiCopy = Buffer.from(asciiBuf);

      Wir haben jetzt einen neuen Puffer asciiCopy erstellt, der dieselben Daten wie asciiBuf enthält.

      Nachdem wir die Erstellung von Puffern erlebt haben, können wir uns mit Beispielen zum Lesen ihrer Daten befassen.

      Schritt 2 – Lesen aus einem Puffer

      Es gibt viele Möglichkeiten, auf Daten in einem Puffer zuzugreifen. Wir können auf ein einzelnes Byte in einem Puffer zugreifen oder den gesamten Inhalt extrahieren.

      Um auf ein Byte eines Puffers zuzugreifen, übergeben wir den Index oder die Position des gewünschten Bytes. Puffer speichern Daten sequentiell wie Arrays. Sie indizieren ihre Daten auch wie Arrays, beginnend bei 0. Wir können die Array-Notation für das Pufferobjekt verwenden, um ein einzelnes Byte zu erhalten.

      Sehen wir uns an, wie dies aussieht, indem Sie einen Puffer aus einer Zeichenfolge in der REPL erstellen:

      • const hiBuf = Buffer.from('Hi!');

      Lesen wir nun das erste Byte des Puffers:

      Wenn Sie ENTER drücken, zeigt die REPL Folgendes:

      Output

      72

      Die Ganzzahl 72 entspricht der UTF-8-Darstellung für den Buchstaben H.

      Hinweis: Die Werte für Bytes können Zahlen zwischen 0 und 255 sein. Ein Byte ist eine Folge von 8 Bits. Ein Bit ist binär und kann daher nur einen von zwei Werten haben: 0 oder 1. Wenn wir eine Folge von 8 Bits und zwei mögliche Werte pro Bit haben, haben wir maximal 2⁸ mögliche Werte für ein Byte. Das führt zu einer maximalen Größe von 256 Werten. Da wir ab Null zählen, bedeutet dies, dass unsere höchste Zahl 255 ist.

      Machen wir dasselbe für das zweite Byte. Geben Sie Folgendes in die REPL ein:

      Die REPL gibt 105 zurück, was den Kleinbuchstaben i darstellt.

      Wir erhalten schließlich das dritte Zeichen:

      In der REPL wird 33 angezeigt, was ! entspricht.

      Versuchen wir, ein Byte aus einem ungültigen Index abzurufen:

      Die REPL wird Folgendes ausgeben:

      Output

      undefined

      Dies ist genau so, als hätten wir versucht, auf ein Element in einer Anordnung mit einem falschen Index zuzugreifen.

      Nachdem wir nun gesehen haben, wie einzelne Bytes eines Puffers gelesen werden, sehen wir uns unsere Optionen zum gleichzeitigen Abrufen aller in einem Puffer gespeicherten Daten an. Das Pufferobjekt wird mit den Methoden toString() und toJSON() bereitgestellt, die den gesamten Inhalt eines Puffers in zwei verschiedenen Formaten zurückgeben.

      Wie der Name schon sagt, konvertiert die toString()-Methode die Bytes des Puffers in eine Zeichenfolge und gibt sie an den Benutzer zurück. Wenn wir diese Methode auf hiBuf verwenden, erhalten wir die Zeichenfolge Hi!. Versuchen wir es!

      Geben Sie in der Eingabeaufforderung Folgendes ein:

      Die REPL wird Folgendes ausgeben:

      Output

      'Hi!'

      Dieser Puffer wurde aus einer Zeichenfolge erstellt. Sehen wir uns an, was passiert, wenn wir toString() in einem Puffer verwenden, der nicht aus Zeichenfolgendaten hergestellt wurde.

      Erstellen wir einen neuen, leeren Puffer, der 10 Bytes groß ist:

      • const tenZeroes = Buffer.alloc(10);

      Verwenden wir nun die toString()-Methode:

      Wir sehen das folgende Ergebnis:

      'u0000u0000u0000u0000u0000u0000u0000u0000u0000u0000'
      

      Die Zeichenfolge u0000 ist das Unicode-Zeichen für NULL. Sie entspricht der Zahl 0. Wenn die Daten des Puffers nicht als Zeichenfolge codiert sind, gibt die Methode toString() die UTF-8-Codierung der Bytes zurück.

      toString() verfügt über einen optionalen Parameter, die Codierung. Mit diesem Parameter können wir die Codierung der zurückgegebenen Pufferdaten ändern.

      Wenn Sie beispielsweise die hexadezimale Codierung für hiBuf wünschen, geben Sie bei der Eingabeaufforderung Folgendes ein:

      Diese Aussage wird folgendermaßen bewertet:

      Output

      '486921'

      486921 ist die hexadezimale Darstellung für die Bytes, die die Zeichenfolge Hi! darstellen. Wenn Benutzer in Node.js die Codierung von Daten von einem Formular in ein anderes konvertieren möchten, legen sie die Zeichenfolge normalerweise in einen Puffer und rufen toString() mit der gewünschten Codierung auf.

      Die toJSON()-Methode verhält sich anders. Unabhängig davon, ob der Puffer aus einer Zeichenfolge erstellt wurde oder nicht, werden die Daten immer als ganzzahlige Darstellung des Bytes zurückgegeben.

      Lassen Sie uns die Puffer hiBuf und tenZeroes erneut verwenden, um die Verwendung von toJSON() zu üben. Geben Sie bei der Eingabeaufforderung Folgendes ein:

      Die REPL wird Folgendes ausgeben:

      Output

      { type: 'Buffer', data: [ 72, 105, 33 ] }

      Das JSON-Objekt verfügt über eine type-Eigenschaft, die immer Puffer ist. Auf diese Weise können Programme diese JSON-Objekte von anderen JSON-Objekten unterscheiden.

      Die Daten-Eigenschaft enthält eine Anordnung der ganzzahligen Darstellung der Bytes. Möglicherweise haben Sie bemerkt, dass 72, 105 und 33 den Werten entsprechen, die wir beim einzelnen Abrufen der Bytes erhalten haben.

      Versuchen wir die toJSON()-Methode mit tenZeroes:

      In der REPL sehen Sie Folgendes:

      Output

      { type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }

      Der Typ ist der gleiche wie zuvor angegeben. Die Daten sind jedoch jetzt eine Anordnung mit zehn Nullen.

      Nachdem wir uns nun mit den wichtigsten Möglichkeiten zum Lesen aus einem Puffer befasst haben, schauen wir uns an, wie wir den Inhalt eines Puffers ändern.

      Schritt 3 — Modifizieren eines Puffers

      Es gibt viele Möglichkeiten, um ein bestehendes Pufferobjekt zu ändern. Ähnlich wie beim Lesen können wir Pufferbytes mithilfe der Array-Syntax einzeln ändern. Wir können auch neue Inhalte in einen Puffer schreiben und die vorhandenen Daten ersetzen.

      Lassen Sie uns zunächst untersuchen, wie wir einzelne Bytes eines Puffers ändern können. Erinnern Sie sich an unsere Puffervariable hiBuf, die den String Hi! enthält. Lassen Sie uns jedes Byte so ändern, dass es stattdessen Hey enthält.

      Versuchen wir in der REPL zunächst, das zweite Element von hiBuf auf e zu setzen:

      Lassen Sie uns diesen Puffer nun als Zeichenfolge betrachten, um zu bestätigen, dass die richtigen Daten gespeichert werden. Rufen Sie anschließend die toString()-Methode auf:

      Sie wird bewertet als:

      Output

      'Hu0000!'

      Wir haben diese seltsame Ausgabe erhalten, weil der Puffer nur einen ganzzahligen Wert akzeptieren kann. Wir können ihn nicht dem Buchstaben e zuordnen; vielmehr müssen wir ihm die Zahl zuweisen, deren binäres Äquivalent e darstellt:

      Wenn wir nun die toString()-Methode aufrufen:

      In der REPL erhalten wir diese Ausgabe:

      Output

      'He!'

      Um das letzte Zeichen im Puffer zu ändern, müssen wir das dritte Element auf die Ganzzahl setzen, die dem Byte für y entspricht:

      Lassen Sie uns dies noch einmal mit der toString()-Methode bestätigen:

      Ihre REPL zeigt:

      Output

      'Hey'

      Wenn wir versuchen, ein Byte zu schreiben, das außerhalb des Bereichs des Puffers liegt, wird es ignoriert und der Inhalt des Puffers ändert sich nicht. Versuchen wir beispielsweise, das nicht vorhandene vierte Element des Puffers auf o zu setzen:

      Wir können bestätigen, dass der Puffer mit der toString()-Methode unverändert bleibt:

      Die Ausgabe ist immer noch:

      Output

      'Hey'

      Wenn wir den Inhalt des gesamten Puffers ändern möchten, können wir die write()-Methode verwenden. Die write()-Methode akzeptiert eine Zeichenfolge, die den Inhalt eines Puffers ersetzt.

      Verwenden wir die write()-Methode, um den Inhalt von hiBuf wieder zu Hi! zu ändern. Geben Sie in Ihrer Node.js-Shell bei der Eingabeaufforderung den folgenden Befehl ein:

      Die write()-Methode hat 3 in der REPL zurückgegeben. Dies liegt daran, dass drei Datenbytes geschrieben wurden. Jeder Buchstabe hat eine Byte-Größe, da dieser Puffer eine UTF-8-Codierung verwendet, bei der für jedes Zeichen ein Byte verwendet wird. Wenn der Puffer eine UTF-16-Codierung verwendet hätte, die mindestens zwei Bytes pro Zeichen enthält, hätte die Funktion write() 6 zurückgegeben.

      Überprüfen Sie nun den Inhalt des Puffers mit toString():

      Die REPL wird Folgendes ausgeben:

      Output

      'Hi!'

      Dies ist schneller, als jedes Element byteweise ändern zu müssen.

      Wenn Sie versuchen, mehr Bytes als die Größe eines Puffers zu schreiben, akzeptiert das Pufferobjekt nur, welche Bytes passen. Erstellen wir zur Veranschaulichung einen Puffer, in dem drei Bytes gespeichert sind:

      • const petBuf = Buffer.alloc(3);

      Versuchen wir nun, Cats darauf zu schreiben:

      Wenn der Aufruf von write() ausgewertet wird, gibt die REPL 3 zurück und zeigt an, dass nur drei Bytes in den Puffer geschrieben wurden. Bestätigen Sie nun, dass der Puffer die ersten drei Bytes enthält:

      Die REPL gibt Folgendes aus:

      Output

      'Cat'

      Die Funktion write() fügt die Bytes in sequentieller Reihenfolge hinzu, sodass nur die ersten drei Bytes in den Puffer gestellt wurden.

      Im Gegensatz dazu erstellen wir einen Puffer, der vier Bytes speichert:

      • const petBuf2 = Buffer.alloc(4);

      Schreiben Sie den gleichen Inhalt darauf:

      Fügen Sie dann einige neue Inhalte hinzu, die weniger Platz beanspruchen als der ursprüngliche Inhalt:

      Da Puffer nacheinander ab 0 schreiben, wenn wir den Inhalt des Puffers drucken:

      Wir würden begrüßt werden mit:

      Output

      'Hits'

      Die ersten beiden Zeichen werden überschrieben, der Rest des Puffers bleibt jedoch unberührt.

      Manchmal befinden sich die Daten, die wir in unserem bereits vorhandenen Puffer haben möchten, nicht in einer Zeichenfolge, sondern in einem anderen Pufferobjekt. In diesen Fällen können wir die copy()-Funktion verwenden, um zu ändern, was unser Puffer speichert.

      Erstellen wir zwei neue Puffer:

      • const wordsBuf = Buffer.from('Banana Nananana');
      • const catchphraseBuf = Buffer.from('Not sure Turtle!');

      Die Puffer wordsBuf und catchphraseBuf enthalten beide Zeichenfolgendaten. Wir möchten catchphraseBuf so ändern, dass es Nananana Turtle! speichert statt Not sure Turtle!​​​. Wir werden copy() verwenden, um Nananana von wordsBuf zu catchphraseBuf zu bekommen.

      Um Daten von einem Puffer in den anderen zu kopieren, verwenden wir die copy()-Methode für den Puffer, der die Informationsquelle darstellt. Da wordsBuf die zu kopierenden Zeichenfolgendaten enthält, müssen wir diese wie folgt kopieren:

      • wordsBuf.copy(catchphraseBuf);

      Der Zielparameter ist in diesem Fall der catchphraseBuf-Puffer.

      Wenn wir das in die REPL eingeben, gibt es 15 zurück, was anzeigt, dass 15 Bytes geschrieben wurden. Die Zeichenfolge Nananana verwendet nur 8 Datenbytes, sodass wir sofort wissen, dass unsere Kopie nicht wie beabsichtigt verlaufen ist. Verwenden Sie die toString()-Methode, um den Inhalt von catchphraseBuf anzuzeigen:

      • catchphraseBuf.toString();

      Die REPL gibt Folgendes aus:

      Output

      'Banana Nananana!'

      Standardmäßig hat copy() den gesamten Inhalt von wordsBuf übernommen und in catchphraseBuf abgelegt. Wir müssen selektiver für unser Ziel sein und nur Nananana kopieren. Lassen Sie uns den ursprünglichen Inhalt von catchphraseBuf neu schreiben, bevor wir fortfahren:

      • catchphraseBuf.write('Not sure Turtle!');

      Die Funktion copy() verfügt über einige weitere Parameter, mit denen wir anpassen können, welche Daten in den anderen Puffer kopiert werden. Hier ist eine Liste aller Parameter dieser Funktion:

      • target – Dies ist der einzige erforderliche Parameter von copy(). Wie wir aus unserer vorherigen Verwendung gesehen haben, ist dies der Puffer, in den wir kopieren möchten.
      • targetStart – Dies ist der Index der Bytes im Zielpuffer, in den mit dem Kopieren begonnen werden soll. Standardmäßig ist es 0, d. h. es werden Daten kopiert, die am Anfang des Puffers beginnen.
      • sourceStart – Dies ist der Index der Bytes im Quellpuffer, aus denen kopiert werden soll.
      • sourceEnd – Dies ist der Index der Bytes im Quellpuffer, in den das Kopieren beendet werden soll. Standardmäßig ist es die Länge des Puffers.

      Um Nananana aus wordsBuf in catchphraseBuf zu kopieren, sollte unser Ziel catchphraseBuf wie zuvor sein. Der targetStart wäre 0, da Nananana am Anfang von catchphraseBuf erscheinen soll. Der sourceStart sollte 7 sein, da dies der Index ist, in dem Nananana in wordsBuf beginnt. Das sourceEnd würde weiterhin die Länge der Puffer sein.

      Kopieren Sie bei der REPL-Eingabeaufforderung den Inhalt von wordsBuf folgendermaßen:

      • wordsBuf.copy(catchphraseBuf, 0, 7, wordsBuf.length);

      Die REPL bestätigt, dass 8 Bytes geschrieben wurden. Beachten Sie, wie wordsBuf.length als Wert für den Parameter sourceEnd verwendet wird. Wie bei Arrays gibt uns die Eigenschaft length die Größe des Puffers an.

      Nun sehen wir uns den Inhalt von catchphraseBuf an:

      • catchphraseBuf.toString();

      Die REPL gibt Folgendes aus:

      Output

      'Nananana Turtle!'

      Erfolg! Wir konnten die Daten von catchphraseBuf ändern, indem wir den Inhalt von wordsBuf kopiert haben.

      Sie können die Node.js REPL beenden, wenn Sie dies möchten. Beachten Sie, dass alle erstellten Variablen nicht mehr verfügbar sind, wenn Sie Folgendes tun:

      Zusammenfassung

      In diesem Tutorial haben Sie gelernt, dass Puffer Zuordnungen fester Länge im Speicher sind, in denen Binärdaten gespeichert werden. Sie haben zuerst Puffer erstellt, indem Sie ihre Größe im Speicher definiert und sie mit bereits vorhandenen Daten initialisiert haben. Anschließend lasen Sie Daten aus einem Puffer, indem Sie die einzelnen Bytes untersucht und die Methoden toString() und toJSON() verwendet haben. Schließlich haben Sie die von einem Puffer gespeicherten Daten geändert, indem Sie die einzelnen Bytes geändert und die Methoden write() und copy() verwendet haben.

      Puffer geben Ihnen einen guten Einblick, wie Binärdaten von Node.js manipuliert werden. Jetzt, da Sie mit Puffern interagieren können, können Sie die verschiedenen Auswirkungen der Zeichenkodierung auf die Speicherung von Daten beobachten. Sie können beispielsweise Puffer aus Zeichenfolgendaten erstellen, die keine UTF-8- oder ASCII-Codierung aufweisen, und deren Größenunterschied beobachten. Sie können auch einen Puffer mit UTF-8 und toString() verwenden, um ihn in andere Codierungsschemata zu konvertieren.

      Informationen zu Puffern in Node.js finden Sie in der Node.js-Dokumentation zum Puffer-Objekt. Wenn Sie Node.js weiter lernen möchten, können Sie zur Codieren in Node-Reihe zurückkehren oder Programmierprojekte und -einstellungen auf unserer Node-Themenseite durchsuchen.



      Source link

      Verwenden der PDO-PHP-Erweiterung zur Durchführung von MySQL-Transaktionen in PHP unter Ubuntu 18.04


      Der Autor wählte die Kooperation Open Sourcing Mental Illness, um eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Eine MySQL-Transaktion ist eine Gruppe von logisch zusammenhängenden SQL-Befehlen, die in der Datenbank als eine einzige Einheit ausgeführt werden. Transaktionen werden verwendet, um die ACID (Atomicity, Consistency, Isolation und Durability)-Konformität in einer Anwendung durchzusetzen. Dabei handelt es sich um eine Reihe von Standards, die die Zuverlässigkeit der Verarbeitungsvorgänge in einer Datenbank regeln.

      Atomarität (Atomicity) gewährleistet den Erfolg zusammengehöriger Transaktionen oder einen vollständigen Ausfall, wenn ein Fehler auftritt. Konsistenz (Consistency) garantiert die Gültigkeit der an die Datenbank übermittelten Daten gemäß der definierten Geschäftslogik. Isolierung (Isolation) ist die korrekte Ausführung von gleichzeitigen Transaktionen, wobei sichergestellt wird, dass die Auswirkungen verschiedener Clients, die sich mit einer Datenbank verbinden, sich nicht gegenseitig beeinflussen. Dauerhaftigkeit (Durability) stellt sicher, dass logisch zusammenhängende Transaktionen dauerhaft in der Datenbank verbleiben.

      Über eine Transaktion ausgegebene SQL-Anweisungen sollten entweder erfolgreich sein oder ganz fehlschlagen. Wenn eine der Abfragen fehlschlägt, macht MySQL die Änderungen rückgängig und sie werden niemals in die Datenbank übertragen.

      Ein gutes Beispiel zum Verständnis der Funktionsweise von MySQL-Transaktionen ist eine E-Commerce-Website. Wenn ein Kunde eine Bestellung aufgibt, fügt die Anwendung je nach Geschäftslogik Datensätze in mehrere Tabellen ein, wie beispielsweise: orders und orders_products. Mehrtabellen-Datensätze, die sich auf eine einzelne Bestellung beziehen, müssen als eine einzige logische Einheit atomar an die Datenbank gesendet werden.

      Ein weiterer Anwendungsfall ist in einer Bankanwendung. Wenn ein Kunde Geld überweist, werden einige Transaktionen an die Datenbank gesendet. Das Konto des Senders wird belastet und dem Konto der Partei des Empfängers gutgeschrieben. Die beiden Transaktionen müssen gleichzeitig bestätigt werden. Wenn eine von ihnen fehlschlägt, kehrt die Datenbank in ihren ursprünglichen Zustand zurück, und es sollten keine Änderungen auf der Festplatte gespeichert werden.

      In diesem Tutorial werden Sie die PDO-PHP-Erweiterung verwenden, die eine Schnittstelle für die Arbeit mit Datenbanken in PHP bietet, um MySQL-Transaktionen auf einem Ubuntu 18.04-Server durchzuführen.

      Voraussetzungen

      Bevor Sie beginnen, benötigen Sie Folgendes:

      Schritt 1 — Erstellen einer Beispieldatenbank und von Tabellen

      Als Erstes erstellen Sie eine Beispieldatenbank und fügen einige Tabellen hinzu, bevor Sie mit MySQL-Transaktionen zu arbeiten beginnen. Melden Sie sich zunächst als root bei Ihrem MySQL-Server an:

      Wenn Sie dazu aufgefordert werden, geben Sie Ihr MySQL-Root-Passwort ein und drücken Sie ENTER, um fortzufahren. Erstellen Sie dann eine Datenbank. Für die Zwecke dieses Tutorials werden wir die Datenbank sample_store nennen:

      • CREATE DATABASE sample_store;

      Sie sehen die folgende Ausgabe:

      Output

      Query OK, 1 row affected (0.00 sec)

      Erstellen Sie einen Benutzer namens sample_user für Ihre Datenbank. Denken Sie daran, PASSWORD durch einen starken Wert zu ersetzen:

      • CREATE USER 'sample_user'@'localhost' IDENTIFIED BY 'PASSWORD';

      Erteilen Sie Ihrem Benutzer uneingeschränkte Berechtigungen für die Datenbank sample_store:

      • GRANT ALL PRIVILEGES ON sample_store.* TO 'sample_user'@'localhost';

      Laden Sie abschließend die MySQL-Berechtigungen neu:

      Sobald Sie Ihren Benutzer angelegt haben, sehen Sie die folgende Ausgabe:

      Output

      Query OK, 0 rows affected (0.01 sec) . . .

      Mit der vorhandenen Datenbank und dem Benutzer können Sie jetzt mehrere Tabellen erstellen, um zu demonstrieren, wie MySQL-Transaktionen funktionieren.

      Melden Sie sich vom MySQL-Server ab:

      Sobald das System Sie abmeldet, sehen Sie die folgende Ausgabe:

      Output

      Bye.

      Melden Sie sich dann mit den Anmeldeinformationen des Benutzers sample_user an, den Sie gerade angelegt haben:

      • sudo mysql -u sample_user -p

      Geben Sie das Passwort für den sample_user ein und drücken Sie ENTER, um fortzufahren.

      Wechseln Sie zu sample_store, um sie zur aktuell ausgewählten Datenbank zu machen:

      Sobald die Datenbank ausgewählt ist, sehen Sie die folgende Ausgabe:

      Output

      Database Changed.

      Erstellen Sie als Nächstes eine Tabelle products:

      • CREATE TABLE products (product_id BIGINT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(50), price DOUBLE) ENGINE = InnoDB;

      Mit diesem Befehl wird eine Tabelle products mit einem Feld namens product_id erstellt. Verwenden Sie einen Datentyp BIGINT, der einen großen Wert von bis zu 2^63-1 aufnehmen kann. Sie verwenden dasselbe Feld als PRIMARY KEY zur eindeutigen Identifizierung von Produkten. Das Schlüsselwort AUTO_INCREMENT weist MySQL an, den nächsten numerischen Wert zu erzeugen, wenn neue Produkte eingefügt werden.

      Das Feld product_name ist vom Typ VARCHAR und kann bis zu 50 Buchstaben oder Zahlen enthalten. Für den Produktpreis (price) verwenden Sie einen Datentyp DOUBLE, um Fließkommaformate in Preisen mit Dezimalzahlen zu berücksichtigen.

      Zuletzt verwenden Sie die InnoDB als ENGINE, weil sie im Gegensatz zu anderen Speicher-Engines wie MyISAM MySQL-Transaktionen komfortabel unterstützt.

      Sobald Sie Ihre Tabelle products erstellt haben, erhalten Sie die folgende Ausgabe:

      Output

      Query OK, 0 rows affected (0.02 sec)

      Fügen Sie als Nächstes einige Artikel zur Tabelle products hinzu, indem Sie die folgenden Befehle ausführen:

      • INSERT INTO products(product_name, price) VALUES ('WINTER COAT','25.50');
      • INSERT INTO products(product_name, price) VALUES ('EMBROIDERED SHIRT','13.90');
      • INSERT INTO products(product_name, price) VALUES ('FASHION SHOES','45.30');
      • INSERT INTO products(product_name, price) VALUES ('PROXIMA TROUSER','39.95');

      Nach jeder Operation INSERT sehen Sie eine Ausgabe ähnlich der folgenden:

      Output

      Query OK, 1 row affected (0.02 sec) . . .

      Überprüfen Sie dann, ob die Daten der Tabelle products hinzugefügt wurden:

      Sie sehen eine Liste mit den vier von Ihnen eingefügten Produkten:

      Output

      +------------+-------------------+-------+ | product_id | product_name | price | +------------+-------------------+-------+ | 1 | WINTER COAT | 25.5 | | 2 | EMBROIDERED SHIRT | 13.9 | | 3 | FASHION SHOES | 45.3 | | 4 | PROXIMA TROUSER | 39.95 | +------------+-------------------+-------+ 4 rows in set (0.01 sec)

      Als Nächstes erstellen Sie eine Tabelle customers, die grundlegende Informationen über Kunden enthält:

      • CREATE TABLE customers (customer_id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(50) ) ENGINE = InnoDB;

      Wie in der Tabelle products verwenden Sie für die customer_id den Datentyp BIGINT, wodurch sichergestellt wird, dass die Tabelle viele Kunden mit bis zu 2^63-1 Datensätzen unterstützt. Das Schlüsselwort AUTO_INCREMENT erhöht den Wert der Spalten, sobald Sie einen neuen Kunden einfügen.

      Da die Spalte customer_name alphanumerische Werte akzeptiert, verwenden Sie den Datentyp VARCHAR mit einer Beschränkung von 50 Zeichen. Auch hier verwenden Sie die InnoDB Storage ENGINE zur Unterstützung von Transaktionen.

      Nachdem Sie den vorherigen Befehl zum Anlegen der Tabelle customers ausgeführt haben, sehen Sie die folgende Ausgabe:

      Output

      Query OK, 0 rows affected (0.02 sec)

      Sie fügen der Tabelle drei Musterkunden hinzu. Führen Sie die folgenden Befehle aus:

      • INSERT INTO customers(customer_name) VALUES ('JOHN DOE');
      • INSERT INTO customers(customer_name) VALUES ('ROE MARY');
      • INSERT INTO customers(customer_name) VALUES ('DOE JANE');

      Sobald die Kunden hinzugefügt wurden, sehen Sie eine Ausgabe ähnlich der folgenden:

      Output

      Query OK, 1 row affected (0.02 sec) . . .

      Überprüfen Sie dann die Daten in der Tabelle customers:

      Sie sehen eine Liste mit den drei Kunden:

      Output

      +-------------+---------------+ | customer_id | customer_name | +-------------+---------------+ | 1 | JOHN DOE | | 2 | ROE MARY | | 3 | DOE JANE | +-------------+---------------+ 3 rows in set (0.00 sec)

      Anschließend erstellen Sie eine Tabelle orders, um die von verschiedenen Kunden aufgegebenen Bestellungen aufzuzeichnen. Führen Sie den folgenden Befehl aus, um die Tabelle orders zu erstellen:

      • CREATE TABLE orders (order_id BIGINT AUTO_INCREMENT PRIMARY KEY, order_date DATETIME, customer_id BIGINT, order_total DOUBLE) ENGINE = InnoDB;

      Verwenden Sie die Spalte order_id als PRIMARY KEY. Der Datentyp BIGINT ermöglicht Ihnen die Aufnahme von bis zu 2^63-1 Bestellungen und wird nach jeder Bestellungseinfügung automatisch erhöht. Das Feld order_date enthält das tatsächliche Datum und die tatsächliche Uhrzeit der Bestellung, daher verwenden Sie den Datentyp DATETIME. Die customer_id bezieht sich auf die zuvor von Ihnen erstellte Tabelle customers.

      Sie sehen die folgende Ausgabe:

      Output

      Query OK, 0 rows affected (0.02 sec)

      Da die Bestellung eines einzelnen Kunden mehrere Artikel enthalten kann, müssen Sie eine Tabelle orders_products erstellen, um diese Informationen zu speichern.

      Führen Sie den folgenden Befehl aus, um die Tabelle orders_products zu erstellen:

      • CREATE TABLE orders_products (ref_id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT, product_id BIGINT, price DOUBLE, quantity BIGINT) ENGINE = InnoDB;

      Verwenden Sie die ref_id als PRIMARY KEY, der nach jeder Einfügung eines Datensatzes automatisch erhöht wird. Die order_id und product_id beziehen sich auf die Tabelle orders und products. Die Spalte price ist vom Datentyp DOUBLE, um gleitende Werte aufzunehmen.

      Die Speicher-Engine InnoDB muss mit den zuvor erstellten Tabellen übereinstimmen, da sich die Bestellung eines einzelnen Kunden unter Verwendung von Transaktionen gleichzeitig auf mehrere Tabellen auswirkt.

      Ihre Ausgabe bestätigt die Erstellung der Tabelle:

      Output

      Query OK, 0 rows affected (0.02 sec)

      Sie werden vorerst keine Daten zu den Tabellen orders und orders_products hinzufügen. Dies geschieht jedoch später mit einem PHP-Skript, das MySQL-Transaktionen implementiert.

      Melden Sie sich vom MySQL-Server ab:

      Ihr Datenbankschema ist nun vollständig und Sie haben es mit einigen Datensätzen gefüllt. Nun werden Sie eine PHP-Klasse für die Handhabung von Datenbankverbindungen und MySQL-Transaktionen erstellen.

      Schritt 2 – Entwerfen einer PHP-Klasse zur Abwicklung von MySQL-Transaktionen

      In diesem Schritt erstellen Sie eine PHP-Klasse, die PDO (PHP Data Objects) zur Abwicklung von MySQL-Transaktionen verwendet. Die Klasse wird eine Verbindung zu Ihrer MySQL-Datenbank herstellen und Daten atomar in die Datenbank einfügen.

      Speichern Sie die Klassendatei im Stammverzeichnis Ihres Apache-Webservers. Erstellen Sie dazu mit Ihrem Texteditor eine Datei DBTransaction.php:

      • sudo nano /var/www/html/DBTransaction.php

      Fügen Sie dann den folgenden Code in die Datei ein: Ersetzen Sie PASSWORD mit dem in Schritt 1 erstellten Wert:

      /var/www/html/DBTransaction.php

      <?php
      
      class DBTransaction
      {
          protected $pdo;
          public $last_insert_id;
      
          public function __construct()
          {
              define('DB_NAME', 'sample_store');
              define('DB_USER', 'sample_user');
              define('DB_PASSWORD', 'PASSWORD');
              define('DB_HOST', 'localhost');
      
              $this->pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
              $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
              $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
          }
      

      Zu Beginn der Klasse DBTransaction verwendet das PDO die Konstanten (DB_HOST, DB_NAME, DB_USER und DB_PASSWORD) zur Initialisierung und Verbindung mit der Datenbank, die Sie in Schritt 1 erstellt haben.

      Anmerkung: Da wir hier MySQL-Transaktionen in kleinem Maßstab demonstrieren, haben wir die Datenbankvariable in der Klasse DBTransaction deklariert. In einem großen Produktionsprojekt würden Sie normalerweise eine separate Konfigurationsdatei erstellen und die Datenbankkonstanten aus dieser Datei mit Hilfe einer PHP-Anweisung require_once laden.

      Als Nächstes legen Sie zwei Attribute für die Klasse PDO fest:

      • ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION: Dieses Attribut weist PDO an, eine Ausnahme auszulösen, wenn ein Fehler auftritt. Solche Fehler können zum Debugging protokolliert werden.
      • ATTR_EMULATE_PREPARES, false: Diese Option deaktiviert die Emulation vorbereiteter Anweisungen und erlaubt der MySQL-Datenbank-Engine, die Anweisungen selbst vorzubereiten.

      Fügen Sie nun den folgenden Code in Ihre Datei ein, um die Methoden für Ihre Klasse zu erstellen:

      /var/www/html/DBTransaction.php

      . . .
          public function startTransaction()
          {
              $this->pdo->beginTransaction();
          }
      
          public function insertTransaction($sql, $data)
          {
              $stmt = $this->pdo->prepare($sql);
              $stmt->execute($data);
              $this->last_insert_id = $this->pdo->lastInsertId();
          }
      
          public function submitTransaction()
          {
              try {
                  $this->pdo->commit();
              } catch(PDOException $e) {
                  $this->pdo->rollBack();
                  return false;
              }
      
                return true;
          }
      }
      

      Speichern und schließen Sie die Datei, indem Sie STRG + X, Y, dann ENTER drücken.

      Um mit MySQL-Transaktionen zu arbeiten, erstellen Sie in der Klasse DBTransaction drei Hauptmethoden: startTransaction, insertTransaction und submitTransaction.

      • startTransaction: Diese Methode weist PDO an, eine Transaktion zu starten, und schaltet Auto-Commit aus, bis ein Commit-Befehl ausgegeben wird.

      • insertTransaction: Diese Methode benötigt zwei Argumente. Die Variable $sql enthält die auszuführende SQL-Anweisung, während die Variable $data ein Array mit Daten ist, die an die SQL-Anweisung gebunden werden sollen, da Sie vorbereitete Anweisungen verwenden. Die Daten werden als Array an die Methode insertTransaction übergeben.

      • submitTransaction: Diese Methode bindet die Änderungen dauerhaft an die Datenbank, indem Sie einen Befehl commit() ausgibt. Wenn jedoch ein Fehler auftritt und die Transaktionen ein Problem haben, ruft die Methode die Methode rollBack() auf, um die Datenbank in ihren ursprünglichen Zustand zurückzuversetzen, falls eine PDO-Ausnahme ausgelöst wird.

      Ihre Klasse DBTransaction initialisiert eine Transaktion, bereitet die verschiedenen auszuführenden SQL-Befehle vor, und bindet schließlich die Änderungen atomar in die Datenbank ein, wenn es keine Probleme gibt. Andernfalls wird die Transaktion rückgängig gemacht. Darüber hinaus ermöglicht Ihnen die Klasse, den Datensatz order_id abzurufen, den Sie gerade durch Zugriff auf die öffentliche Eigenschaft last_insert_id erstellt haben.

      Die Klasse DBTransaction ist nun bereit, von jedem PHP-Code, den Sie als Nächstes erstellen, aufgerufen und verwendet zu werden.

      Schritt 3 – Erstellen eines PHP-Skripts zur Verwendung der Klasse DBTransaction

      Sie erstellen ein PHP-Skript, das die Klasse DBTransaction implementiert und eine Gruppe von SQL-Befehlen in die MySQL-Datenbank sendet. Sie imitieren den Workflow einer Kundenbestellung in einem Online-Einkaufswagen.

      Diese SQL-Abfragen wirken sich auf die Tabellen orders und orders_products aus. Ihre Klasse DBTransaction sollte nur Änderungen in der Datenbank zulassen, wenn alle Abfragen fehlerfrei ausgeführt werden. Andernfalls erhalten Sie einen Fehler zurück, und alle Änderungsversuche werden rückgängig gemacht.

      Sie erstellen eine einzelne Bestellung für den mit customer_id 1 identifizierten Kunden JOHN DOE. Die Bestellung des Kunden enthält drei verschiedene Artikel mit unterschiedlichen Mengen aus der Tabelle products. Ihr PHP-Skript nimmt die Bestelldaten des Kunden und übergibt sie an die Klasse DBTransaction.

      Erstellen Sie die Datei orders.php:

      • sudo nano /var/www/html/orders.php

      Fügen Sie dann den folgenden Code in die Datei ein:

      /var/www/html/orders.php

      <?php
      
      require("DBTransaction.php");
      
      $db_host = "database_host";
      $db_name = "database_name";
      $db_user = "database_user";
      $db_password = "PASSWORD";
      
      $customer_id = 2;
      
      $products[] = [
        'product_id' => 1,
        'price' => 25.50,
        'quantity' => 1
      ];
      
      $products[] = [
        'product_id' => 2,
        'price' => 13.90,
        'quantity' => 3
      ];
      
      $products[] = [
        'product_id' => 3,
        'price' => 45.30,
        'quantity' => 2
      ];
      
      $transaction = new DBTransaction($db_host, $db_user, $db_password, $db_name);
      

      Sie haben ein PHP-Skript erstellt, das eine Instanz der Klasse DBTransaction initialisiert, die Sie in Schritt 2 erstellt haben.

      In diesem Skript fügen Sie die Datei DBTransaction.php ein und initialisieren die Klasse DBTransaction. Als Nächstes bereiten Sie ein mehrdimensionales Array aller Produkte vor, die der Kunde im Geschäft bestellt. Sie rufen auch die Methode startTransaction() auf, um eine Transaktion zu starten.

      Anschließend fügen Sie den folgenden Code hinzu, um Ihr Skript orders.php zu vervollständigen:

      /var/www/html/orders.php

      . . .
      $order_query = "insert into orders (order_id, customer_id, order_date, order_total) values(:order_id, :customer_id, :order_date, :order_total)";
      $product_query = "insert into orders_products (order_id, product_id, price, quantity) values(:order_id, :product_id, :price, :quantity)";
      
      $transaction->insertQuery($order_query, [
        'customer_id' => $customer_id,
        'order_date' => "2020-01-11",
        'order_total' => 157.8
      ]);
      
      $order_id = $transaction->last_insert_id;
      
      foreach ($products as $product) {
        $transaction->insertQuery($product_query, [
          'order_id' => $order_id,
          'product_id' => $product['product_id'],
          'price' => $product['price'],
          'quantity' => $product['quantity']
        ]);
      }
      
      $result = $transaction->submit();
      
      if ($result) {
          echo "Records successfully submitted";
      } else {
          echo "There was an error.";
      }
      
      

      Speichern und schließen Sie die Datei, indem Sie STRG + X, Y, dann ENTER drücken.

      Bereiten Sie den Befehl vor, der über die Methode insertTransaction in die Tabelle orders eingefügt werden soll. Danach rufen Sie den Wert der öffentlichen Eigenschaft last_insert_id aus der Klasse DBTransaction ab und verwenden ihn als $order_id.

      Sobald Sie über eine $order_id verfügen, verwenden Sie die eindeutige ID, um die von dem Kunden bestellten Artikel in die Tabelle orders_products einzufügen.

      Anschließend rufen Sie die Methode submitTransaction auf, um die gesamten Bestelldetails des Kunden in die Datenbank zu übertragen, wenn es keine Probleme gibt. Andernfalls macht die Methode submitTransaction die versuchten Änderungen rückgängig.

      Jetzt führen Sie das Skript orders.php in Ihrem Browser aus. Führen Sie das Folgende aus und ersetzen Sie your-server-IP mit der öffentlichen IP-Adresse Ihres Servers:

      http://your-server-IP/orders.php

      Sie erhalten eine Bestätigung, dass die Datensätze erfolgreich übermittelt wurden.

      PHP-Ausgabe aus der MySQL-Klasse Transactions

      Ihr PHP-Skript funktioniert wie erwartet und die Bestellung zusammen mit den zugehörigen Bestellprodukten wurde atomar in die Datenbank übertragen.

      Sie haben die Datei orders.php in einem Browserfenster ausgeführt. Das Skript hat die Klasse DBTransaction aufgerufen, die ihrerseits die Details der orders in die Datenbank übermittelte. Im nächsten Schritt überprüfen Sie, ob die Datensätze in den Bezugstabellen der Datenbank gespeichert wurden.

      Schritt 4 — Bestätigen der Einträge in Ihrer Datenbank

      In diesem Schritt überprüfen Sie, ob die vom Browserfenster aus initiierte Transaktion für die Bestellung des Kunden wie erwartet in die Datenbanktabellen verbucht wurde.

      Dazu melden Sie sich erneut bei Ihrer MySQL-Datenbank an:

      • sudo mysql -u sample_user -p

      Geben Sie das Passwort für den sample_user ein und drücken Sie ENTER, um fortzufahren.

      Wechseln Sie zur Datenbank sample_store:

      Stellen Sie sicher, dass die Datenbank geändert wird, bevor Sie fortfahren, indem Sie die folgende Ausgabe bestätigen:

      Output

      Database Changed.

      Geben Sie dann den folgenden Befehl ein, um Datensätze aus der Tabelle orders abzurufen:

      Dadurch wird die folgende Ausgabe angezeigt, die die Bestellung des Kunden beschreibt:

      Output

      +----------+---------------------+-------------+-------------+ | order_id | order_date | customer_id | order_total | +----------+---------------------+-------------+-------------+ | 1 | 2020-01-11 00:00:00 | 2 | 157.8 | +----------+---------------------+-------------+-------------+ 1 row in set (0.00 sec)

      Rufen Sie dann die Datensätze aus der Tabelle orders_products ab:

      • SELECT * FROM orders_products;

      Sie sehen eine Ausgabe ähnlich der folgenden mit einer Liste von Produkten aus der Bestellung des Kunden:

      Output

      +--------+----------+------------+-------+----------+ | ref_id | order_id | product_id | price | quantity | +--------+----------+------------+-------+----------+ | 1 | 1 | 1 | 25.5 | 1 | | 2 | 1 | 2 | 13.9 | 3 | | 3 | 1 | 3 | 45.3 | 2 | +--------+----------+------------+-------+----------+ 3 rows in set (0.00 sec)

      Die Ausgabe bestätigt, dass die Transaktion in der Datenbank gespeichert wurde und Ihre Helferklasse DBTransaction wie erwartet funktioniert.

      Zusammenfassung

      In diesem Leitfaden haben Sie die PHP PDO verwendet, um mit MySQL-Transaktionen zu arbeiten. Obwohl dies kein erschöpfender Artikel über die Gestaltung einer E-Commerce-Software ist, hat er ein Beispiel für die Verwendung von MySQL-Transaktionen in Ihren Anwendungen bereitgestellt.

      Weitere Informationen über das Modell MySQL ACID finden Sie in dem Leitfaden InnoDB und das ACID-Modell auf der offiziellen MySQL-Website. Besuchen Sie unsere MySQL-Inhaltsseite für weitere verwandte Tutorials, Artikel und Fragen und Antworten.



      Source link