TypeORM và Query Builder trong TypeORM
Giới thiệu
TypeORM là một ORM có thể chạy trong các nền tảng NodeJS, Browser, Cordova, PhoneGap, Ionic, React Native, NativeScript, Expo và Electron và có thể được sử dụng với TypeScript và JavaScript (ES5, ES6, ES7, ES8). Mục tiêu của nó là luôn hỗ trợ các tính năng JavaScript mới nhất và cung cấp các tính năng bổ sung giúp bạn phát triển bất kỳ loại ứng dụng nào sử dụng cơ sở dữ liệu – từ các ứng dụng nhỏ với một vài bảng đến các ứng dụng doanh nghiệp quy mô lớn với nhiều cơ sở dữ liệu.
ORM là một loại công cụ ánh xạ các thực thể với các bảng cơ sở dữ liệu. ORM cung cấp đơn giản hóa quá trình phát triển bằng cách tự động hóa chuyển đổi đối tượng sang bảng và từ bảng sang đối tượng.
Tổng quan
TypeORM là một thư viện Object Relational Mapper chạy trong node.js và được viết bằng TypeScript. TypeScript là một cải tiến đối với JavaScript với kiểu gõ tùy chọn. TypeScript là một ngôn ngữ biên dịch. Nó không được diễn giải tại thời điểm chạy run-time. Trình biên dịch TypeScript biên dịch file TypeScript (.ts) thành file JavaScript (.js).
TypeORM hỗ trợ nhiều cơ sở dữ liệu như MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, SAP Hana và WebSQL. TypeORM là ORM dễ sử dụng để tạo mới ứng dụng kết nối với cơ sở dữ liệu. Chức năng TypeORM là các khái niệm dành riêng cho RDBMS.
Đặc điểm của TypeORM
Tự động tạo các lược đồ bảng cơ sở dữ liệu dựa trên các mô hình của bạn
Dễ dàng chèn, cập nhật và xóa đối tượng trong cơ sở dữ liệu
Tạo ánh xạ (một-một, một-nhiều và nhiều-nhiều) giữa các bảng
Cung cấp các lệnh CLI đơn giản
Lợi ích của TypeORM
TypeORM rất dễ sử dụng ORM framework với coding đơn giản. Nó có những lợi ích sau:
- Các ứng dụng chất lượng cao và được ghép nối lỏng lẻo
- Các ứng dụng có thể mở rộng
- Dễ dàng tích hợp với các modules khác
- Hoàn toàn phù hợp với mọi kiến trúc từ ứng dụng nhỏ đến ứng dụng doanh nghiệp
Trong typeorm có rất nhiều tính năng nhưng trong blog này mình sẽ viết về cách sử dụng query builder của typeorm với typescript.
Query Builder
Query builder được sử dụng để xây dựng các truy vấn SQL phức tạp một cách dễ dàng. Nó được khởi tạo từ phương thức Connection.
Connection
Hãy xem xét một ví dụ đơn giản về cách sử dụng QueryBuilder bằng phương thức kết nối:
import {getConnection} from "typeorm"; const user = await getConnection() .createQueryBuilder() .from("user", "user") .select("user") .where("user.id = :id", { id: 1 }) .getOne();
Repository
Chúng ta có thể sử dụng repository để tạo trình tạo truy vấn. Nó được mô tả dưới đây,
import {getRepository} from "typeorm"; const user = await getRepository(User) .createQueryBuilder("user") .where("user.id = :id", { id: 1 }) .getOne();
Adding expression use getConnection (no model needed)
table user: id, name, age
where được sử dụng để lọc các bản ghi nếu điều kiện được khớp.
getConnection().createQueryBuilder().from("user", "user").select("user") .where("user.id = :id", { id: 1 }) .getRawOne();
Truy vấn này tương đương với:
select * from user where user.id=1;
orderBy được sử dụng để sắp xếp các bản ghi dựa trên trường
getConnection().createQueryBuilder().from("user", "user").orderBy("user.name");
Truy vấn này tương đương với:
select * from user order by user.name;
groupBy: Nó được sử dụng để nhóm các bản ghi dựa trên cột được chỉ định.
getConnection().createQueryBuilder().from("user", "user").groupBy("user.id")
Truy vấn này tương đương với:
select * from user group by user.id;
limit: được sử dụng để giới hạn việc chọn rows.
getConnection().createQueryBuilder().from("user", "user").limit(5);
Truy vấn này tương đương với:
select * from user limit 5;
offset được sử dụng để chỉ định, có bao nhiêu rows để bỏ qua kết quả.
getConnection().createQueryBuilder().from("user", "user").offset(5);
Truy vấn này tương đương với:
select * from user offset 5;
joins: mệnh đề nối được sử dụng để kết hợp các hàng từ hai hoặc nhiều bảng, dựa trên một cột có liên quan. Ví dụ bên dưới là 2 bảng student với project có mối quan hệ là 1-n:
student: id, name, email, phone
project : id, name, student_id
leftJoinAndSelect
const student = await getConnection().createQueryBuilder().from("student", "student") .leftJoinAndSelect("project", "project", "project.student_id = student.id") .where("student.name = :name", { name: "Student1" }) .getOne();
Truy vấn này tương đương với:
SELECT student.*, project.* FROM student student LEFT JOIN project project ON project.student_id = student.id WHERE student.name = 'Student1'
innerJoinAndSelect
const student = await getConnection().createQueryBuilder().from("student", "student") .innerJoinAndSelect("project", "project", "project.student_id = student.id") .where("student.name = :name", { name: "student1" }) .getOne();
Truy vấn trên tương đương với:
SELECT student.*, project.* FROM student student INNER JOIN project project ON project.student_id = student.id WHERE student.name = 'student1';
Insert
import {getConnection} from "typeorm"; await getConnection() .createQueryBuilder() .insert() .into('student') .values([ { name: "test", email: "test@gmail.com", phone: "09011112222"}, { name: "test2", email: "test2@gmail.com", phone: "09011112222"} ]) .execute();
Update
import {getConnection} from "typeorm"; await getConnection().createQueryBuilder() .update('student') .set({ name: "test3", email: "test3@gmail.com"}) .where("id = :id", { id: 1 }) .execute();
Delete
import {getConnection} from "typeorm"; await getConnection() .createQueryBuilder() .delete() .from('student') .where("id = :id", { id: 1 }) .execute();
Subqueries
Ví dụ 1: Sử dụng subQuery để select field name trong bảng student các record mà có email chứa chuỗi test
Ví dụ 2: Sử dụng subQuery để select id lớn nhất trong bảng project thỏa field name có chứa chuỗi test và select ra studentId, studentName, projectId
const student = await getConnection().createQueryBuilder().select("student.name", "name") .from((subQuery) => { return subQuery.select("student.name", "name") .from("student", "student").where("student.email like :email", { email: '%test%'}) }, "student") .getRawMany(); const student = await getConnection().createQueryBuilder() .select(`student.id as studentId,student.name as studentName,project.id as projectId`)) .from("student", "student").leftJoin("project", "project", "project.student_id = student.id") .where ("project.id = (select max(id) from project where name like '%test%' ")).getRawMany();
Adding expression use getRepository() with model
Ví dụ có model User sau:
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm"; @Entity('user') export class User { @PrimaryGeneratedColumn() id: number; @Column('varchar', {name: 'name', nullable: true, length: 50}) name: string | null; @Column('int', {name: 'age', nullable: true}) name: number | null; }
where được sử dụng để lọc các bản ghi nếu điều kiện được khớp.
getRepository(User).createQueryBuilder("user").where("user.id= :id", { id: 1 });
Truy vấn này tương đương với:
select * from user where user.id=1;
orderBy được sử dụng để sắp xếp các bản ghi dựa trên trường
getRepository(User).createQueryBuilder("user").orderBy("user.name");
Truy vấn này tương đương với:
select * from user order by user.name;
groupBy: Nó được sử dụng để nhóm các bản ghi dựa trên cột được chỉ định.
getRepository(User).createQueryBuilder("user") .groupBy("user.id")
Truy vấn này tương đương với:
select * from user group by user.id;
limit: được sử dụng để giới hạn việc chọn rows.
getRepository(User).createQueryBuilder("user").limit(5);
Truy vấn này tương đương với:
select * from user limit 5;
offset được sử dụng để chỉ định, có bao nhiêu rows để bỏ qua kết quả.
getRepository(User).createQueryBuilder("user") .offset(5);
Truy vấn này tương đương với:
select * from user offset 5;
joins: mệnh đề nối được sử dụng để kết hợp các hàng từ hai hoặc nhiều bảng, dựa trên một cột có liên quan. Hãy xem xét hai thực thể:
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm"; import {Project} from "./Project"; @Entity('student') export class Student { @PrimaryGeneratedColumn() id: number; @Column('varchar', {name: 'name', nullable: true, length: 50}) name: string | null; @Column('varchar', {name: 'email', nullable: true, length: 30}) email: string | null; @Column('varchar', {name: 'phone', nullable: true, length: 11}) phone: string | null; @OneToMany(()=> Project, project => project.student) projects: project[]; }
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm"; import {Student} from "./Student"; @Entity('project') export class Project { @PrimaryGeneratedColumn() id: number; @Column('varchar', {name: 'name', nullable: true, length: 50}) name: string | null; @Column('bigint', {name: 'student_id', nullable: true, unsigned: true}) studentId: number | null; @ManyToOne(() => Student, student => student.projects) @JoinColumn([{name: 'student_id', referencedColumnName: 'id'}]) student : Student; }
leftJoinAndSelect
const student = await getRepository(Student).createQueryBuilder("student") .leftJoinAndSelect("student.projects", "project") .where("student.name = :name", { name: "Student1" }) .getOne();
Truy vấn này tương đương với:
SELECT student.*, project.* FROM student student LEFT JOIN projects project ON project.student = student.id WHERE student.name = 'Student1'
innerJoinAndSelect
const student = await getRepository(Student).createQueryBuilder("student") .innerJoinAndSelect("student.projects", "project") .where("student.name = :name", { name: "student1" }) .getOne();
Truy vấn trên tương đương với:
SELECT student.*, project.* FROM students student INNER JOIN projects project ON project.student = student.id WHERE student.name = 'Student1';
Insert
import {getConnection} from "typeorm"; await getRepository(Student) .createQueryBuilder() .insert() .into(Student) .values([ { name: "test", email: "test@gmail.com", phone: "09011112222"}, { name: "test2", email: "test2@gmail.com", phone: "09011112222"} ]) .execute();
Update
import {getConnection} from "typeorm"; await getRepository(Student) .createQueryBuilder() .update(Student) .set({ name: "test3", email: "test3@gmail.com"}) .where("id = :id", { id: 1 }) .execute();
Delete
import {getConnection} from "typeorm"; await getRepository(Student).createQueryBuilder() .delete() .from(Student) .where("id = :id", { id: 1 }) .execute();
Kết luận
Nên sử dụng query builder bởi vì nó có thể build các câu sql từ dễ đến phức tạp
Syntax cũng tương tự gần giống với sql thuần nên rất dễ viết code và đọc hiểu
Cũng có thể sử dụng query builder một cách bình thường mà không cần dùng đến khai báo model
Về hiệu năng thì sử dụng query builder thời gian truy xuất sẽ nhanh hơn sử dụng repository api có sẵn của typeorm