TypeORM
This document is obsolete from v3.4.0.
TypeORM is the most mature object relation mapper (ORM
) in the existing community of node.js
. Midway and TypeORM match to make development easier.
Related information:
Description | |
---|---|
Can be used for standard projects | ✅ |
Can be used for Serverless | ✅ |
Can be used for integration | ✅ |
Installation Components
Install orm components to provide database ORM capabilities.
$ npm i @midwayjs/orm@3 typeorm --save
Or reinstall the following dependencies in package.json
.
{
"dependencies": {
"@midwayjs/orm": "^3.0.0",
"typeorm": "~0.3.0 ",
// ...
},
"devDependencies": {
// ...
}
}
Enable component
Introducing orm components in src/configuration.ts
, an example is as follows.
// configuration.ts
import { Configuration } from '@midwayjs/core';
import * as orm from '@midwayjs/orm';
import { join } from 'path';
@Configuration({
imports: [
// ...
orm // load orm components
],
importConfigs: [
join(__dirname, './config')
]
})
export class ContainerConfiguratin {
}
Install database Driver
The commonly used database drivers are as follows. Select the database type to install the corresponding connection:
# for MySQL or MariaDB, you can also use mysql2 instead
npm install mysql --save
npm install mysql2 --save
# for PostgreSQL or CockroachDB
npm install pg --save
# for SQLite
npm install sqlite3 --save
# for Microsoft SQL Server
npm install mssql --save
# for SQL .js
npm install SQL .js --save
# for Oracle
npm install oracledb --save
# for MongoDB(experimental)
npm install mongodb --save
To make the Oracle driver work, you need to follow the installation instructions from their site.
Simple directory structure
Let's take a simple project as an example. Please refer to other structures yourself.
MyProject
├── src // TS root directory
│ ├── config
│ │ └── config.default.ts // Application Profile
│ ├── entity // entity (database Model) directory
│ │ └── photo.ts // entity file
│ │ └── photoMetadata.ts
│ ├── configuration.ts // Midway configuration file
│ └── service // Other service directory
├── .gitignore
├── package.json
├── README.md
└── tsconfig.json
Here, our database entities are mainly located in the entity
directory (non-mandatory). This is a simple convention.
Getting Started
Next, we will take mysql as an example.
1. Create Model
We associate the model with the database. The model in the application is the database table. In TypeORM, the model is bound to the entity. Each Entity file is a Model and an Entity.
In the example, you need an entity. Let's take photo
as an example. Create an entity directory and add the entity file photo.ts
to the entity directory. A simple entity is as follows.
// entity/photo.ts
export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
isPublished: boolean;
}
Note that each attribute of the entity file here is actually one-to-one corresponding to the database table. Based on the existing database table, we add content up.
2. Add a solid model decorator
We use EntityModel
to define an entity model class.
// entity/photo.ts
import { EntityModel } from '@midwayjs/orm';
@EntityModel('photo')
export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
isPublished: boolean;
}
Note that the EntityModel here is a special decorator packaged by midway, in order to better combine with midway. Please do not directly use Entity in the typeorm.
If the table name is different from the current entity name, you can specify it in the parameter.
// entity/photo.ts
import { EntityModel } from '@midwayjs/orm';
@EntityModel('photo_table_name')
export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
isPublished: boolean;
}
These entity columns can also be generated using typeorm_generator tools.
3. Add database columns
Attributes are decorated with the @Column
decorator provided by typeorm, and each attribute corresponds to a column.
// entity/photo.ts
import { EntityModel } from '@midwayjs/orm';
import { Column } from 'typeorm';
@EntityModel()
export class Photo {
@Column()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
The id
, name
, description
, filename
, views
, isPublished
columns are added to the photo
table. The column types in the database are inferred according to the attribute types you use, for example, number will be converted to integers, strings will be converted to varchar, boolean values will be converted to bool, and so on. However, you can use any column type supported by the database by explicitly specifying the column type in the @Column
decorator.
We generated a database table with columns, but there is one thing left. Each database table must have a column with a primary key.
Database columns include more column options (ColumnOptions), such as modifying column names, specifying column types, and column lengths. For more options, see the [official documentation](https://github.com/typeorm/typeorm/blob/master/docs/zh_CN/entities.md#%E5%88% 97% E9%80% 89% E9%A1%B9).
4. Create a primary key column
Each entity must have at least one primary key column. To make a column a primary key, you need to use the @PrimaryColumn
decorator.
// entity/photo.ts
import { EntityModel } from '@midwayjs/orm';
import { Column, PrimaryColumn } from 'typeorm';
@EntityModel()
export class Photo {
@PrimaryColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
5. Create a self-increasing primary key column
Now, if you want to set the self-increasing id column, you need to change the @PrimaryColumn
decorator to the @PrimaryGeneratedColumn
decorator:
// entity/photo.ts
import { EntityModel } from '@midwayjs/orm';
import { Column, PrimaryGeneratedColumn } from 'typeorm';
@EntityModel()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
6. Column data type
Next, let's adjust the data type. By default, strings map to types similar to varchar(255)
(depending on the database type). Number is mapped to an integer-like type (depending on the database type). However, we do not want all columns to be limited to varchars or integers. Some changes can be made at this time.
// entity/photo.ts
import { EntityModel } from '@midwayjs/orm';
import { Column, PrimaryGeneratedColumn } from 'typeorm';
@EntityModel()
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;
}
Example, different column names
@Column({
length: 100
name: 'custom_name'
})
name: string;
In addition, there are several special column types that can be used:
@CreateDateColumn
is a special column that automatically inserts dates for entities.- The
@UpdateDateColumn
is a special column that automatically updates the entity date each time the entity manager or save of the repository is called. - The
@VersionColumn
is a special column that automatically increases the entity version (increment number) each time the entity manager or save of the repository is called. @DeleteDateColumn
is a special column that automatically sets the deletion time of the entity when soft-delete is called.
The column type is database-specific. You can set any column type supported by the database. For more information about supported column types, see here.
CreateDateColumn
and UpdateDateColumn
rely on the insertion date function of creating the default data on the column when the table structure is synchronized for the first time. If the table is created by yourself, you need to add the default data to the column.
7. Configure connection information
Please refer to the Configuration chapter to add configuration files.
Then configure the database connection information in config.default.ts
.
// src/config/config.default.ts
export default {
// ...
orm: {
/**
* Single database instance
*/
type: 'mysql',
host: '',
port: 3306
username: '',
password: '',
database: undefined
synchronize: false, // If the table does not exist for the first time, you can write true if you need synchronization.
logging: false
},
}
utc time is stored by default (recommended).
Time zone can also be configured (not recommended)
// src/config/config.default.ts
export default {
// ...
orm: {
// ...
timezone: '+08:00',
},
}
You can use other database types for this type
field, including mysql
, mariadb
, postgres
, cockroachdb
, sqlite
, mssql
, oracle
, cordova
, nativescript
, react-native
, expo
, or mongodb
For example, sqlite only needs the following information.
// src/config/config.default.ts
export default {
// ...
orm: {
type: 'sqlite',
database: path.join(__dirname, '../../test.sqlite')
synchronize: true
logging: true
},
}
Note: synchronize fields are used to synchronize table structures. It is not safe to use synchronize: true
for production mode synchronization. Please set this field to false after going online.
8. Use Model to insert database data
In common Midway files, use the @InjectEntityModel
decorator to inject our configured Model. All we need to do is:
- Create entity objects
- Execute the
save()
- Execute the
import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from '../entity/photo';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
// save
async savePhoto() {
// create a entity object
let 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;
// save entity
const photoResult = await this.photoModel.save(photo);
// save success
console.log('photo id =', photoResult.id);
}
}
9. Query Data
For more information, see find documentation.
The query API has changed since typeorm@0.3.0.
import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from '../entity/photo';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
// find
async findPhotos() {
// find All
let allPhotos = await this.photoModel.find(); // v0.2.x
let allPhotos = await this.photoModel.find({}); // v0.3.x
console.log("All photos from the db: ", allPhotos);
// find first
let firstPhoto = await this.photoModel.findOne(1);
let firstPhoto = await this.photoModel.findOne({ // v0.3.x
where: {
id: 1
}
});
console.log("First photo from the db: ", firstPhoto);
// find one by name
// v0.2.x
let meAndBearsPhoto = await this.photoModel.findOne({ name: "Me and Bears" });
// v0.3.x
let meAndBearsPhoto = await this.photoModel.findOne({
where: { name: "Me and Bears"}
});
console.log("Me and Bears photo from the db: ", meAndBearsPhoto);
// find by views
// v0.2.x
let allViewedPhotos = await this.photoModel.find({ views: 1 });
// v0.3.x
let allViewedPhotos = await this.photoModel.find({
where: { views: 1}
});
console.log("All viewed photos: ", allViewedPhotos);
// v0.2.x
let allViewedPhotos = await this.photoModel.find({ views: 1 });
// v0.3.x
let allPublishedPhotos = await this.photoModel.find({
where: { isPublished: true}
});
console.log("All published photos: ", allPublishedPhotos);
// find and get count
// v0.2.x
let [allPhotos, photosCount] = await this.photoModel.findAndCount();
// v0.3.x
let [allPhotos, photosCount] = await this.photoModel.findAndCount({});
console.log("All photos: ", allPhotos);
console.log("Photos count: ", photosCount);
}
}
10. Update the database
Now, let's load a photo from the database, update it and save it.
import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from '../entity/photo';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
async updatePhoto() {
let photoToUpdate = await this.photoModel.findOne(1);
photoToUpdate.name = "Me, my friends and polar bears";
await this.photoModel.save(photoToUpdate);
}
}
11. Delete data
import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from '../entity/photo';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
async updatePhoto() {
/*...*/
let photoToRemove = await this.photoModel.findOne(1); // typeorm@0.2.x
await this.photoModel.remove(photoToRemove);
}
}
Now, Photo with ID = 1 will be deleted from the database.
There is also a soft deletion method.
await this.photoModel.softDelete(1);
12. Create a one-to-one association
Let's create a one-to-one relationship with another class. Let's create a new class in entity/photoMetadata.ts
. This class contains additional meta-information for photo.
import { Column, PrimaryGeneratedColumn, OneToOne, JoinColumn } from 'typeorm';
import { EntityModel } from '@midwayjs/orm';
import { Photo } from "./photo";
@EntityModel()
export class PhotoMetadata {
@PrimaryGeneratedColumn()
id: number;
@Column("int")
height: number;
@Column("int")
width: number;
@Column()
orientation: string;
@Column()
compressed: boolean;
@Column()
comment: string;
@OneToOne(type => Photo)
@JoinColumn()
photo: Photo;
}
Here, we use a new fitting called @OneToOne
. It allows us to create a one-to-one relationship between two entities. type => photo
is a function that returns the class of the entity with which we want to establish a relationship.
Due to the particularity of the language, we are forced to use a function that returns the class instead of using the class directly. You can also write it as () => Photo
, but we use type => Photo
as a convention to improve the readability of the code. The type variable itself contains nothing.
We also added an @JoinColumn
decorator, which indicates that this side of the relationship will have the relationship. Relationships can be one-way or two-way. The relationship can only be owned by one party. The owner side of the relationship needs to use the @JoinColumn decorator. If you run the application, you will see a newly generated table that will contain a column containing foreign keys for the Photo relationship.
+-------------+--------------+----------------------------+
| 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 |
+-------------+--------------+----------------------------+
Next we will associate them in the code.
import { Provide, Inject, Func } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from './entity/photo';
import { PhotoMetadata } from './entity/photoMetadata';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
@InjectEntityModel(PhotoMetadata)
photoMetadataModel: Repository<PhotoMetadata>;
async updatePhoto() {
// create a photo
let 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 a photo metadata
let 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
// first we should save a photo
await this.photoModel.save(photo);
// photo is saved. Now we need to save a photo metadata
await this.photoMetadataModel.save(metadata);
// done
console.log("Metadata is saved, and relation between metadata and photo is created in the database too");
}
}
13. Reverse relation mapping
Relational mapping can be one-way or two-way. When the relationship between PhotoMetadata and Photo is one-way. The owner of the relationship is PhotoMetadata, and Photo knows nothing about PhotoMetadata. This complicates accessing PhotoMetadata from the Photo side. To solve this problem, we add a reverse relational mapping to make the PhotoMetadata and Photo a two-way association. Let's modify our entity.
import { EntityModel } from '@midwayjs/orm';
import { Column, PrimaryGeneratedColumn, OneToOne, JoinColumn } from 'typeorm';
import { Photo } from './photo';
@EntityModel()
export class PhotoMetadata {
/* ... other columns */
@OneToOne(type => Photo, photo => photo.metadata)
@JoinColumn()
photo: Photo;
}
import { EntityModel } from '@midwayjs/orm';
import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from 'typeorm';
import { PhotoMetadata } from './photoMetadata';
@EntityModel()
export class Photo {
/* ... other columns */
@OneToOne(type => PhotoMetadata, photoMetadata => photoMetadata.photo)
metadata: PhotoMetadata;
}
photo => photo.metadata
is a function that returns a reverse mapping relationship. Here, we explicitly declare the metadata property of the Photo class to associate PhotoMetadata. In addition to passing functions that return the photo property, you can also pass strings directly to the @OneToOne
decorator, such as "metadata"
. But we use this method of function callback to make our code writing easier.
Note that the @JoinColumn
decorator will only be used on one side of the relationship map. No matter which side of this decorator you place, you are the owner of the relationship. The owner of the relationship contains columns with foreign keys in the database.
14. Load objects and their dependencies
Now, let's try to load out Photo and PhotoMetadata together in a single query. There are two ways to do this, using the find *
method or using the QueryBuilder
function. Let's use the find *
method first. The find*
methods allow you to specify objects using the FindOneOptions
/ FindManyOptions
interfaces.
import { Provide, Inject, Func } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from './entity/photo';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
// find
async findPhoto() {
/*...*/
let photos = await this.photoModel.find({ relations: [ 'metadata' ] }); // typeorm@0.2.x
}
}
Here, the value of photos is an array containing the query results for the entire database, and each photo object contains its associated metadata property. Learn more about Find Options
in this documentation.
Find Options
is simple, but if you need more complex queries, you should use QueryBuilder
instead. QueryBuilder
allows more complex queries to be used in an elegant way.
import { Provide, Inject, Func } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from './entity/photo';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
// find
async findPhoto() {
/*...*/
let photos = await this.photoModel
.createQueryBuilder('photo')
.innerJoinAndSelect('photo.metadata', 'metadata')
.getMany();
}
}
QueryBuilder
allows the creation and execution of almost any complex SQL query. When using QueryBuilder
, think like creating SQL queries. In this example, "photo" and "metadata" are aliases applied to the selected photos. You can use aliases to access the columns and properties of the selected data.
15. Use cascade operations to automatically save associated objects
Cascade can be set in the relationship when we want to automatically save the associated object every time we save another object. Let's slightly change the @OneToOne
decorator of the photo.
export class Photo {
/// ... other columns
@OneToOne(type => PhotoMetadata, metadata => metadata.photo, {
cascade: true,
})
metadata: PhotoMetadata;
}
Using cascade
allows us to no longer save Photo and PhotoMetadata separately now. Due to the cascade option, metadata objects will be saved automatically.
import { Provide, Inject, Func } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from './entity/photo';
import { PhotoMetadata } from './entity/photoMetadata';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
async updatePhoto() {
// create photo object
let 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
let 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
// save a photo also save the metadata
await this.photoModel.save(photo);
// done
console.log("Photo is saved, photo metadata is saved too");
}
}
Note that we now set the metadata of Photo instead of setting the Photo attribute of metadata as before. This is only valid when you connect Photo to the PhotoMetadata from the Photo side. If set on the PhotoMetadata side, it will not be saved automatically.
16. Create many-to-one/one-to-many associations
Let's create a many-to-one/one-to-many relationship. Suppose a photo has an author, and each author can have many photos. First, let's create an Author class:
import { EntityModel } from '@midwayjs/orm';
import { Column, PrimaryGeneratedColumn, OneToMany, JoinColumn } from "typeorm";
import { Photo } from './entity/photo';
@EntityModel()
export class Author {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(type => Photo, photo => photo.author) // note: we will create author property in the Photo class below
photos: Photo[];
}
Author
contains a reverse relationship. OneToMany
and ManyToOne
need to appear in pairs.
Now, add the owner of the relationship to the Photo entity:
import { EntityModel } from '@midwayjs/orm';
import { Column, PrimaryGeneratedColumn, ManyToOne } from "typeorm";
import { PhotoMetadata } from "./photoMetadata";
import { Author } from "./author";
@Entity()
export class Photo {
/* ... other columns */
@ManyToOne(type => Author, author => author.photos)
author: Author;
}
In a many-to-one/one-to-many relationship, the owner is always many-to-one. This means that the class using the @ManyToOne
will store the ID of the related object.
After the application is run, ORM creates the author
table:
+-------------+--------------+----------------------------+
| author |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
It also modifies the photo
table, adds a new author
column, and creates a foreign key for it:
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| description | varchar(255) | |
| filename | varchar(255) | |
| isPublished | boolean | |
| authorId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
17. Create many-to-many associations
Let's create a many-to-one/many-to-many relationship. Suppose a photo can be in many albums, and each album can contain many photos. Let's create an Album
class.
import { EntityModel } from '@midwayjs/orm';
import { PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from "typeorm";
@EntityModel()
export class Album {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(type => Photo, photo => photo.albums)
@JoinTable()
photos: Photo[];
}
@JoinTable
is used to indicate that this is the owner of the relationship.
Now, add the reverse association to Photo
.
export class Photo {
/// ... other columns
@ManyToMany(type => Album, album => album.photos)
albums: Album[];
}
After running the application, ORM will create a album_photos_photo_albums join table:
+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int(11) | PRIMARY KEY FOREIGN KEY |
| photo_id | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
Now, let's insert albums and photos into the database:
import { Provide, Inject, Func } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/orm';
import { Photo } from './entity/photo';
import { PhotoMetadata } from './entity/photoMetadata';
import { Repository } from 'typeorm';
@Provide()
export class PhotoService {
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
@InjectEntityModel(Album)
albumModel: Repository<Album>
async updatePhoto() {
// create a few albums
let album1 = new Album();
album1.name = "Bears";
await this.albumModel.save(album1);
let album2 = new Album();
album2.name = "Me";
await this.albumModel.save(album2);
// create a few photos
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.albums = [album1, album2];
await this.photoModel.save(photo);
// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await this.photoModel.findOne(1, { relations: ["albums"] }); // typeorm@0.2.x
}
}
The loadedPhoto
value is:
{
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"
}]
}
18. Use QueryBuilder
You can use QueryBuilder to build almost any complex SQL query. For example, you can do this:
let photos = await this.photoModel
.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();
The query selects all published photos with "My" or "Mishka" names. It will return results (paging offset) from position 5, and only 10 results (paging limit) will be selected. The selection results will be sorted in descending order of ID. The photo album will be left-Joined and metadata will be automatically associated.
You will use query generators extensively in your application. Learn more about QueryBuilder here.
19. Event Subscriber
typeorm provides an event subscription mechanism to facilitate log output when doing some database operations. For this reason, midway provides a EventSubscriberModel
decorator to label event subscription classes with the following code.
import { Provide } from '@midwayjs/core';
import { EventSubscriberModel } from '@midwayjs/orm';
import { EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent } from 'typeorm';
@Provide()
@EventSubscriberModel()
export class EverythingSubscriber implements EntitySubscriberInterface {
/**
* Called before entity insertion.
*/
beforeInsert(event: InsertEvent<any>) {
console.log('BEFORE ENTITY INSERTED:', event.entity);
}
/**
* Called before entity insertion.
*/
beforeUpdate(event: UpdateEvent<any>) {
console.log('BEFORE ENTITY UPDATED:', event.entity);
}
/**
* Called before entity insertion.
*/
beforeRemove(event: RemoveEvent<any>) {
console.log('BEFORE ENTITY WITH ID ${event.entityId} REMOVED:', event.entity);
}
/**
* Called after entity insertion.
*/
afterInsert(event: InsertEvent<any>) {
console.log('AFTER ENTITY INSERTED:', event.entity);
}
/**
* Called after entity insertion.
*/
afterUpdate(event: UpdateEvent<any>) {
console.log('AFTER ENTITY UPDATED:', event.entity);
}
/**
* Called after entity insertion.
*/
afterRemove(event: RemoveEvent<any>) {
console.log('AFTER ENTITY WITH ID ${event.entityId} REMOVED:', event.entity);
}
/**
* Called after entity is loaded.
*/
afterLoad(entity: any) {
console.log('AFTER ENTITY LOADED:', entity);
}
}
This subscription class provides some common interfaces to perform some things during database operations.
20. OrmConnectionHook
In versions prior to 3.4.0 (not included), the Midway package provided a Hook mechanism for monitoring database connection and disconnection events; the code is as follows.
import { Provide } from '@midwayjs/core';
import { OrmConnectionHook, OrmHook } from '@midwayjs/orm';
import { Connection, ConnectionOptions } from 'typeorm';
@Provide()
@OrmHook()
export class OrmConnectionListener implements OrmConnectionHook {
/**
* Called before connection create
* @param opts
* @returns
*/
async beforeCreate(opts?: ConnectionOptions): Promise<ConnectionOptions> {
console.log('BEFORE CONNECTION CREATE');
return opts;
}
/**
* Called after connection create
* @param conn
* @param opts
* @returns
*/
async afterCreate(conn?: Connection, opts?: ConnectionOptions): Promise<Connection> {
console.log('AFTER CONNECTION CREATE');
return conn;
}
/**
* Called before connection close
* @param conn
* @param connectionName
* @returns
*/
async beforeClose(conn?: Connection, connectionName?: string): Promise<Connection> {
console.log('BEFORE CONNECTION CLOSE');
return conn;
}
/**
* Called after connection close
* @param conn
* @returns
*/
async afterClose(conn?: Connection): Promise<Connection> {
console.log('AFTER CONNECTION CLOSE');
return conn;
}
}
Advanced features
Multi-database support
Sometimes, we have multiple database connections (Connection) in an application, and there will be multiple configurations at this time. We use the object form to define the configuration.
For example, the following defines two database connections (Connection), default
and test
.
import {join} from 'path';
export default {
orm: {
default: {
type: 'sqlite',
database: join(__dirname, '../../default.sqlite')
logging: true
},
test: {
type: 'mysql',
host: '127.0.0.1',
port: 3306
username: '*********',
password: '*********',
database: undefined
synchronize: true
logging: false
}
}
}
In use, you need to specify which connection (Connection) the model belongs.
// entity/photo.ts
import { InjectEntityModel } from '@midwayjs/orm';
import { User } from './model/user';
export class XXX {
@InjectEntityModel(User, 'test')
testUserModel: Repository<User>;
//...
}
Similarly, when using the injection Model, you need to specify the connection.
// entity/photo.ts
import { EntityModel } from '@midwayjs/orm';
@EntityModel('photo', {
connectionName: 'test'
})
export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
isPublished: boolean;
}
Get connection pool
import { Configuration } from '@midwayjs/core';
import { getConnection } from 'typeorm';
@Configuration()
export class MainConfiguration {
async onReady() {
const conn = getConnection('default');
console.log(conn.isConnected);
}
}
Hooks scenario support
For the scenario of functional programming, we provide a simplified functional writing.
import { useEntityModel } from '@midwayjs/orm';
import { Photo } from './entity/photo';
export async function getPhoto() {
// get model
const photoModel = useEntityModel(Photo);
const photo = new Photo();
// create entity
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
// find
const newPhoto = await photoModel.save(photo);
return 'hello world';
}
About Table Structure Synchronization
- If you already have a table structure, you want to automatically create an Entity and use the Generator
- If you already have Entity code, use the
synchronize: true
in the configuration to create a table structure.
Frequently Asked Questions
Handshake inactivity timeout
Generally, it is due to network reasons. If it appears locally, you can ping but telnet is not available. You can try to execute the following command:
$sudo sysctl -w net.inet.tcp.sack=0
About the current time zone display of mysql time column
If you use the @UpdateDateColumn
and @CreateDateColumn
columns, UTC time is normally saved in the database. If you want to return the time in the current time zone, you can use the following method.
When configuring, turn on the time-to-string option.
// src/config/config.default.ts
export default {
// ...
orm: {
//...
dateStrings: true
},
}
The time column in the entity requires a column type.
@EntityModel()
export class Photo {
//...
@UpdateDateColumn({
name: "gmt_modified ",
type: 'timestamp'
})
gmtModified: Date;
@CreateDateColumn({
name: "gmt_create ",
type: 'timestamp'
})
gmtCreate: Date;
}
In this way, the output time field is the current time zone.
The effect is as follows:
Before configuring:
gmtModified: 2021-12-13T03:49:43.000Z
gmtCreate: 2021-12-13T03:49:43.000Z
After configuration:
gmtModified: '2021-12-13 11:49:43',
gmtCreate: '2021-12-13 11:49:43'
Default values for time columns
If the @UpdateDateColumn
and @CreateDateColumn
columns are used, note that the default value is typeorm automatically added to the table-building statement. if the table is self-built, the field will be written to 00:00:00 because there is no default value.
There are two solutions: 1. Modify the default value of a table or 2. Modify the default value of a column in the code
If you don't want to modify the table, but want to modify the code, please refer to the code below.
@Column({
default: () => "NOW() ",
type: 'timestamp'
})
createdOn: Date;
@Column({
default: () => "NOW() ",
type: 'timestamp'
})
modifiedOn: Date;
Install mysql and mysql2 at the same time
when both mysql and mysql2 are present in the node_modules, the typeorm automatically loads mysql instead of mysql2.
If you need to use mysql2 at this time, please specify driver.
// src/config/config.default.ts
export default {
// ...
orm: {
//...
type: 'mysql',
driver: require('mysql2')
},
}