目 录CONTENT

文章目录

入门TypeOrm

Hello!你好!我是村望~!
2024-12-28 / 0 评论 / 0 点赞 / 13 阅读 / 3,813 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

TypeOrm

Demo项目配置

首先初始化一个 typeORM 的 demo 项目

npx typeorm@latest init --name temp_typeorm-all-feature --database mysql

确认好数据源配置信息启动

src/data-source.ts

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"

export const AppDataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "123",
    database: "test",
    synchronize: true,
    logging: false,
    entities: [User],
    migrations: [],
    subscribers: [],
})

如果此时有报错 Client does not support authentication protocol requested by server; consider upgrading MySQL client

Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
    at Handshake.Sequence._packetToError (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/protocol/sequences/Sequence.js:47:14)
    at Handshake.ErrorPacket (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/protocol/sequences/Handshake.js:123:18)
    at Protocol._parsePacket (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/protocol/Protocol.js:291:23)
    at Parser._parsePacket (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/protocol/Parser.js:433:10)
    at Parser.write (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/protocol/Parser.js:43:10)
    at Protocol.write (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/protocol/Protocol.js:38:16)
    at Socket.<anonymous> (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/Connection.js:88:28)
    at Socket.<anonymous> (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/Connection.js:526:10)
    at Socket.emit (node:events:517:28)
    at Socket.emit (node:domain:489:12)
    --------------------
    at Protocol._enqueue (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/protocol/Protocol.js:144:48)
    at Protocol.handshake (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/protocol/Protocol.js:51:23)
    at PoolConnection.connect (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/Connection.js:116:18)
    at Pool.getConnection (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/mysql/lib/Pool.js:48:16)
    at /Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/typeorm/src/driver/mysql/MysqlDriver.ts:1268:18
    at new Promise (<anonymous>)
    at MysqlDriver.createPool (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/typeorm/src/driver/mysql/MysqlDriver.ts:1265:16)
    at MysqlDriver.connect (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/typeorm/src/driver/mysql/MysqlDriver.ts:400:36)
    at DataSource.initialize (/Users/xiaohao/FE/temp_typeorm-all-feature/node_modules/src/data-source/DataSource.ts:253:27)
    at Object.<anonymous> (/Users/xiaohao/FE/temp_typeorm-all-feature/src/index.ts:4:15) {
  code: 'ER_NOT_SUPPORTED_AUTH_MODE',
  errno: 1251,
  sqlMessage: 'Client does not support authentication protocol requested by server; consider upgrading MySQL client',
  sqlState: '08004',
  fatal: true
}

因为默认 MySQL client 版本较低,可以使用 MySQL2

npm i mysql2

指定 connectorPackage 为 mysql2

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"

export const AppDataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "123",
    database: "test",
    synchronize: true,
    connectorPackage: 'mysql2',
    logging: false,
    entities: [User],
    migrations: [],
    subscribers: [],
})

启动测试,默认有一个 User 实体 entity !

image-20241227112357501

启动自动帮我们创建了 user 表,因为我自己启动了两次 所以自己插入了两次数据,它启动脚本的逻辑

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    console.log("Inserting a new user into the database...")
    const user = new User()
    user.firstName = "Timber"
    user.lastName = "Saw"
    user.age = 25
    await AppDataSource.manager.save(user)
    console.log("Saved a new user with id: " + user.id)

    console.log("Loading users from the database...")
    const users = await AppDataSource.manager.find(User)
    console.log("Loaded users: ", users)

    console.log("Here you can setup and run express / fastify / any other framework.")

}).catch(error => console.log(error))

配置说明

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"

export const AppDataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "123",
    database: "test",
    synchronize: true,
    connectorPackage: 'mysql2',
    logging: true,
    entities: [User],
    migrations: [],
    subscribers: [],
})

type 是数据库的类型,因为 TypeORM 不只支持 MySQL 还支持 postgres、oracle、sqllite 等数据库。

host、port 是指定数据库服务器的主机和端口号。

user、password 是登录数据库的用户名和密码。

database 是要指定操作的 database,因为 mysql 是可以有多个 database 或者叫 schema 的。

synchronize 是根据同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行。

logging 是打印生成的 sql 语句。

image-20241227112914696entities 是指定有哪些和数据库的表对应的 Entity。[如果指定了synchronize 为 true,就自动创建这里指定实体的数据库]

import {Column, Entity, PrimaryColumn, PrimaryGeneratedColumn} from "typeorm";

@Entity()
export default class Goods {
    @PrimaryGeneratedColumn()
    Id: number;

    @Column()
    GoodsName: string;

    @Column()
    GoodsPrice: number;
}

image-20241227141103525

这里把 id 换成了 PrimaryGeneratedColumn PRIMARY KEY AUTO_INCREMENT

migrations 是修改表结构之类的 sql
subscribers 是一些 Entity 生命周期的订阅者,比如 insert、update、remove 前后,可以加入一些逻辑
poolSize 是指定数据库连接池中连接的最大数量。
connectorPackage 是指定用什么驱动包。
extra 是额外发送给驱动包的一些选项。

这些配置都保存在 DataSource 里。

CRUD

单个增加

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const newGood = new Goods()
    newGood.GoodsName = "雪花肥牛"
    newGood.GoodsPrice = 88
    const res = await AppDataSource.manager.save(newGood)
    console.log(res)
}).catch(error => console.log(error))

image-20241227141331390

如果指定了 id,那就变成修改了

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const newGood = new Goods()
    newGood.GoodsName = "雪花肥牛(修改)"
    newGood.GoodsPrice = 88
    newGood.Id = 1
    const res = await AppDataSource.manager.save(newGood)
    console.log(res)
}).catch(error => console.log(error))

image-20241227141655001

当你指定了 id 的时候,typeorm 会先查询这个 id 的记录,如果查到了,那就执行 update。

image-20241227141838856

如果查不到那就是插入

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const newGood = new Goods()
    newGood.GoodsName = "雪花肥牛(修改)"
    newGood.GoodsPrice = 88
    newGood.Id = 901
    const res = await AppDataSource.manager.save(newGood)
    console.log(res)
}).catch(error => console.log(error))

image-20241227141924124

数据库中就多一条 Id 为 901 的商品

GoodsName GoodsPrice Id
雪花肥牛(修改) 88 1
雪花肥牛(修改) 88 901

批量插入

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    await AppDataSource.manager.save(Goods, [
        {
            GoodsName: "吊龙",
            GoodsPrice: 78,
        },
        {
            GoodsName: "嫩肉",
            GoodsPrice: 88,
        },
        {
            GoodsName: "三花趾",
            GoodsPrice: 90,
        }
    ])
}).catch(error => console.log(error))

image-20241227142735333

GoodsName GoodsPrice Id
雪花肥牛(修改) 88 1
雪花肥牛(修改) 88 901
吊龙 78 902
嫩肉 88 903
三花趾 90 904

批量修改-传入 Id 就行了

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    await AppDataSource.manager.save(Goods, [
        {
            GoodsName: "无敌吊龙",
            GoodsPrice: 78,
            Id: 902,
        },
        {
            GoodsName: "超绝嫩肉",
            GoodsPrice: 88,
            Id: 903,
        },
        {
            GoodsName: "夺命三花趾",
            GoodsPrice: 90,
            Id: 904,
        }
    ])
}).catch(error => console.log(error))

同样的套路 先查 查得到就 update

image-20241227143304033

EntityManager 还有 update 和 insert 方法,分别是修改和插入的,但是它们不会先 select 查询一次。

而 save 方法会先查询一次数据库来确定是插入还是修改。

如果同样的在执行一次 Insert

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    await AppDataSource.manager.insert(Goods, [
        {
            GoodsName: "无敌吊龙",
            GoodsPrice: 78,
            Id: 902,
        },
        {
            GoodsName: "超绝嫩肉",
            GoodsPrice: 88,
            Id: 903,
        },
        {
            GoodsName: "夺命三花趾",
            GoodsPrice: 90,
            Id: 908,
        }
    ])
}).catch(error => console.log(error))

就会直接报错 重复key,因为他不管其他的直接插,有异常就直接暴露异常!

image-20241227143528602

删除和批量删除用 delete 方法

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    // 单个ID删除
    await AppDataSource.manager.delete(Goods, 1)

    // 多个ID删除
    await AppDataSource.manager.delete(Goods, [903,904])
}).catch(error => console.log(error))

image-20241227143954377

GoodsName GoodsPrice Id
雪花肥牛(修改) 88 901
无敌吊龙 78 902
夺命三花趾 90 908

删除和批量删除用 remove 方法

delete 和 remove 的区别是,delete 直接传 id、而 remove 则是传入 entity 对象。

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    // 外部传入一个 Id 查出来 然后删除
    const goods = await AppDataSource.manager.findOneBy(Goods, {
        Id: 901
    })
    await AppDataSource.manager.remove(Goods, goods)
}).catch(error => console.log(error))

image-20241227144526807

查询是使用 find 方法

find 方法很多变式

image-20241227144838026

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    // 外部传入一个 Id 查出来 然后删除
    const goods = await AppDataSource.manager.find(Goods)
    console.log(goods)
}).catch(error => console.log(error))

image-20241227144932850

指定更多条件

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const goods = await AppDataSource.manager.find(Goods, {
        select: {
            GoodsName: true,
            GoodsPrice: true
        },
        order: {
            GoodsPrice: "desc"
        }
    })
    console.log(goods)
}).catch(error => console.log(error))

image-20241227150654053

findBy 方法根据条件查询

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const goods = await AppDataSource.manager.findBy(Goods,{
        GoodsName:"无敌吊龙"
    })
    console.log(goods)
}).catch(error => console.log(error))

image-20241227145551744

findAndCount 来拿到有多少条记录

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const goods = await AppDataSource.manager.findAndCountBy(Goods,{
        GoodsName:"无敌吊龙"
    })
    console.log(goods)
}).catch(error => console.log(error))

image-20241227145736555

会额外执行一个统计的 sql

除了可以查询多条,还可以查询一条,使用 findOne

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const goods = await AppDataSource.manager.findOne(Goods, {
        select: {
            GoodsName: true,
            GoodsPrice: true
        },
        where:{
            GoodsName:"无敌吊龙"
        },
        order: {
            GoodsPrice: "desc"
        }
    })
    console.log(goods)
}).catch(error => console.log(error))

image-20241227150605383

会抛出异常的查询 findOneOrFail

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";
import {In} from "typeorm";

AppDataSource.initialize().then(async () => {
    const goods = await AppDataSource.manager.findOneOrFail(Goods, {
        select: {
            GoodsName: true,
            GoodsPrice: true
        },
        where: {
            Id: In([666, 999])
        },
        order: {
            GoodsPrice: "desc"
        }
    })
    console.log(goods)
}).catch(error => console.log(error))

image-20241227151038549

会抛出 EntityNotFoundError: 异常!

query 方法直接执行 sql 语句

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const res = await AppDataSource.manager.query("select GoodsName,GoodsPrice from goods order by GoodsPrice DESC")
    console.log(res)
}).catch(error => console.log(error))

image-20241227151347399

query builder

复杂 sql 语句不会直接写,而是会用 query builder,涉及到多个表,也就是多个 Entity 的关联查询

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import Goods from "./entity/Goods";

AppDataSource.initialize().then(async () => {
    const queryBuilder = AppDataSource.manager.createQueryBuilder();

    const res = await queryBuilder
        .select("g.GoodsName").from(Goods, 'g')
        .where('g.GoodsName like:GoodsName')
        .andWhere('g.GoodsPrice >:GoodsPrice')
        .setParameters({
            GoodsName: "%龙",
            GoodsPrice: 50,
        }).execute()
    console.log(res)
}).catch(error => console.log(error))

image-20241227153143751

等价于下面这个 SQL

SELECT GoodsName FROM goods as g where g.GoodsName like "%龙" and g.GoodsPrice > 50;

image-20241227153521010

使用事务

创建一个余额Balances表和产品表

Balances

import {Column, Entity, PrimaryGeneratedColumn} from "typeorm";

@Entity({
    name: "balances"
})
export default class Balances {
    // 主键自增列,一般命名为类似表名加_id的形式,这里表示余额记录的唯一标识
    @PrimaryGeneratedColumn()
    balance_id: number;

    // 关联到客户的ID,命名体现其关联性和用途
    @Column()
    customer_id: number;

    // 可用余额字段,清晰表明含义且遵循命名规范
    @Column({
        type: "decimal",
        precision: 10,
        scale: 2
    })
    available_balance: number;

    @Column({
        type: "timestamp",
        onUpdate: 'CURRENT_TIMESTAMP',
        default: () => 'CURRENT_TIMESTAMP',
    })
    last_updated: number;
}

Product

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity('products')
export class Product {
    @PrimaryGeneratedColumn()
    product_id: number;

    @Column({ type: 'varchar', length: 255, nullable: false })
    product_name: string;

    @Column({ type: 'decimal', precision: 10, scale: 2, nullable: false })
    price: number;

    @Column({ type: 'int', nullable: false })
    stock_quantity: number;
}

image-20241227164535258

-- 余额表
CREATE TABLE `balances`
(
    `balance_id`        int            NOT NULL AUTO_INCREMENT,
    `customer_id`       int            NOT NULL,
    `available_balance` decimal(10, 2) NOT NULL,
    `last_updated`      timestamp      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`balance_id`)
) ENGINE = InnoDB


-- 产品表
CREATE TABLE `products`
(
    `product_id`     int            NOT NULL AUTO_INCREMENT,
    `product_name`   varchar(255)   NOT NULL,
    `price`          decimal(10, 2) NOT NULL,
    `stock_quantity` int            NOT NULL,
    PRIMARY KEY (`product_id`)
) ENGINE = InnoDB

然后插入点数据

-- 余额
INSERT INTO balances (customer_id, available_balance)
VALUES
    (1001, 15000.00),
    (1002, 8000.00),
    (1003, 10000.00),
    (1004, 12000.00),
    (1005, 5000.00),
    (1006, 9000.00);

-- 产品
 INSERT INTO products (product_name, price, stock_quantity)
VALUES
    -- 手机类商品
    ('iPhone 15 Pro', 8999.00, 50),
    ('华为 P60 Pro', 6488.00, 40),
    ('小米 14', 4299.00, 60),
    ('三星 Galaxy S24', 7499.00, 35),
    -- 电脑类商品
    ('联想小新 Pro 16', 6999.00, 20),
    ('戴尔灵越 16 Plus', 7999.00, 15),
    ('苹果 MacBook Air M2', 9999.00, 10),
    ('华为 MateBook X Pro', 8999.00, 8),
    -- 家电类商品
    ('美的空调 KFR-35GW', 2999.00, 30),
    ('海尔冰箱 BCD-596WGH', 4999.00, 18),
    ('格力电风扇 FS-3015h7', 399.00, 50),
    ('苏泊尔电饭煲 CFXB40FC833 - 75', 299.00, 45);
import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import {Product} from "./entity/Product";
import Balances from "./entity/Balances";

AppDataSource.initialize().then(async () => {
    await AppDataSource.manager.transaction(async manger => {
        const p = manger.getRepository(Product)
        const b = manger.getRepository(Balances)

        const product = await p.findOneBy({
            product_name: "iPhone 15 Pro"
        })
        if (product.stock_quantity <= 0) return
        const balance = await b.findOneBy({
            balance_id: 1
        })
        if (Number(balance.available_balance) < Number(product.price)) return
        product.stock_quantity -= 1
        await p.save(product)
        balance.available_balance -= Number(product.price)
        await b.save(balance)
    })
}).catch(error => console.log(error))

开启事务用 transaction 方法包裹下就好了。

上面的逻辑 先查商品库存,然后查余额是否能买,然后扣库存,扣余额,没有报错自动提交事务

image-20241228110106451

报错也会自动回滚

import {AppDataSource} from "./data-source"
import {User} from "./entity/User"
import {Product} from "./entity/Product";
import Balances from "./entity/Balances";

AppDataSource.initialize().then(async () => {
    await AppDataSource.manager.transaction(async manger => {
        const p = manger.getRepository(Product)
        const b = manger.getRepository(Balances)

        const product = await p.findOneBy({
            product_name: "iPhone 15 Pro"
        })
        if (product.stock_quantity <= 0) return
        const balance = await b.findOneBy({
            balance_id: 3
        })
        if (Number(balance.available_balance) < Number(product.price)) return
        product.stock_quantity -= 1
        await p.save(product)
        throw new Error("模拟事务中异常")
        balance.available_balance -= Number(product.price)
        await b.save(balance)
    })
}).catch(error => console.log(error))

image-20241228110311758

可以看到数据表中库存也扣除失败

image-20241228110403066

小结

DataSource 里管理着数据库连接配置,数据库驱动包,调用它的 intialize 方法会创建和 mysql 的连接。

连接创建的时候,如果指定了 synchronize,会根据 Entitiy 生成建表 sql。

Entity 里通过 @Entity 指定和数据库表的映射,通过 @PrimaryGeneratedColumn 和 @Column 指定和表的字段的映射。

对 Entity 做增删改查通过 EntityManager 的 save、delete、find、createQueryBuilder 等方法。

如果只是对单个 Entity 做 CRUD,那可以先 getRepository 拿到对具体 Entity 操作的工具类,再调用 save、delete、find 等方法。

具体的 EntityManager 和 Repository 的方法有这些:

  • save:新增或者修改 Entity,如果传入了 id 会先 select 再决定修改还新增
  • update:直接修改 Entity,不会先 select
  • insert:直接插入 Entity
  • delete:删除 Entity,通过 id
  • remove:删除 Entity,通过对象
  • find:查找多条记录,可以指定 where、order by 等条件
  • findBy:查找多条记录,第二个参数直接指定 where 条件,更简便一点
  • findAndCount:查找多条记录,并返回总数量
  • findByAndCount:根据条件查找多条记录,并返回总数量
  • findOne:查找单条记录,可以指定 where、order by 等条件
  • findOneBy:查找单条记录,第二个参数直接指定 where 条件,更简便一点
  • findOneOrFail:查找失败会抛 EntityNotFoundError 的异常
  • query:直接执行 sql 语句
  • createQueryBuilder:创建复杂 sql 语句,比如 join 多个 Entity 的查询
  • transaction:包裹一层事务的 sql
  • getRepository:拿到对单个 Entity 操作的类,方法同 EntityManager
0

评论区