Erste Schritte
Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →
TypeORM ist ein ORM, der auf Node.js, Browser, Cordova, Ionic, React Native, NativeScript, Expo und Electron läuft sowie mit TypeScript und JavaScript (ES2021) genutzt werden kann.
Sein Ziel ist es, stets die neuesten JavaScript-Features zu unterstützen und zusätzliche Funktionen bereitzustellen, die bei der Entwicklung jeder Art von datenbankgestützter Anwendung helfen – von kleinen Anwendungen mit wenigen Tabellen bis hin zu großen Enterprise-Anwendungen mit mehreren Datenbanken.
TypeORM unterstützt mehr Datenbanken als jedes andere JS/TS-ORM: Google Spanner, Microsoft SqlServer, MongoDB, MySQL/MariaDB, Oracle, Postgres, SAP HANA und SQLite, sowie abgeleitete Datenbanken und verschiedene Treiber.
TypeORM unterstützt sowohl das Active Record- als auch das Data Mapper-Muster, im Gegensatz zu allen anderen existierenden JavaScript-ORMs. Das bedeutet, dass Sie hochwertige, lose gekoppelte, skalierbare und wartbare Anwendungen auf produktivste Weise entwickeln können.
TypeORM ist stark von anderen ORMs beeinflusst, wie Hibernate, Doctrine und Entity Framework.
Funktionen
-
Unterstützt sowohl DataMapper als auch ActiveRecord (Ihre Wahl).
-
Entitäten und Spalten.
-
Datenbankspezifische Spaltentypen.
-
Entity Manager.
-
Repositories und benutzerdefinierte Repositories.
-
Sauberes objektrelationales Modell.
-
Assoziationen (Relationen).
-
Eager und Lazy Loading für Relationen.
-
Unidirektionale, bidirektionale und selbstreferenzierende Relationen.
-
Unterstützt mehrere Vererbungsmuster.
-
Kaskadierende Operationen.
-
Indizes.
-
Transaktionen.
-
Migrationen mit automatischer Generierung.
-
Verbindungspooling.
-
Replikation.
-
Nutzung mehrerer Datenbankinstanzen.
-
Arbeiten mit verschiedenen Datenbanktypen.
-
Datenbankübergreifende und schemaübergreifende Abfragen.
-
Elegante Syntax, flexibler und leistungsstarker QueryBuilder.
-
Left und Inner Joins.
-
Korrekte Paginierung für Abfragen mit Joins.
-
Abfrage-Caching.
-
Streaming von Rohdaten-Ergebnissen.
-
Protokollierung.
-
Listener und Subscriber (Hooks).
-
Unterstützt das Closure Table Pattern.
-
Schema-Deklaration in Modellen oder separaten Konfigurationsdateien.
-
Unterstützt MySQL / MariaDB / Postgres / CockroachDB / SQLite / Microsoft SQL Server / Oracle / SAP Hana / sql.js.
-
Unterstützt MongoDB NoSQL-Datenbank.
-
Funktioniert auf Node.js / Browser / Ionic / Cordova / React Native / NativeScript / Expo / Electron Plattformen.
-
TypeScript und JavaScript Unterstützung.
-
ESM und CommonJS Unterstützung.
-
Erzeugter Code ist performant, flexibel, sauber und wartbar.
-
Folgt allen möglichen Best Practices.
-
CLI.
Und mehr...
Mit TypeORM sehen Ihre Modelle so aus:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
age: number
}
Und Ihre Domain-Logik sieht so aus:
const userRepository = AppDataSource.getRepository(User)
const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.age = 25
await userRepository.save(user)
const allUsers = await userRepository.find()
const firstUser = await userRepository.findOneBy({
id: 1,
}) // find by id
const timber = await userRepository.findOneBy({
firstName: "Timber",
lastName: "Saw",
}) // find by firstName and lastName
await userRepository.remove(timber)
Alternativ, wenn Sie die ActiveRecord-Implementierung bevorzugen, können Sie auch diese verwenden:
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm"
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
age: number
}
Und Ihre Domain-Logik würde dann so aussehen:
const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.age = 25
await user.save()
const allUsers = await User.find()
const firstUser = await User.findOneBy({
id: 1,
})
const timber = await User.findOneBy({
firstName: "Timber",
lastName: "Saw",
})
await timber.remove()
Installation
-
Installieren Sie das npm-Paket:
npm install typeorm -
Sie müssen das
reflect-metadata-Shim installieren:npm install reflect-metadataund es an einer globalen Stelle Ihrer App importieren (z.B. in
app.ts):import "reflect-metadata" -
Möglicherweise müssen Sie Node-Typings installieren:
npm install @types/node --save-dev -
Installieren Sie einen Datenbanktreiber: Siehe Dokumentation für jeden spezifischen Treiber: mongodb, mssql, mysql/mariadb, oracle, postgres, sap, spanner, sqlite.
TypeScript-Konfiguration
Stellen Sie außerdem sicher, dass Sie TypeScript Version 4.5 oder höher verwenden,
und folgende Einstellungen in tsconfig.json aktiviert haben:
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
Schnellstart
Der schnellste Einstieg in TypeORM erfolgt über CLI-Befehle zur Erstellung eines Starterprojekts. Der Schnellstart funktioniert nur, wenn Sie TypeORM in einer Node.js-Anwendung verwenden. Für andere Plattformen folgen Sie der Schritt-für-Schritt-Anleitung.
Führen Sie folgenden Befehl aus, um ein neues Projekt mit CLI zu erstellen:
npx typeorm init --name MyProject --database postgres
Dabei ist name der Name Ihres Projekts und database die verwendete Datenbank.
Datenbank kann einer dieser Werte sein: mysql, mariadb, postgres, cockroachdb, sqlite, mssql, sap, spanner, oracle, mongodb,
cordova, react-native, expo, nativescript.
Dieser Befehl generiert ein neues Projekt im Verzeichnis MyProject mit folgenden Dateien:
MyProject
├── src // place of your TypeScript code
│ ├── entities // place where your entities (database models) are stored
│ │ └── User.ts // sample entity
│ ├── migrations // place where your migrations are stored
│ ├── data-source.ts // data source and all connection configuration
│ └── index.ts // start point of your application
├── .gitignore // standard gitignore file
├── package.json // node module dependencies
├── README.md // simple readme file
└── tsconfig.json // TypeScript compiler options
Sie können
typeorm initauch in einem existierenden Node-Projekt ausführen, aber Vorsicht - dies könnte vorhandene Dateien überschreiben.
Installieren Sie als nächstes die Projektabhängigkeiten:
cd MyProject
npm install
Nach Installation aller Abhängigkeiten bearbeiten Sie die Datei data-source.ts und tragen Ihre Datenbankverbindungsdaten ein:
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "test",
password: "test",
database: "test",
synchronize: true,
logging: true,
entities: [Post, Category],
subscribers: [],
migrations: [],
})
Insbesondere müssen Sie meist nur host, username, password, database und eventuell port konfigurieren.
Nach abgeschlossener Konfiguration und Installation aller Module starten Sie Ihre Anwendung:
npm start
Das war's! Ihre Anwendung sollte nun erfolgreich laufen und einen neuen Benutzer in die Datenbank einfügen. Sie können mit diesem Projekt weiterarbeiten, weitere Module integrieren und zusätzliche Entitäten erstellen.
Sie können ein ESM-Projekt generieren mit:
npx typeorm init --name MyProject --database postgres --module esm
Ein erweitertes Projekt mit Express-Installation generieren Sie mit:
npx typeorm init --name MyProject --database mysql --express
Eine docker-compose-Datei generieren Sie mit:
npx typeorm init --name MyProject --database postgres --docker
Schritt-für-Schritt-Anleitung
Was erwarten Sie von einem ORM? Zunächst einmal erwarten Sie, dass es Datenbanktabellen für Sie erstellt und Ihre Daten findet, einfügt, aktualisiert und löscht, ohne dass Sie viele schwer wartbare SQL-Abfragen schreiben müssen. Diese Anleitung zeigt Ihnen, wie Sie TypeORM von Grund auf einrichten und es so konfigurieren, dass es das tut, was Sie von einem ORM erwarten.
Ein Modell erstellen
Die Arbeit mit einer Datenbank beginnt mit dem Erstellen von Tabellen. Wie teilen Sie TypeORM mit, dass es eine Datenbanktabelle erstellen soll? Die Antwort lautet: über die Modelle. Ihre Modelle in Ihrer Anwendung sind Ihre Datenbanktabellen.
Beispielsweise haben Sie ein Photo-Modell:
export class Photo {
id: number
name: string
description: string
filename: string
views: number
isPublished: boolean
}
Und Sie möchten Fotos in Ihrer Datenbank speichern. Um Dinge in der Datenbank zu speichern, benötigen Sie zunächst eine Datenbanktabelle, und Datenbanktabellen werden aus Ihren Modellen erstellt. Nicht alle Modelle, sondern nur diejenigen, die Sie als Entitäten definieren.
Eine Entität erstellen
Eine Entität ist Ihr Modell, das mit einem @Entity-Dekorator versehen ist.
Für solche Modelle wird eine Datenbanktabelle erstellt.
Sie arbeiten in TypeORM überall mit Entitäten.
Sie können sie laden, einfügen, aktualisieren, entfernen und andere Operationen mit ihnen durchführen.
Machen wir unser Photo-Modell zu einer Entität:
import { Entity } from "typeorm"
@Entity()
export class Photo {
id: number
name: string
description: string
filename: string
views: number
isPublished: boolean
}
Nun wird eine Datenbanktabelle für die Photo-Entität erstellt, und wir können überall in unserer App damit arbeiten.
Wir haben eine Datenbanktabelle erstellt, aber welche Tabelle kann ohne Spalten existieren?
Erstellen wir einige Spalten in unserer Datenbanktabelle.
Tabellenspalten hinzufügen
Um Datenbankspalten hinzuzufügen, müssen Sie die Eigenschaften einer Entität, die Sie zu einer Spalte machen möchten,
mit einem @Column-Dekorator versehen.
import { Entity, Column } from "typeorm"
@Entity()
export class Photo {
@Column()
id: number
@Column()
name: string
@Column()
description: string
@Column()
filename: string
@Column()
views: number
@Column()
isPublished: boolean
}
Nun werden die Spalten id, name, description, filename, views und isPublished zur photo-Tabelle hinzugefügt.
Die Spaltentypen in der Datenbank werden von den verwendeten Eigenschaftstypen abgeleitet, z. B.
wird number in integer, string in varchar, boolean in bool usw. konvertiert.
Sie können jedoch jeden Spaltentyp verwenden, den Ihre Datenbank unterstützt, indem Sie den Spaltentyp explizit im @Column-Dekorator angeben.
Wir haben eine Datenbanktabelle mit Spalten erzeugt, aber es fehlt noch etwas. Jede Datenbanktabelle muss eine Spalte mit einem Primärschlüssel haben.
Eine Primärschlüsselspalte erstellen
Jede Entität muss mindestens eine Primärschlüsselspalte haben.
Das ist eine Anforderung, und Sie können sie nicht umgehen.
Um eine Spalte zu einem Primärschlüssel zu machen, müssen Sie den @PrimaryColumn-Dekorator verwenden.
import { Entity, Column, PrimaryColumn } from "typeorm"
@Entity()
export class Photo {
@PrimaryColumn()
id: number
@Column()
name: string
@Column()
description: string
@Column()
filename: string
@Column()
views: number
@Column()
isPublished: boolean
}
Eine automatisch generierte Spalte erstellen
Nehmen wir nun an, Sie möchten, dass Ihre ID-Spalte automatisch generiert wird (bekannt als Auto-Increment-, Sequenz-, Serial- oder generierte Identitätsspalte).
Dazu müssen Sie den @PrimaryColumn-Dekorator in einen @PrimaryGeneratedColumn-Dekorator ändern:
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column()
description: string
@Column()
filename: string
@Column()
views: number
@Column()
isPublished: boolean
}
Spaltendatentypen
Als nächstes korrigieren wir unsere Datentypen. Standardmäßig wird ein String auf einen varchar(255)-ähnlichen Typ abgebildet (abhängig vom Datenbanktyp). Eine Zahl wird auf einen integer-ähnlichen Typ abgebildet (abhängig vom Datenbanktyp). Wir möchten nicht, dass alle unsere Spalten auf varchars oder integers beschränkt sind. Legen wir die korrekten Datentypen fest:
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column({
length: 100,
})
name: string
@Column("text")
description: string
@Column()
filename: string
@Column("double")
views: number
@Column()
isPublished: boolean
}
Spaltentypen sind datenbankspezifisch. Sie können jeden Spaltentyp einstellen, den Ihre Datenbank unterstützt. Weitere Informationen zu unterstützten Spaltentypen finden Sie hier.
Eine neue DataSource erstellen
Nun, da unsere Entität erstellt ist, erstellen wir die Datei index.ts und richten dort unsere DataSource ein:
import "reflect-metadata"
import { DataSource } from "typeorm"
import { Photo } from "./entity/Photo"
const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "root",
password: "admin",
database: "test",
entities: [Photo],
synchronize: true,
logging: false,
})
// to initialize the initial connection with the database, register all entities
// and "synchronize" database schema, call "initialize()" method of a newly created database
// once in your application bootstrap
try {
await AppDataSource.initialize()
} catch (error) {
console.log(error)
}
In diesem Beispiel verwenden wir Postgres, aber Sie können jede andere unterstützte Datenbank nutzen.
Um eine andere Datenbank zu verwenden, ändern Sie den type in den Optionen zum gewünschten Datenbanktyp:
mysql, mariadb, postgres, cockroachdb, sqlite, mssql, oracle, sap, spanner, cordova, nativescript, react-native,
expo oder mongodb.
Stellen Sie außerdem sicher, dass Sie Ihre eigenen Einstellungen für Host, Port, Benutzername, Passwort und Datenbank verwenden.
Wir haben unsere Photo-Entität zur Liste der Entitäten für diese Datenquelle hinzugefügt. Jede Entität, die Sie in Ihrer Verbindung verwenden, muss dort aufgeführt sein.
Durch Setzen von synchronize wird sichergestellt, dass Ihre Entitäten bei jedem Ausführen der Anwendung mit der Datenbank synchronisiert werden.
Anwendung ausführen
Wenn Sie nun Ihr index.ts ausführen, wird eine Verbindung zur Datenbank initialisiert und eine Datenbanktabelle für Ihre Fotos erstellt.
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(100) | |
| description | text | |
| filename | varchar(255) | |
| views | int | |
| isPublished | boolean | |
+-------------+--------------+----------------------------+
Foto erstellen und in der Datenbank speichern
Erstellen wir nun ein neues Foto, um es in der Datenbank zu speichern:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photo = new Photo()
photo.name = "Me and Bears"
photo.description = "I am near polar bears"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true
await AppDataSource.manager.save(photo)
console.log("Photo has been saved. Photo id is", photo.id)
Sobald Ihre Entität gespeichert ist, erhält sie eine neu generierte ID.
Die save-Methode gibt eine Instanz desselben Objekts zurück, das Sie übergeben haben.
Es handelt sich nicht um eine neue Kopie des Objekts – die Methode modifiziert dessen "id" und gibt es zurück.
Entity Manager verwenden
Wir haben gerade ein neues Foto erstellt und in der Datenbank gespeichert.
Dazu haben wir den EntityManager verwendet.
Mit dem Entity Manager können Sie jede Entität in Ihrer App manipulieren.
Laden wir beispielsweise unsere gespeicherte Entität:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const savedPhotos = await AppDataSource.manager.find(Photo)
console.log("All photos from the db: ", savedPhotos)
savedPhotos wird ein Array von Photo-Objekten mit den aus der Datenbank geladenen Daten sein.
Erfahren Sie mehr über den EntityManager.
Repositories verwenden
Lassen Sie uns unseren Code nun refaktorieren und Repository statt EntityManager verwenden.
Jede Entität hat ihr eigenes Repository, das alle Operationen mit dieser Entität handhabt.
Wenn Sie häufig mit Entitäten arbeiten, sind Repositories praktischer als Entity Manager:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photo = new Photo()
photo.name = "Me and Bears"
photo.description = "I am near polar bears"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true
const photoRepository = AppDataSource.getRepository(Photo)
await photoRepository.save(photo)
console.log("Photo has been saved")
const savedPhotos = await photoRepository.find()
console.log("All photos from the db: ", savedPhotos)
Erfahren Sie mehr über Repositories hier.
Aus der Datenbank laden
Probieren wir weitere Ladeoperationen mit dem Repository aus:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource.getRepository(Photo)
const allPhotos = await photoRepository.find()
console.log("All photos from the db: ", allPhotos)
const firstPhoto = await photoRepository.findOneBy({
id: 1,
})
console.log("First photo from the db: ", firstPhoto)
const meAndBearsPhoto = await photoRepository.findOneBy({
name: "Me and Bears",
})
console.log("Me and Bears photo from the db: ", meAndBearsPhoto)
const allViewedPhotos = await photoRepository.findBy({ views: 1 })
console.log("All viewed photos: ", allViewedPhotos)
const allPublishedPhotos = await photoRepository.findBy({ isPublished: true })
console.log("All published photos: ", allPublishedPhotos)
const [photos, photosCount] = await photoRepository.findAndCount()
console.log("All photos: ", photos)
console.log("Photos count: ", photosCount)
In der Datenbank aktualisieren
Laden wir nun ein einzelnes Foto aus der Datenbank, aktualisieren es und speichern es:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource.getRepository(Photo)
const photoToUpdate = await photoRepository.findOneBy({
id: 1,
})
photoToUpdate.name = "Me, my friends and polar bears"
await photoRepository.save(photoToUpdate)
Nun wird das Foto mit id = 1 in der Datenbank aktualisiert.
Aus der Datenbank entfernen
Entfernen wir nun unser Foto aus der Datenbank:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource.getRepository(Photo)
const photoToRemove = await photoRepository.findOneBy({
id: 1,
})
await photoRepository.remove(photoToRemove)
Nun wird das Foto mit id = 1 aus der Datenbank entfernt.
1:1-Beziehung erstellen
Erstellen wir eine 1:1-Beziehung mit einer anderen Klasse.
Erstellen wir eine neue Klasse in PhotoMetadata.ts. Diese PhotoMetadata-Klasse soll zusätzliche Metainformationen unseres Fotos enthalten:
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
} from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class PhotoMetadata {
@PrimaryGeneratedColumn()
id: number
@Column("int")
height: number
@Column("int")
width: number
@Column()
orientation: string
@Column()
compressed: boolean
@Column()
comment: string
@OneToOne(() => Photo)
@JoinColumn()
photo: Photo
}
Hier verwenden wir einen neuen Dekorator namens @OneToOne. Er ermöglicht eine 1:1-Beziehung zwischen zwei Entitäten. Wir fügen auch einen @JoinColumn-Dekorator hinzu, der anzeigt, dass diese Seite der Beziehung deren Eigentümer ist.
Beziehungen können unidirektional oder bidirektional sein.
Nur eine Seite der Beziehung kann der Eigentümer sein.
Der @JoinColumn-Dekorator ist auf der Eigentümerseite der Beziehung erforderlich.
Wenn Sie die App ausführen, sehen Sie eine neu generierte Tabelle, die eine Spalte mit einem Fremdschlüssel für die Foto-Beziehung enthält:
+-------------+--------------+----------------------------+
| photo_metadata |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| height | int | |
| width | int | |
| comment | varchar(255) | |
| compressed | boolean | |
| orientation | varchar(255) | |
| photoId | int | FOREIGN KEY |
+-------------+--------------+----------------------------+
1:1-Beziehung speichern
Speichern wir nun ein Foto und seine Metadaten und verknüpfen sie miteinander.
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
// Create a photo
const photo = new Photo()
photo.name = "Me and Bears"
photo.description = "I am near polar bears"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true
// Create a photo metadata
const metadata = new PhotoMetadata()
metadata.height = 640
metadata.width = 480
metadata.compressed = true
metadata.comment = "cybershoot"
metadata.orientation = "portrait"
metadata.photo = photo // this way we connect them
// Get entity repositories
const photoRepository = AppDataSource.getRepository(Photo)
const metadataRepository = AppDataSource.getRepository(PhotoMetadata)
// First we should save a photo
await photoRepository.save(photo)
// The Photo is saved. Now we need to save a photo metadata
await metadataRepository.save(metadata)
// Done
console.log(
"Metadata is saved, and the relation between metadata and photo is created in the database too",
)
Gegenseite der Beziehung
Beziehungen können unidirektional oder bidirektional sein. Derzeit ist unsere Beziehung zwischen PhotoMetadata und Photo unidirektional. Die besitzende Seite der Beziehung ist PhotoMetadata, und Photo weiß nichts über PhotoMetadata. Das erschwert den Zugriff auf PhotoMetadata von der Photo-Seite aus. Um dies zu beheben, sollten wir eine inverse Beziehung hinzufügen und die Beziehung zwischen PhotoMetadata und Photo bidirektional gestalten. Ändern wir unsere Entitäten:
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
} from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class PhotoMetadata {
/* ... other columns */
@OneToOne(() => Photo, (photo) => photo.metadata)
@JoinColumn()
photo: Photo
}
import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"
@Entity()
export class Photo {
/* ... other columns */
@OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
metadata: PhotoMetadata
}
photo => photo.metadata ist eine Funktion, die den Namen der inversen Seite der Relation zurückgibt.
Hier zeigen wir, dass die metadata-Eigenschaft der Photo-Klasse der Ort ist, an dem wir PhotoMetadata in der Photo-Klasse speichern.
Anstatt eine Funktion zu übergeben, die eine Eigenschaft des Photo-Objekts zurückgibt, kannst Du alternativ einen String an den @OneToOne-Decorator übergeben, wie z.B. "metadata".
Wir haben jedoch diesen funktionsbasierten Ansatz gewählt, um Refactorings zu erleichtern.
Beachten Sie, dass wir den @JoinColumn-Dekorator nur auf einer Seite einer Beziehung verwenden sollten.
Die Seite, auf der Sie diesen Dekorator platzieren, wird die besitzende Seite der Beziehung sein.
Die besitzende Seite einer Beziehung enthält in der Datenbank eine Spalte mit einem Fremdschlüssel.
Beziehungen in ESM-Projekten
Wenn Sie ESM in Ihrem TypeScript-Projekt verwenden, sollten Sie den Relation-Wrapper-Typ in Beziehungseigenschaften nutzen, um Zirkelabhängigkeiten zu vermeiden.
Ändern wir unsere Entitäten:
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
Relation,
} from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class PhotoMetadata {
/* ... other columns */
@OneToOne(() => Photo, (photo) => photo.metadata)
@JoinColumn()
photo: Relation<Photo>
}
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
Relation,
} from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"
@Entity()
export class Photo {
/* ... other columns */
@OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
metadata: Relation<PhotoMetadata>
}
Laden von Objekten mit ihren Beziehungen
Laden wir nun unser Photo und seine zugehörigen Metadaten in einer einzigen Abfrage.
Es gibt zwei Möglichkeiten: find*-Methoden oder die QueryBuilder-Funktionalität.
Verwenden wir zuerst die find*-Methode.
find*-Methoden erlauben die Angabe eines Objekts mit dem FindOneOptions/FindManyOptions-Interface.
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource.getRepository(Photo)
const photos = await photoRepository.find({
relations: {
metadata: true,
},
})
Hier enthält photos ein Array von Photos aus der Datenbank, und jedes Photo enthält seine zugehörigen Metadaten. Mehr zu Find-Optionen finden Sie in dieser Dokumentation.
Find-Optionen sind einfach und effektiv, aber für komplexere Abfragen sollte QueryBuilder verwendet werden.
QueryBuilder ermöglicht elegante komplexe Abfragen:
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
import { AppDataSource } from "./index"
const photos = await AppDataSource.getRepository(Photo)
.createQueryBuilder("photo")
.innerJoinAndSelect("photo.metadata", "metadata")
.getMany()
QueryBuilder erlaubt das Erstellen und Ausführen nahezu beliebig komplexer SQL-Abfragen.
Stellen Sie sich beim Arbeiten mit QueryBuilder vor, Sie erstellen eine SQL-Abfrage.
In diesem Beispiel sind "photo" und "metadata" Aliase für ausgewählte Photos.
Sie verwenden Aliase, um auf Spalten und Eigenschaften der ausgewählten Daten zuzugreifen.
Automatisches Speichern verknüpfter Objekte mit Cascades
Wir können Cascade-Optionen in unseren Beziehungen konfigurieren, um verknüpfte Objekte automatisch zu speichern.
Passen wir den @OneToOne-Dekorator für unser Photo an:
export class Photo {
// ... other columns
@OneToOne(() => PhotoMetadata, (metadata) => metadata.photo, {
cascade: true,
})
metadata: PhotoMetadata
}
Mit cascade müssen wir Photos und Metadaten nicht mehr separat speichern.
Jetzt können wir einfach ein Photo-Objekt speichern, und das Metadaten-Objekt wird automatisch gespeichert.
import { AppDataSource } from "./index"
// create photo object
const photo = new Photo()
photo.name = "Me and Bears"
photo.description = "I am near polar bears"
photo.filename = "photo-with-bears.jpg"
photo.isPublished = true
// create photo metadata object
const metadata = new PhotoMetadata()
metadata.height = 640
metadata.width = 480
metadata.compressed = true
metadata.comment = "cybershoot"
metadata.orientation = "portrait"
photo.metadata = metadata // this way we connect them
// get repository
const photoRepository = AppDataSource.getRepository(Photo)
// saving a photo also save the metadata
await photoRepository.save(photo)
console.log("Photo is saved, photo metadata is saved too.")
Beachten Sie, dass wir nun die metadata-Eigenschaft des Photos setzen, nicht wie zuvor die photo-Eigenschaft der Metadaten. Die cascade-Funktion funktioniert nur, wenn Sie das Photo von der Photo-Seite mit seinen Metadaten verbinden. Wenn Sie die Metadaten-Seite setzen, würden die Metadaten nicht automatisch gespeichert.
Erstellen einer Many-to-One / One-to-Many-Beziehung
Erstellen wir eine Many-to-One/One-to-Many-Beziehung.
Angenommen, ein Photo hat einen Autor, und jeder Autor kann viele Photos haben.
Erstellen wir zuerst eine Author-Klasse:
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToMany,
JoinColumn,
} from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class Author {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@OneToMany(() => Photo, (photo) => photo.author) // note: we will create author property in the Photo class below
photos: Photo[]
}
Author enthält die inverse Seite einer Beziehung.
OneToMany ist immer die inverse Seite der Beziehung und kann nicht ohne ManyToOne auf der anderen Seite existieren.
Fügen wir nun die besitzende Seite der Beziehung in die Photo-Entität ein:
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"
import { Author } from "./Author"
@Entity()
export class Photo {
/* ... other columns */
@ManyToOne(() => Author, (author) => author.photos)
author: Author
}
In Many-to-One / One-to-Many-Beziehungen ist die besitzende Seite immer Many-to-One.
Das bedeutet, dass die Klasse mit @ManyToOne die ID des verknüpften Objekts speichert.
Nach dem Ausführen der Anwendung erstellt die ORM die author-Tabelle:
+-------------+--------------+----------------------------+
| author |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
Es wird auch die Tabelle photo modifizieren, indem eine neue Spalte author hinzugefügt und ein Fremdschlüssel dafür erstellt wird:
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| description | varchar(255) | |
| filename | varchar(255) | |
| isPublished | boolean | |
| authorId | int | FOREIGN KEY |
+-------------+--------------+----------------------------+
Erstellen einer Many-to-Many-Beziehung
Lassen Sie uns eine Many-to-Many-Beziehung erstellen.
Angenommen, ein Foto kann in vielen Alben enthalten sein und jedes Album kann viele Fotos beinhalten.
Erstellen wir eine Album-Klasse:
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
@Entity()
export class Album {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToMany(() => Photo, (photo) => photo.albums)
@JoinTable()
photos: Photo[]
}
@JoinTable ist erforderlich, um anzugeben, dass dies die Eigentümerseite der Beziehung ist.
Fügen wir nun die inverse Seite unserer Beziehung zur Photo-Klasse hinzu:
export class Photo {
// ... other columns
@ManyToMany(() => Album, (album) => album.photos)
albums: Album[]
}
Nachdem Sie die Anwendung ausführen, erstellt die ORM eine album_photos_photo_albums Junction-Tabelle:
+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int | PRIMARY KEY FOREIGN KEY |
| photo_id | int | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
Vergessen Sie nicht, die Album-Klasse bei Ihrer Verbindung in der ORM zu registrieren:
const options: DataSourceOptions = {
// ... other options
entities: [Photo, PhotoMetadata, Author, Album],
}
Fügen wir nun Alben und Fotos in unsere Datenbank ein:
import { AppDataSource } from "./index"
// create a few albums
const album1 = new Album()
album1.name = "Bears"
await AppDataSource.manager.save(album1)
const album2 = new Album()
album2.name = "Me"
await AppDataSource.manager.save(album2)
// create a few photos
const photo = new Photo()
photo.name = "Me and Bears"
photo.description = "I am near polar bears"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true
photo.albums = [album1, album2]
await AppDataSource.manager.save(photo)
// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await AppDataSource.getRepository(Photo).findOne({
where: {
id: 1,
},
relations: {
albums: true,
},
})
loadedPhoto wird folgendermaßen aussehen:
{
id: 1,
name: "Me and Bears",
description: "I am near polar bears",
filename: "photo-with-bears.jpg",
albums: [{
id: 1,
name: "Bears"
}, {
id: 2,
name: "Me"
}]
}
Verwendung des QueryBuilders
Sie können QueryBuilder verwenden, um SQL-Abfragen nahezu beliebiger Komplexität zu erstellen. Beispiel:
const photos = await AppDataSource.getRepository(Photo)
.createQueryBuilder("photo") // First argument is an alias. Alias is what you are selecting - photos. You must specify it.
.innerJoinAndSelect("photo.metadata", "metadata")
.leftJoinAndSelect("photo.albums", "album")
.where("photo.isPublished = true")
.andWhere("(photo.name = :photoName OR photo.name = :bearName)")
.orderBy("photo.id", "DESC")
.skip(5)
.take(10)
.setParameters({ photoName: "My", bearName: "Mishka" })
.getMany()
Diese Abfrage wählt alle veröffentlichten Fotos mit den Namen "My" oder "Mishka" aus. Sie selektiert Ergebnisse ab Position 5 (Paginierungsoffset) und begrenzt die Auswahl auf 10 Ergebnisse (Paginierungslimit). Die Ergebnisse werden nach ID in absteigender Reihenfolge sortiert. Fotoalben werden per Left Join verknüpft, deren Metadaten per Inner Join.
Sie werden den Query Builder in Ihrer Anwendung häufig nutzen. Mehr dazu erfahren Sie hier.
Beispiele
Sehen Sie sich die Beispiele im sample-Verzeichnis für Anwendungsfälle an.
Hier sind einige Repositories zum Ausprobieren:
-
Beispiel zur Verwendung von TypeORM mit JavaScript und Babel
-
Beispiel zur Verwendung von TypeORM mit TypeScript und SystemJS im Browser
-
Beispiel zur Verwendung von TypeORM mit TypeScript und React im Browser
-
Beispiel zur Verwendung von TypeORM mit Nativescript-Angular
-
Beispiel zur Verwendung von TypeORM mit Electron (JavaScript)
-
Beispiel zur Verwendung von TypeORM mit Electron (TypeScript)
Erweiterungen
Es gibt mehrere Erweiterungen, die die Arbeit mit TypeORM und die Integration in andere Module vereinfachen:
-
Generierung von Modellen aus bestehenden Datenbanken - typeorm-model-generator
-
Fixtures-Loader - typeorm-fixtures-cli
-
ER-Diagramm-Generator - typeorm-uml
-
Weiterer ER-Diagramm-Generator - erdia
-
Datenbank erstellen, löschen und mit Beispieldaten füllen - typeorm-extension
-
Automatisches Aktualisieren von
data-source.tsnach Generierung von Migrations/Entities - typeorm-codebase-sync -
Einfache Manipulation von
relations-Objekten - typeorm-relations -
Automatische Generierung von
relationsbasierend auf GraphQL-Abfragen - typeorm-relations-graphql
Mitwirken
Erfahren Sie hier wie Sie mitwirken können und wie Sie Ihre Entwicklungsumgebung hier einrichten.
Dieses Projekt existiert dank all der Menschen, die mitwirken:
Sponsoren
Open-Source-Arbeit ist aufwendig und zeitintensiv. Wenn Sie in TypeORMs Zukunft investieren möchten, können Sie Sponsor werden und unserem Kernteam ermöglichen, mehr Zeit für Verbesserungen und neue Funktionen aufzuwenden. Werden Sie Sponsor
Gold-Sponsoren
Werden Sie Gold-Sponsor und erhalten Sie Premium-Support von unseren Kernentwicklern. Werden Sie Gold-Sponsor