Перейти к основному содержанию

Отношения «многие к одному» / «один ко многим»

Неофициальный Бета-перевод

Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →

Отношение «многие к одному» / «один ко многим» описывает ситуацию, где сущность A содержит множество экземпляров сущности B, а сущность B связана только с одним экземпляром сущности A.
Рассмотрим пример сущностей User и Photo.
Пользователь может иметь несколько фотографий, но каждая фотография принадлежит только одному пользователю.

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"
import { User } from "./User"

@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number

@Column()
url: string

@ManyToOne(() => User, (user) => user.photos)
user: User
}
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"
import { Photo } from "./Photo"

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

@Column()
name: string

@OneToMany(() => Photo, (photo) => photo.user)
photos: Photo[]
}

Здесь мы добавили @OneToMany к свойству photos и указали тип целевого отношения как Photo.
Декоратор @JoinColumn можно опускать в отношениях @ManyToOne / @OneToMany.
@OneToMany не может существовать без @ManyToOne.
Если вы используете @OneToMany, то @ManyToOne обязателен. Однако обратное неверно: если вам нужно только отношение @ManyToOne, вы можете определить его без @OneToMany в связанной сущности.
Там, где установлен @ManyToOne, в связанной сущности будут присутствовать "идентификатор отношения" и внешний ключ.

Данный пример создаст следующие таблицы:

+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| url | varchar(255) | |
| userId | int | FOREIGN KEY |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+

Пример сохранения такого отношения:

const photo1 = new Photo()
photo1.url = "me.jpg"
await dataSource.manager.save(photo1)

const photo2 = new Photo()
photo2.url = "me-and-bears.jpg"
await dataSource.manager.save(photo2)

const user = new User()
user.name = "John"
user.photos = [photo1, photo2]
await dataSource.manager.save(user)

или альтернативный способ:

const user = new User()
user.name = "Leo"
await dataSource.manager.save(user)

const photo1 = new Photo()
photo1.url = "me.jpg"
photo1.user = user
await dataSource.manager.save(photo1)

const photo2 = new Photo()
photo2.url = "me-and-bears.jpg"
photo2.user = user
await dataSource.manager.save(photo2)

При включённых каскадах вы можете сохранить отношение одним вызовом save.

Для загрузки пользователя с фотографиями необходимо указать отношение в FindOptions:

const userRepository = dataSource.getRepository(User)
const users = await userRepository.find({
relations: {
photos: true,
},
})

// or from inverse side

const photoRepository = dataSource.getRepository(Photo)
const photos = await photoRepository.find({
relations: {
user: true,
},
})

Или использовать QueryBuilder для объединения:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.getMany()

// or from inverse side

const photos = await dataSource
.getRepository(Photo)
.createQueryBuilder("photo")
.leftJoinAndSelect("photo.user", "user")
.getMany()

При включённой жадной загрузке (eager loading) отношения вам не нужно указывать его в команде поиска — оно ВСЕГДА загружается автоматически.
При использовании QueryBuilder жадная загрузка отключена, поэтому для загрузки отношений нужно использовать leftJoinAndSelect.