Zum Inhalt springen

Aufgabe 17 - ORM und Migrationen mit Prisma

Zu Zen-Modus wechseln

In dieser Übung wird ein Next.js-Projekt über das ORM Prisma mit einer laufenden PostgreSQL-Datenbank verbunden (siehe Kapitel 9 - Integration in Webanwendungen). Der Fokus liegt auf den Datenbankkonzepten dahinter: ein Schema im Code, was bei Migrationen in der Datenbank passiert und wie man das N+1-Problem vermeidet.

  • Kapitel 9 - Integration in Webanwendungen.
  • Eine laufende Docker-PostgreSQL-Umgebung; Node.js (≥ 18) und npm; psql oder pgAdmin.
  • Sie verbinden ein ORM mit einer Datenbank über einen Connection String.
  • Sie definieren ein Schema im Code und beschreiben Beziehungen.
  • Sie erzeugen und verstehen versionierte Migrationen.
  • Sie erkennen und lösen das N+1-Problem mit Eager Loading.
  • Reproduktion: Projekt und Prisma einrichten (Teil A).
  • Reorganisation und Transfer: Schema definieren und migrieren (Teile B und C).
  • Reflexion, Problemlösung und Urteilsbildung: das N+1-Problem beobachten, lösen und beurteilen (Teil D).

Die Übung ist auf etwa zwei Stunden ausgelegt. Teil D ist der Expertenteil.

Zuerst schriftlich: Unterschied ORM gegen nativen Treiber (pg); was steckt in einem Connection String (DATABASE_URL); warum gehört er in eine .env-Datei und nicht in den Code?

  1. Ein Next.js-Projekt anlegen:

    Terminal-Fenster
    npx create-next-app@latest schoolblog --typescript --app --no-tailwind --no-eslint --src-dir
    cd schoolblog
  2. Die Datenbank schoolblog manuell anlegen (CREATE DATABASE schoolblog;), da Prisma nur Tabellen, aber keine Datenbank erstellt.

  3. Prisma installieren und initialisieren, dann die DATABASE_URL in .env anpassen:

    Terminal-Fenster
    npm install prisma @prisma/client
    npx prisma init
    DATABASE_URL="postgresql://pgadmin:postgres-root-password@localhost:5432/schoolblog"

Zuerst schriftlich: Was ist eine Migration und warum als Datei in der Versionsverwaltung? Unterschied prisma migrate dev gegen prisma migrate deploy? Was passiert in der Datenbank beim Hinzufügen einer Spalte?

  1. In prisma/schema.prisma das Schema definieren:

    model User {
    id Int @id @default(autoincrement())
    email String @unique
    name String
    posts Post[]
    createdAt DateTime @default(now())
    }
    model Category {
    id Int @id @default(autoincrement())
    name String @unique
    posts Post[]
    }
    model Post {
    id Int @id @default(autoincrement())
    title String
    content String?
    published Boolean @default(false)
    author User @relation(fields: [authorId], references: [id])
    authorId Int
    category Category @relation(fields: [categoryId], references: [id])
    categoryId Int
    createdAt DateTime @default(now())
    }

    Festhalten: Welche Beziehungstypen bestehen? Was bedeutet @relation(...) (welche SQL-Konstruktion), und was @unique?

  2. Die erste Migration ausführen und auf zwei Wegen kontrollieren:

    Terminal-Fenster
    npx prisma migrate dev --name init

    In prisma/migrations/*/migration.sql den generierten SQL-Code prüfen und in der Datenbank mit \dt und \d "Post" nachsehen, ob die Fremdschlüssel angelegt wurden.

  1. Dem Modell Post ein Feld updatedAt DateTime? @updatedAt hinzufügen und migrieren:

    Terminal-Fenster
    npx prisma migrate dev --name add_updated_at_to_post

    In der neuen migration.sql den ALTER TABLE prüfen und begründen, warum das Feld nullable ist (was bei NOT NULL ohne Default an einer befüllten Tabelle passieren würde).

  2. Den Migrationsverlauf prüfen:

    SELECT migration_name, finished_at FROM "_prisma_migrations" ORDER BY finished_at;

    Reflexion: Was meldet Prisma, wenn man eine bereits angewandte Migrationsdatei nachträglich editiert? Wann ist prisma migrate reset sinnvoll, und warum ist es in der Produktion gefährlich?

Teil D - Expertenteil: Das N+1-Problem beobachten und lösen

Abschnitt betitelt „Teil D - Expertenteil: Das N+1-Problem beobachten und lösen“

Zuerst schriftlich: Was ist das N+1-Problem (am Beispiel Posts/Autoren)? Unterschied include gegen select? Warum ein globaler Prisma-Client-Singleton?

  1. Einen Prisma-Client-Singleton src/lib/prisma.ts anlegen (mit log: ['query'], damit jede SQL-Abfrage im Terminal sichtbar ist).

  2. Testdaten über prisma/seed.ts einfügen (zwei User, zwei Kategorien, vier Posts) und mit npx prisma db seed laden.

  3. N+1 beobachten: Eine src/app/blog/page.tsx mit absichtlich fehlerhafter Logik schreiben, die zuerst alle Posts lädt und dann pro Post den Autor einzeln nachlädt:

    const posts = await prisma.post.findMany({ where: { published: true } });
    // one separate query per post:
    const author = await prisma.user.findUnique({ where: { id: post.authorId } });

    Den Dev-Server starten (npm run dev), /blog aufrufen und im Terminal zählen, wie viele SQL-Abfragen laufen und welche sich wiederholen. Was ändert sich mit einem fünften Post?

  4. N+1 lösen: Die Abfrage durch eine einzige mit Eager Loading ersetzen:

    const posts = await prisma.post.findMany({
    where: { published: true },
    include: {
    author: { select: { name: true } },
    category: { select: { name: true } },
    },
    orderBy: { createdAt: 'desc' },
    });

    Erneut laden und im Terminal prüfen, dass nur noch eine Abfrage läuft; den JOIN in der generierten SQL finden.

  5. Selektiv laden: Eine Abfrage schreiben, die nur title und den name des Autors lädt (select statt include), und die generierte SQL mit der aus Schritt 4 vergleichen.

  6. Reflexion: Warum ist das N+1-Problem in Eloquent/Laravel (Lazy Loading) besonders tückisch? Wann wäre eine separate Abfrage sinnvoller als include? Was passiert bei 10.000 Posts, wenn das Problem unbemerkt bleibt?

  1. Sie finden einen Tippfehler in einer bereits eingecheckten und angewandten Migration. Dürfen Sie die Datei direkt editieren? Beschreiben Sie das korrekte Vorgehen.

  2. Erklären und korrigieren Sie:

    const categories = await prisma.category.findMany();
    for (const cat of categories) {
    const posts = await prisma.post.findMany({ where: { categoryId: cat.id } });
    console.log(`${cat.name}: ${posts.length} Posts`);
    }
  3. Nennen Sie zwei Situationen, in denen man trotz ORM auf direktes SQL (prisma.$queryRaw) zurückgreifen sollte.

  • Ein Word- oder PDF-Dokument mit den Antworten auf alle Theorie- und Reflexionsfragen, Screenshots der beiden migration.sql, der Tabellenstruktur, der _prisma_migrations-Tabelle sowie der Terminal-Ausgabe mit dem N+1-Problem (Schritt 3) und nach der Lösung (Schritt 4), dazu der fertige Code von page.tsx.

HTL Villach, Schuljahr 2025-2026,
https://www.htl-villach.at