Typeorm es un ORM que puede ejecutarse en NodeJS, navegador, Cordova, PhoneGap, Ionic, React Native, NativeScript, Expo y plataformas de electrones y puede usarse con TypeScript y JavaScript (ES2021). Su objetivo es siempre admitir las últimas funciones de JavaScript y proporcionar características adicionales que lo ayuden a desarrollar cualquier tipo de aplicación que use bases de datos, desde aplicaciones pequeñas con algunas tablas hasta aplicaciones empresariales a gran escala con múltiples bases de datos.
Typeorm admite patrones activos de registros y mapeadores de datos, a diferencia de todos los demás ORM de JavaScript actualmente existentes, lo que significa que puede escribir aplicaciones de alta calidad, acopladas, escalables y mantenibles de la manera más productiva.
Typeorm está muy influenciado por otros Orms, como el marco de hibernación, doctrina y entidad.
Anuncio: el futuro de typeorm
Estamos entusiasmados de compartir nuestra visión para un tormenta revitalizado, una estrategia centrada en construir una base estable, robusta y sostenible a largo plazo. Aprenda cómo estamos estructurando el mantenimiento y reuniendo recursos dedicados para garantizar que topeorme prospere en los próximos años.
Lea el anuncio completo
Y más ...
Con typingorm, sus modelos se ven así:
import { Entity , PrimaryGeneratedColumn , Column } from "typeorm"
@ Entity ( )
export class User {
@ PrimaryGeneratedColumn ( )
id : number
@ Column ( )
firstName : string
@ Column ( )
lastName : string
@ Column ( )
age : number
}
Y su lógica de dominio se ve así:
const userRepository = MyDataSource . 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 )
Alternativamente, si prefiere usar la implementación ActiveRecord
, también puede usarla:
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
}
Y su lógica de dominio se verá así:
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 ( )
Instale el paquete NPM:
npm install typeorm --save
Debe instalar reflect-metadata
shim:
npm install reflect-metadata --save
e importarlo en algún lugar del lugar global de su aplicación (por ejemplo en app.ts
):
import "reflect-metadata"
Es posible que deba instalar tipificaciones de nodo:
npm install @types/node --save-dev
Instale un controlador de base de datos:
para mysql o mariadb
npm install mysql --save
(también puede instalar mysql2
)
para PostgreSQL o cucarachdb
npm install pg --save
para sqlite
npm install sqlite3 --save
para Microsoft SQL Server
npm install mssql --save
para sql.js
npm install sql.js --save
para Oracle
npm install oracledb --save
Para que el controlador de Oracle funcione, debe seguir las instrucciones de instalación desde su sitio.
Para SAP HANA
npm install @sap/hana-client
npm install hdb-pool
El soporte de SAP HANA hizo posible por el patrocinio de Neptuno Software.
para Google Cloud Splawer
npm install @google-cloud/spanner --save
Proporcione credenciales de autenticación a su código de aplicación configurando la variable de entorno GOOGLE_APPLICATION_CREDENTIALS
:
# Linux/macOS
export GOOGLE_APPLICATION_CREDENTIALS= " KEY_PATH "
# Windows
set GOOGLE_APPLICATION_CREDENTIALS=KEY_PATH
# Replace KEY_PATH with the path of the JSON file that contains your service account key.
Para usar Spanner con el emulador, debe establecer la variable de entorno SPANNER_EMULATOR_HOST
:
# Linux/macOS
export SPANNER_EMULATOR_HOST=localhost:9010
# Windows
set SPANNER_EMULATOR_HOST=localhost:9010
para MongoDB (experimental)
npm install mongodb@^5.2.0 --save
Para NativeScript , React-Native y Cordova
Verifique la documentación de las plataformas compatibles
Instale solo uno de ellos, dependiendo de la base de datos que use.
Además, asegúrese de estar utilizando la versión 4.5 o superior de TypeScript, y ha habilitado la siguiente configuración en tsconfig.json
:
"emitDecoratorMetadata" : true ,
"experimentalDecorators" : true ,
También es posible que deba habilitar es6
en la sección lib
de las opciones del compilador, o instalar es6-shim
desde @types
.
La forma más rápida de comenzar con Typeorm es usar sus comandos CLI para generar un proyecto de inicio. El inicio rápido funciona solo si está utilizando Typeorm en una aplicación NodeJS. Si está utilizando otras plataformas, continúe con la guía paso a paso.
Para crear un nuevo proyecto usando CLI, ejecute el siguiente comando:
npx typeorm init --name MyProject --database postgres
Donde name
es el nombre de su proyecto y database
es la base de datos que utilizará. La base de datos puede ser uno de los siguientes valores: mysql
, mariadb
, postgres
, cockroachdb
, sqlite
, mssql
nativescript
spanner
, oracle
, MongoDB, cordova
, react-native
sap
expo
, mongodb
.
Este comando generará un nuevo proyecto en el directorio MyProject
con los siguientes archivos:
MyProject
├── src // place of your TypeScript code
│ ├── entity // place where your entities (database models) are stored
│ │ └── User.ts // sample entity
│ ├── migration // 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
También puede ejecutar
typeorm init
en un proyecto de nodo existente, pero tenga cuidado: puede anular algunos archivos que ya tiene.
El siguiente paso es instalar nuevas dependencias del proyecto:
cd MyProject
npm install
Después de que tenga todas las dependencias instaladas, edite el archivo data-source.ts
y coloque sus propias opciones de configuración de conexión de base de datos allí:
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 : [ ] ,
} )
En particular, la mayoría de las veces solo necesitará configurar host
, username
, password
, database
y tal vez las opciones port
.
Una vez que termine con la configuración y todos los módulos de nodo están instalados, puede ejecutar su aplicación:
npm start
Eso es todo, su aplicación debe ejecutar e insertar con éxito un nuevo usuario en la base de datos. Puede continuar trabajando con este proyecto e integrar otros módulos que necesita y comenzar a crear más entidades.
Puede generar un proyecto ESM ejecutando
npx typeorm init --name MyProject --database postgres --module esm
Command.
Puede generar un proyecto aún más avanzado con Express instalado ejecutando
npx typeorm init --name MyProject --database mysql --express
command.
Puede generar un archivo Docker-Compose ejecutando
npx typeorm init --name MyProject --database postgres --docker
Command.
¿Qué esperas de Orm? En primer lugar, espera que cree tablas de bases de datos para usted y busque / inserte / actualice / elimine sus datos sin el dolor de tener que escribir muchas consultas SQL apenas mantenibles. Esta guía le mostrará cómo configurar typeorm desde cero y hacer que haga lo que espera de un ORM.
Trabajar con una base de datos comienza con la creación de tablas. ¿Cómo le dice a typeorm que cree una tabla de base de datos? La respuesta es: a través de los modelos. Sus modelos en su aplicación son las tablas de su base de datos.
Por ejemplo, tienes un modelo Photo
:
export class Photo {
id : number
name : string
description : string
filename : string
views : number
isPublished : boolean
}
Y quieres almacenar fotos en tu base de datos. Para almacenar cosas en la base de datos, primero, necesita una tabla de base de datos, y las tablas de bases de datos se crean a partir de sus modelos. No todos los modelos, sino solo aquellos que define como entidades .
La entidad es su modelo decorada por un decorador @Entity
. Se creará una tabla de base de datos para dichos modelos. Trabajas con entidades en todas partes en Typeorm. Puede cargar/insertar/actualizar/eliminar y realizar otras operaciones con ellos.
Hagamos que nuestro modelo Photo
sea una entidad:
import { Entity } from "typeorm"
@ Entity ( )
export class Photo {
id : number
name : string
description : string
filename : string
views : number
isPublished : boolean
}
Ahora, se creará una tabla de base de datos para la entidad Photo
y podremos trabajar con ella en cualquier lugar de nuestra aplicación. Hemos creado una tabla de base de datos, sin embargo, ¿qué tabla puede existir sin columnas? Creemos algunas columnas en nuestra tabla de base de datos.
Para agregar columnas de base de datos, simplemente necesita decorar las propiedades de una entidad que desea convertir en una columna con un decorador @Column
.
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
}
Ahora id
, name
, description
, filename
, views
y columnas isPublished
se agregarán a la tabla photo
. Los tipos de columnas en la base de datos se infieren de los tipos de propiedades que usó, por ejemplo, number
de EG se convertirá en integer
, string
en varchar
, boolean
en bool
, etc., pero puede usar cualquier tipo de columna que su base de datos sea especificando explícitamente un tipo de columna en el tipo de columna @Column
Decorator.
Generamos una tabla de base de datos con columnas, pero queda una cosa. Cada tabla de base de datos debe tener una columna con una clave primaria.
Cada entidad debe tener al menos una columna de clave primaria. Este es un requisito y no puede evitarlo. Para que una columna sea una clave principal, debe usar el decorador @PrimaryColumn
.
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
}
Ahora, supongamos que desea que su columna de identificación se genere automáticamente (esto se conoce como columna Auto-Increment / Sequence / Serial / Generated Identity). Para hacer eso, debe cambiar el decorador @PrimaryColumn
a un decorador @PrimaryGeneratedColumn
:
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
}
A continuación, solucionemos nuestros tipos de datos. De manera predeterminada, la cadena se asigna a un tipo similar a Varchar (255) (dependiendo del tipo de base de datos). El número se asigna a un tipo entero (dependiendo del tipo de base de datos). No queremos que todas nuestras columnas sean Varchar o enteros limitados. Configurar los tipos de datos correctos:
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
}
Los tipos de columnas son específicos de la base de datos. Puede configurar cualquier columna tipo que sea compatible con la base de datos. Puede encontrar más información sobre los tipos de columnas compatibles aquí.
DataSource
Ahora, cuando se cree nuestra entidad, creemos el archivo index.ts
y configuremos nuestra DataSource
allí:
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
AppDataSource . initialize ( )
. then ( ( ) => {
// here you can start to work with your database
} )
. catch ( ( error ) => console . log ( error ) )
Estamos utilizando Postgres en este ejemplo, pero puede usar cualquier otra base de datos compatible. Para usar sap
base de datos, simplemente cambie el type
de opciones al tipo de base de datos que está utilizando: mysql
, mariadb
, postgres
, cockroachdb
, sqlite
, mssql
, oracle
, SAP, spanner
, cordova
, nativescript
, react-native
, expo
o mongodb
. También asegúrese de usar su propio host, puerto, nombre de usuario, contraseña y configuración de base de datos.
Agregamos nuestra entidad fotográfica a la lista de entidades para esta fuente de datos. Cada entidad que está utilizando en su conexión debe estar en la lista allí.
Configuración synchronize
Asegúrese de que sus entidades se sincronizarán con la base de datos, cada vez que ejecuta la aplicación.
Ahora, si ejecuta su index.ts
, se inicializará una conexión con la base de datos y se creará una tabla de base de datos para sus fotos.
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(100) | |
| description | text | |
| filename | varchar(255) | |
| views | int(11) | |
| isPublished | boolean | |
+-------------+--------------+----------------------------+
Ahora creemos una nueva foto para guardarla en la base de datos:
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 )
Una vez que se guarde su entidad, obtendrá una identificación recién generada. save
el método Devuelve una instancia del mismo objeto que le pasa. No es una copia nueva del objeto, modifica su "ID" y la devuelve.
Acabamos de crear una nueva foto y la guardamos en la base de datos. Usamos EntityManager
para guardarlo. Usando Entity Manager, puede manipular cualquier entidad en su aplicación. Por ejemplo, cargamos nuestra entidad guardada:
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
será una variedad de objetos fotográficos con los datos cargados desde la base de datos.
Obtenga más información sobre EntityManager aquí.
Ahora refactoremos nuestro código y usemos Repository
en lugar de EntityManager
. Cada entidad tiene su propio repositorio que maneja todas las operaciones con su entidad. Cuando trata mucho con entidades, los repositorios son más convenientes de usar que los entidad:
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 )
Obtenga más información sobre el repositorio aquí.
Probemos más operaciones de carga utilizando el repositorio:
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 )
Ahora cargamos una sola foto desde la base de datos, actualízala y guárdela:
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 )
Ahora la foto con id = 1
se actualizará en la base de datos.
Ahora eliminemos nuestra foto de la base de datos:
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 )
Ahora la foto con id = 1
se eliminará de la base de datos.
Creemos una relación uno a uno con otra clase. Creemos una nueva clase en PhotoMetadata.ts
. Se supone que esta clase Photometadata contiene la metainformación adicional de nuestra foto:
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
}
Aquí, estamos usando un nuevo decorador llamado @OneToOne
. Nos permite crear una relación uno a uno entre dos entidades. type => Photo
es una función que devuelve la clase de la entidad con la que queremos hacer nuestra relación. Nos vemos obligados a usar una función que devuelva una clase, en lugar de usar la clase directamente, debido a los detalles del idioma. También podemos escribirlo como () => Photo
, pero usamos type => Photo
como una convención para aumentar la legibilidad del código. La variable tipo en sí no contiene nada.
También agregamos un decorador @JoinColumn
, que indica que este lado de la relación será dueño de la relación. Las relaciones pueden ser unidireccionales o bidireccionales. Solo un lado de Relacional puede ser poseer. Se requiere usar @JoinColumn
decorador en el lado del propietario de la relación.
Si ejecuta la aplicación, verá una tabla recién generada y contendrá una columna con una clave extranjera para la relación fotográfica:
+-------------+--------------+----------------------------+
| photo_metadata |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| height | int(11) | |
| width | int(11) | |
| comment | varchar(255) | |
| compressed | boolean | |
| orientation | varchar(255) | |
| photoId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
Ahora guardemos una foto y sus metadatos y adjunto entre sí.
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 )
// 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" ,
)
Las relaciones pueden ser unidireccionales o bidireccionales. Actualmente, nuestra relación entre Photometadata y Foto es unidireccional. El propietario de la relación es Photometadata, y la foto no sabe nada sobre Photometadata. Esto hace que sea complicado acceder a Photometadata desde el lado de la foto. Para solucionar este problema, debemos agregar una relación inversa y hacer relaciones entre los fotometadatos y la foto bidireccional. Modificemos nuestras entidades:
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
es una función que devuelve el nombre del lado inverso de la relación. Aquí mostramos que la propiedad de metadatos de la clase de fotos es donde almacenamos Photometadata en la clase de fotos. En lugar de pasar una función que devuelve una propiedad de la foto, alternativamente, podría pasar simplemente una cadena al decorador @OneToOne
, como "metadata"
. Pero utilizamos este enfoque tipo función para facilitar nuestra refactorización.
Tenga en cuenta que debemos usar el decorador @JoinColumn
solo en un lado de una relación. Cualquiera que sea el lado que ponga este decorador, será el lado de la relación. El lado propietario de una relación contiene una columna con una clave extranjera en la base de datos.
Si usa ESM en su proyecto TypeScript, debe usar el tipo de contenedor Relation
en propiedades para evitar problemas de dependencia circular. Modificemos nuestras entidades:
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 >
}
Ahora cargamos nuestra foto y sus metadatos fotográficos en una sola consulta. Hay dos formas de hacerlo: utilizando los métodos find*
o utilizando la funcionalidad QueryBuilder
. Usemos el método find*
primero. Los métodos find*
le permiten especificar un objeto con la interfaz FindOneOptions
/ FindManyOptions
.
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 ,
} ,
} )
Aquí, las fotos contendrán una variedad de fotos de la base de datos, y cada foto contendrá sus metadatos fotográficos. Obtenga más información sobre Buscar opciones en esta documentación.
Usar opciones Find es buena y muy simple, pero si necesita una consulta más compleja, debe usar QueryBuilder
. QueryBuilder
permite que las consultas más complejas se usen de manera elegante:
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
permite la creación y ejecución de consultas SQL de casi cualquier complejidad. Cuando trabaje con QueryBuilder
, piense que está creando una consulta SQL. En este ejemplo, "Photo" y "Metadata" son alias aplicados a fotos seleccionadas. Utiliza alias para acceder a columnas y propiedades de los datos seleccionados.
Podemos configurar opciones de cascada en nuestras relaciones, en los casos en que queremos que nuestro objeto relacionado se guarde cada vez que se guarde el otro objeto. Cambiemos un poco el decorador de nuestras fotos @OneToOne
:
export class Photo {
// ... other columns
@ OneToOne ( ( ) => PhotoMetadata , ( metadata ) => metadata . photo , {
cascade : true ,
} )
metadata : PhotoMetadata
}
El uso cascade
nos permite no guardar las fotos por separado y guardar los objetos de metadatos por separado ahora. Ahora podemos simplemente guardar un objeto fotográfico, y el objeto de metadatos se guardará automáticamente debido a las opciones de cascada.
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." )
Observe que ahora establecemos la propiedad metadata
de la foto, en lugar de la propiedad photo
de los metadatos como antes. La función cascade
solo funciona si conectas la foto a sus metadatos desde el lado de la foto. Si establece el lado de los metadatos, los metadatos no se guardarían automáticamente.
Creemos una relación de muchos a uno/uno a muchos. Digamos que una foto tiene un autor, y cada autor puede tener muchas fotos. Primero, creemos una clase Author
:
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
contiene un lado inverso de una relación. OneToMany
es siempre un lado inverso de la relación, y no puede existir sin ManyToOne
en el otro lado de la relación.
Ahora agregemos el lado del propietario de la relación en la entidad fotográfica:
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
}
En las relaciones de muchos a uno / uno a muchos, el lado del propietario siempre es de muchos a uno. Significa que la clase que usa @ManyToOne
almacenará la identificación del objeto relacionado.
Después de ejecutar la aplicación, el ORM creará la tabla author
:
+-------------+--------------+----------------------------+
| author |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
También modificará la tabla photo
, agregó una nueva columna author
y creará una clave extranjera para ella:
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| description | varchar(255) | |
| filename | varchar(255) | |
| isPublished | boolean | |
| authorId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
Creemos una relación de muchos a muchos. Digamos que una foto puede estar en muchos álbumes, y cada álbum puede contener muchas fotos. Creemos una clase Album
:
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 [ ]
}
Se requiere @JoinTable
para especificar que este es el lado del propietario de la relación.
Ahora agregemos el lado inverso de nuestra relación con la clase Photo
:
export class Photo {
// ... other columns
@ ManyToMany ( ( ) => Album , ( album ) => album . photos )
albums : Album [ ]
}
Después de ejecutar la aplicación, el ORM creará una tabla de Junction de álbum_photos_photo_albums :
+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int(11) | PRIMARY KEY FOREIGN KEY |
| photo_id | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
No olvides registrar la clase Album
con tu conexión en el ORM:
const options : DataSourceOptions = {
// ... other options
entities : [ Photo , PhotoMetadata , Author , Album ] ,
}
Ahora insertemos álbumes y fotos en nuestra base de datos:
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
será igual a:
{
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"
} ]
}
Puede usar QueryBuilder para construir consultas SQL de casi cualquier complejidad. Por ejemplo, puedes hacer esto:
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 ( )
Esta consulta selecciona todas las fotos publicadas con "My" o "Mishka". Seleccionará los resultados de la posición 5 (desplazamiento de paginación) y seleccionará solo 10 resultados (límite de paginación). El resultado de la selección será ordenado por ID en orden descendente. Los álbumes de fotos se unirán y sus metadatos serán internos unidos.
Usará mucho el creador de consultas en su aplicación. Obtenga más información sobre QueryBuilder aquí.
Eche un vistazo a las muestras en muestra para ver ejemplos de uso.
Hay algunos repositorios con los que puede clonar y comenzar:
Hay varias extensiones que simplifican trabajar con typeorm e integrarlo con otros módulos:
data-source.ts
después de generar migraciones/entidades-Typeorm-Codebase-Syncrelations
: relaciones de tipos tormentasrelations
basadas en una consulta GraphQL-Typeorm-Relations-GraphQL Aprenda sobre la contribución aquí y cómo configurar su entorno de desarrollo aquí.
Este proyecto existe gracias a todas las personas que contribuyen:
El código abierto es difícil y requiere mucho tiempo. Si desea invertir en el futuro de Typeorm, puede convertirse en un patrocinador y permitir que nuestro equipo principal pase más tiempo en las mejoras de Typeorm y las nuevas características. Convertirse en patrocinador
Conviértete en un patrocinador de oro y obtiene el soporte técnico premium de nuestros contribuyentes principales. Convertirse en patrocinador de oro