<link rel="stylesheet" href="styles-3GBANQL6.css">
← Zurück zum Blog

TypeORM

Datenbankverwaltung Erforschen mit TypeORM

Datenbankverwaltung Erforschen mit TypeORM

Verstehen von Datenbankspezifischen Spaltentypen

Die Verwendung von datenbankspezifischen Spaltentypen stellt sicher, dass Daten effizient gespeichert und abgerufen werden. Jeder Typ repräsentiert optimal die Art der gespeicherten Daten, was die Leistung und Datenintegrität verbessern kann.

Vorteile: Verbessert die Speichereffizienz, erzwingt die Datenintegrität und ermöglicht optimierte Abfragen.


@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column({ type: 'varchar', length: 100 })
    name: string;

    @Column({ type: 'int' })
    age: number;

    @Column({ type: 'boolean', default: true })
    isActive: boolean;

    @Column({ type: 'date' })
    birthDate: Date;

    @Column({ type: 'decimal', precision: 10, scale: 2 })
    balance: number;

    @Column({ type: 'json' })
    preferences: { theme: string; notifications: boolean };

    @Column({ type: 'text' })
    bio: string;
}
            

Entity Manager

Der Entity Manager ist entscheidend für die Verwaltung von Datenbankoperationen und bietet eine Möglichkeit, Daten zu manipulieren und zu persistieren. Er dient als primäre Schnittstelle zur Interaktion mit der Datenbank.

Vorteile: Vereinfacht die Datenmanipulation und bietet eine konsistente API für CRUD-Operationen.


const user = await entityManager.findOne(User, { where: { id: 1 } });
user.name = 'Aktualisierter Name';
await entityManager.save(user);
            

Repository und Benutzerdefinierte Repositories

Repositories abstrahieren die Datenzugriffslogik und erleichtern das Management und die Interaktion mit Entitäten. Benutzerdefinierte Repositories ermöglichen maßgeschneiderte Datenzugriffsmethoden, die den spezifischen Anforderungen der Anwendung entsprechen.

Vorteile: Verbessert die Codeorganisation, fördert die Wiederverwendbarkeit und kapselt die Datenzugriffslogik.


@EntityRepository(User)
export class UserRepository extends Repository {
    async findActiveUsers() {
        return this.find({ where: { isActive: true } });
    }
}
            

Sauberes Objekt-Relational-Modell

Die Trennung von Anwendungslogik und Datenbankschema schafft eine saubere und wartbare Struktur. Dies fördert das Prinzip der einzigen Verantwortung und erleichtert die Verwaltung von Änderungen.

Vorteile: Erleichtert das Testen, fördert die Wartbarkeit und verbessert die Klarheit der Code-Struktur.


class UserService {
    constructor(private userRepository: UserRepository) {}

    async getActiveUsers() {
        return await this.userRepository.findActiveUsers();
    }
}
            

Deklaratives Beispiel: Definieren einer Benutzerentität

Die deklarative Syntax zur Definition von Entitäten verbessert die Klarheit und reduziert Boilerplate-Code. Sie bietet eine klare Darstellung des Datenmodells.

Vorteile: Verbessert die Lesbarkeit und reduziert potenzielle Fehler während der Modelldefinition.


import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { Post } from './Post';

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column({ type: 'varchar', length: 100 })
    name: string;

    @Column({ type: 'boolean', default: true })
    isActive: boolean;

    @Column({ type: 'date' })
    birthDate: Date;

    @OneToMany(() => Post, (post) => post.user)
    posts: Post[];
}
            

Assoziationen (Beziehungen)

Das Definieren von Assoziationen zwischen Entitäten stellt Beziehungen her, die komplexe Datenstrukturen ermöglichen. Dies ist entscheidend für die Darstellung von realen Beziehungen in der Datenbank.

Vorteile: Ermöglicht strukturierten Datenabruf, erzwingt referentielle Integrität und vereinfacht komplexe Abfragen.


@Entity()
export class Post {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @ManyToOne(() => User, (user) => user.posts)
    user: User;
}
            

Eager- und Lazy-Relations

Eager Loading ruft verwandte Entitäten sofort ab, während Lazy Loading sie auf Abruf abruft. Dies ermöglicht Flexibilität je nach Anwendungsbedarf.

Vorteile: Optimiert die Leistung und Ressourcennutzung je nach Datenzugriffsmustern.


const users = await getRepository(User).find({ relations: ['posts'] }); // Eager Loading
            

const user = await getRepository(User).findOne(1);
const posts = await user.posts; // Lazy Loading
            

Einseitige, Zweiseitige und Selbstreferenzierte Beziehungen

Diese Beziehungen definieren, wie Entitäten miteinander verbunden sind. Zweiseitige Beziehungen ermöglichen die Navigation in beide Richtungen, während selbstreferenzierte Beziehungen Hierarchien darstellen können.

Vorteile: Bietet Flexibilität im Datenmodell und ermöglicht eine genaue Darstellung komplexer Beziehungen.


@Entity()
export class Category {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @ManyToOne(() => Category, (category) => category.subcategories, { nullable: true })
    parent: Category;

    @OneToMany(() => Category, (category) => category.parent)
    subcategories: Category[];
}
            

Unterstützt mehrere Vererbungsmuster

Vererbung ermöglicht es, gemeinsame Eigenschaften in einer Basisklasse zu definieren, während spezialisierte Verhaltensweisen in abgeleiteten Klassen ermöglicht werden. Dies fördert die Wiederverwendbarkeit von Code.

Vorteile: Verbessert die Organisation und minimiert die Duplizierung von Code über ähnliche Entitäten hinweg.


abstract class BaseEntity {
    @PrimaryGeneratedColumn()
    id: number;
}

@Entity()
export class User extends BaseEntity {
    @Column()
    name: string;
}

@Entity()
export class Admin extends BaseEntity {
    @Column()
    adminLevel: number;
}
            

Kaskaden

Kaskadierende Operationen ermöglichen es, dass verwandte Entitäten betroffen sind, wenn eine Entität geändert wird. Dies vereinfacht das Datenmanagement in verwandten Datensätzen.

Vorteile: Gewährleistet die Datenkonsistenz und reduziert die Notwendigkeit manueller Aktualisierungen in verwandten Entitäten.


@OneToMany(() => Post, (post) => post.user, { cascade: true })
posts: Post[];
            

Indizes

Indizes auf häufig abgefragten Spalten zu erstellen, verbessert die Datenbankleistung, indem die Datenabfragevorgänge beschleunigt werden.

Vorteile: Verbessert die Abfrageleistung, insbesondere bei großen Datensätzen.


@Column({ index: true })
email: string;
            

Transaktionen

Transaktionen ermöglichen atomare Operationen, was bedeutet, dass eine Gruppe von Operationen entweder vollständig oder gar nicht ausgeführt wird. Dies ist entscheidend für die Datenintegrität.

Vorteile: Gewährleistet, dass die Datenbank in einem konsistenten Zustand bleibt, auch im Falle von Fehlern.


await connection.transaction(async (manager) => {
    await manager.save(user);
    await manager.save(post);
});
            

Migrations und automatische Migrationsgenerierung

Migrations ermöglichen es, Datenbankschemas zu verwalten und Änderungen im Schema im Zeitverlauf nachzuverfolgen. Automatische Migrationsgenerierung vereinfacht diesen Prozess erheblich.

Vorteile: Vereinfacht das Schema-Management und fördert die Konsistenz zwischen Code und Datenbank.


await connection.runMigrations();
            

Verbindungspooling

Verbindungspooling optimiert die Leistung von Datenbankanwendungen, indem bestehende Verbindungen wiederverwendet werden, anstatt für jede Anfrage neue zu erstellen.

Vorteile: Reduziert die Latenz und den Overhead, die mit der Herstellung von Verbindungen verbunden sind, und verbessert die Gesamtreaktionsfähigkeit der Anwendung.


createConnection({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'test',
    password: 'test',
    database: 'test',
    extra: {
        connectionLimit: 10, // Poolgröße
    },
});
            

Replikation

Replikation erhöht die Datenverfügbarkeit und Zuverlässigkeit, indem Daten über mehrere Datenbankinstanzen hinweg kopiert werden, was für Lastverteilung und Fehlertoleranz entscheidend sein kann.

Vorteile: Erhöht die Datenverfügbarkeit, verbessert die Leistung durch Lastverteilung und bietet Datenredundanz.


{
    type: 'mysql',
    replication: {
        master: {
            host: 'master-db-host',
            username: 'root',
            password: 'password',
            database: 'test',
        },
        slaves: [
            {
                host: 'slave-db-host',
                username: 'root',
                password: 'password',
                database: 'test',
            },
        ],
    },
}
            

Verwendung mehrerer Datenbankinstanzen

Das Definieren mehrerer Verbindungen ermöglicht einer Anwendung, mit verschiedenen Datenbanken oder Instanzen zu interagieren, was in Mikroservices oder Mehrmandantenarchitekturen nützlich sein kann.

Vorteile: Bietet Flexibilität im Umgang mit unterschiedlichen Datenquellen und optimiert die Ressourcennutzung.


const connection1 = await createConnection({ /* Verbindungsoptionen für DB1 * / });
const connection2 = await createConnection({ /* Verbindungsoptionen für DB2 * / });
            

Arbeiten mit mehreren Datenbanktypen

Die Unterstützung mehrerer Datenbanktypen ermöglicht es Anwendungen, das beste Werkzeug für spezifische Aufgaben zu nutzen. Sie fördert auch die Integration mit Altsystemen.

Vorteile: Erhöht die Flexibilität der Anwendung und ermöglicht die Nutzung der Stärken verschiedener Datenbanken.


const mysqlConnection = await createConnection({
    type: 'mysql',
    /* MySQL-Optionen */
});

const mongoClient = await MongoClient.connect('mongodb://localhost:27017');
const db = mongoClient.db('test');
            

Kreuzdatenbank- und Kreuzschemaabfragen

Kreuzdatenbankabfragen ermöglichen den Zugriff auf Daten über verschiedene Datenbanken hinweg, während Kreuzschemaabfragen die Arbeit mit verschiedenen Schemata innerhalb derselben Datenbank ermöglichen.

Vorteile: Fördert die Datenintegration und erleichtert komplexe Datenoperationen über mehrere Datenquellen hinweg.


SELECT * FROM schema1.users u
JOIN schema2.orders o ON u.id = o.userId;
            

Elegante Syntax, flexibler und leistungsstarker QueryBuilder

Die Verwendung eines leistungsstarken QueryBuilders ermöglicht die dynamische und komplexe Erstellung von Abfragen, wodurch Entwickler effizientere Abfragen programmgesteuert erstellen können.

Vorteile: Verbessert die Lesbarkeit des Codes und die Flexibilität, was eine einfachere Wartung und Anpassung an Änderungen ermöglicht.


const users = await getRepository(User)
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.posts', 'post')
    .where('user.isActive = :isActive', { isActive: true })
    .getMany();
            

Linke und Innere Joins

Die Verwendung von Joins ermöglicht es, verwandte Daten effizient aus mehreren Tabellen abzurufen. Innere Joins geben übereinstimmende Datensätze zurück, während linke Joins alle Datensätze aus einer Tabelle unabhängig von Übereinstimmungen zurückgeben.

Vorteile: Verbessert die Abfragefähigkeiten und ermöglicht komplexe Datenausnahmeszenarien.


const posts = await getRepository(Post)
    .createQueryBuilder('post')
    .innerJoin('post.user', 'user')
    .where('user.isActive = :isActive', { isActive: true })
    .getMany();
            

Ordnungsgemäße Paginierung für Abfragen mit Joins

Die Implementierung von Paginierung in Abfragen ermöglicht eine effiziente Datenabrufung, insbesondere bei großen Datensätzen, indem die Anzahl der zurückgegebenen Ergebnisse begrenzt wird.

Vorteile: Verbessert die Anwendungsleistung und Benutzererfahrung, indem Daten in handhabbaren Chunks geladen werden.


const [posts, total] = await getRepository(Post)
    .createQueryBuilder('post')
    .innerJoinAndSelect('post.user', 'user')
    .skip(10) // Überspringe die ersten 10 Ergebnisse
    .take(10) // Nimm die nächsten 10 Ergebnisse
    .getManyAndCount();
            

Abfrage-Caching

Abfrage-Caching speichert die Ergebnisse häufig ausgeführter Abfragen und verbessert die Leistung, indem die Notwendigkeit verringert wird, bei jeder Abfrage die Datenbank anzusprechen.

Vorteile: Verbessert die Leistung erheblich für leseintensive Anwendungen, indem die Datenbanklast minimiert wird.


const users = await getRepository(User)
    .createQueryBuilder('user')
    .cache(60000) // Caching für 60 Sekunden
    .getMany();
            

Streaming roher Ergebnisse

Streaming ermöglicht die Verarbeitung großer Datensätze speichereffizient, da Ergebnisse schrittweise abgerufen werden, anstatt alles auf einmal.

Vorteile: Reduziert den Speicherverbrauch und verbessert die Leistung beim Umgang mit großen Datenmengen.


const query = getConnection()
    .createQueryBuilder()
    .select('user')
    .from(User, 'user');

const stream = await query.stream();
stream.on('data', (row) => {
    console.log(row);
});
            

Protokollierung

Die Einrichtung von Protokollen für Datenbankoperationen hilft beim Überwachen und Debuggen von Problemen. Sie bietet Einblicke in die Abfrageleistung und das Fehlerhandling.

Vorteile: Erleichtert das Troubleshooting und die Leistungsoptimierung, indem wertvolle Ausführungsdaten bereitgestellt werden.


createConnection({
    type: 'mysql',
    logging: ['query', 'error'], // Protokolliere Abfragen und Fehler
});
            

Listener und Abonnenten (Hooks)

Listener und Abonnenten ermöglichen es, benutzerdefinierte Logik auszuführen, wenn bestimmte Ereignisse in der Datenbank auftreten. Dies fördert die Reaktionsfähigkeit der Anwendung auf Änderungen.

Vorteile: Erleichtert die Implementierung von Geschäftslogik und die Anpassung an Datenänderungen.


@EntityListener()
export class UserListener {
    @AfterInsert()
    logInsert(user: User) {
        console.log(`Benutzer eingefügt: ${user.name}`);
    }
}
            

Unterstützt Closure-Tabellenmuster

Closure-Tabellen sind eine effiziente Möglichkeit, hierarchische Daten zu speichern und Abfragen über hierarchische Strukturen zu ermöglichen.

Vorteile: Optimiert die Abfrageleistung für hierarchische Daten und vereinfacht die Verwaltung von Beziehungen.


@Entity()
export class Category {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @OneToMany(() => CategoryClosure, closure => closure.category)
    closures: CategoryClosure[];
}

@Entity()
export class CategoryClosure {
    @PrimaryGeneratedColumn()
    id: number;

    @ManyToOne(() => Category, category => category.closures)
    category: Category;

    @ManyToOne(() => Category, category => category.closures)
    ancestor: Category;
}
            

Schema-Deklaration in Modellen oder separaten Konfigurationsdateien

Die Möglichkeit, das Datenbankschema sowohl innerhalb der Modelle als auch in separaten Konfigurationsdateien zu deklarieren, bietet Flexibilität bei der Verwaltung von Änderungen und der Versionierung.

Vorteile: Verbessert die Organisation des Codes und erleichtert die Zusammenarbeit im Team.


@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column({ nullable: true })
    email: string;
}

// Separates Konfigurationsbeispiel
export const UserSchema = {
    name: 'User',
    columns: [
        { name: 'id', type: 'int', primary: true, generated: true },
        { name: 'name', type: 'varchar' },
        { name: 'email', type: 'varchar', nullable: true },
    ],
};
            

LASSEN SIE UNS ETWAS NEUES BAUEN

Aus Ihrer Idee kann die nächste gute Softwarelösung entstehen.

Erzählen Sie uns, was Sie entwickeln, verbessern oder neu denken möchten.

Projekt besprechen