• TsBatis 预览


    前言

    在发布了 mybatis-dynamic-query之后感觉基本功能稳定了,而且现在在工作项目开发效率大大提高,而且非常易于维护。 最近准备带几个小朋友用typescript 打通前后端,当写node写数据库的时候,我就发现非常麻烦,当时就想,为啥js 没有类似于mybatis 框架,然后。。。 就写了tsBatis (typescript + mybatis)
    项目地址:https://github.com/wz2cool/tsbatis

    TsBatis

    tsbatis:诞生的目的是希望把mybatis一些思想借鉴过来(当然现在肯定没mybatis 强大)。
    typescript:微软的良心产品,而且确实带来了惊喜,使用了decorator 类似于java中的aonnotion 才使得这一切成为可能

    Entity

    这里和java(mybatis) 有点不一样,这里的Entity 不是一个注解,而是一个类,主要做一下标示,主要作用于视图

    TableEntity

    这里和java(mybatis) 不一样,java 里面使用的是@Table 注解, 这里是一个虚类继承Entity,必须实现getTableName()方法, 主要作用于表

    @column

    这是核心注解,里面的参数和mybatis里面@Column注解类似。
    两种构造:
    1.表列(只要指定列名和是否是一个key,是否可以插入)

    import { column, TableEntity } from "../../../src";
    export class Customer extends TableEntity {
        @column("id", true, false)
        public id: number;
        @column("compnay")
        public company: string;
        @column("last_name")
        public lastName: string;
        @column("first_ame")
        public firstName: string;
        @column("email_address")
        public emailAddress: string;
        @column("job_title")
        public jobTitle: string;
    
        public getTableName(): string {
            return "customers";
        }
    }
    

    2.视图列 (指定列名和表名)

    import { column, Entity } from "../../../../src";
    export class NorthwindProductView extends Entity {
        @column("Id", "Product")
        public productId: number;
        @column("ProductName", "Product")
        public productName: string;
        @column("UnitPrice", "Product")
        public unitPrice: number;
        @column("CategoryName", "Category")
        public categoryName: string;
    }
    

    RowBounds 类

    对的,你们没看错,这个就是类似于mybatis 中的 rowBounds, 多的我就不解释了

    PageRowBounds 类

    对的这个同样来自于我们熟悉的abel533 大神写的PageHelper
    项目地址: https://github.com/pagehelper/Mybatis-PageHelper

    Page<>

    我们可以直接使用PageRowRounds,查询返回这个Page,具体可以看实战分页

    export class Page<T extends Entity> {
        private pageNum: number;
        private pageSize: number;
        private total: number;
        private pages: number;
        private entities: T[];
        ...
    }
    

    ISqlConnection

    确实没有mybatis那么强大驱动,而且js数据库驱动都是来自其他库(千差万别),所以我们可能需要自己实现一下

    import { DatabaseType, Entity, SqlTemplate } from "../model";
    export interface ISqlConnection {
        getDataBaseType(): DatabaseType;
        run(sql: string, params: any[], callback: (err: any, result?: any) => void);
        runTransaction(sqlTemplates: SqlTemplate[], callback: (err: any, result: any[]) => void);
        select(sql: string, params: any[], callback: (err: any, result: any[]) => void);
        selectCount(sql: string, params: any[], callback: (err: any, result: number) => void);
        selectEntities<T extends Entity>(
            entityClass: { new(): T },
            sql: string,
            params: any[],
            callback: (err: any, result: T[]) => void);
    }
    

    SqliteConnection

    实现ISqlConnection 接口,是我自己实现的一套sqlite版本主要用于测试。
    数据库驱动:https://github.com/mapbox/node-sqlite3

    const dbPath = path.join(__dirname, "../../northwind.db");
     // 传入到SqliteConnection 构造方法
    this.sqlitedb = new sqlite3.Database(dbPath);
    

    MysqlConnection

    实现ISqlConnection 接口,mysql 用途广泛,当然要实现一下啦。
    数据库驱动:https://github.com/mysqljs/mysql

    // 传入到MysqlConnection 构造方法
    const pool = mysql.createPool({
        host: "sql12.freemysqlhosting.net",
        port: 3306,
        database: "sql12200910",
        user: "sql12200910",
        password: "XXXXXXXXXXXXXXX",
    });
    

    BaseMapper

    提供公共方法,直接用纯sql结果。

    BaseMybatisMapper

    继承BaseMapper,提供mybatis风格查询,比如${}和#{}站位

    BaseTableMapper

    继承BaseMybatisMapper,提供基本的CRUD功能,基本上就是通用mapper

    实战

    BaseTableMapper实战

    注:测试都是基于Sqlite
    1.创建表

    CREATE TABLE users (
        id          INTEGER PRIMARY KEY autoincrement,
        username    VARCHAR(64) NOT NULL,
        password    VARCHAR(64)
    );
    

    2.创建表实体

    export class User extends TableEntity {
        @column("id", true, false)
        public id: number;
        @column("username")
        public username: string;
        @column("password")
        public password: string;
    
        public getTableName(): string {
            return "users";
        }
    }
    

    3.创建UserMapper

    export class UserMapper extends BaseTableMapper<User> {
        constructor(sqlConnection: ISqlConnection) {
            super(sqlConnection);
        }
    
        // 这里需要返回这个User, 因为你懂的,typescript 泛型是编译的,
        // 那个泛型T是拿不到什么东西的
        public getEntityClass(): new () => User {
            return User;
        }
    }
    

    4.组合SqliteConnection 到 UserMapper

    const dbPath = path.join(__dirname, "../sqlite.db");
    const db = new sqlite3.Database(dbPath);
    const connection = new SqliteConnection(db);
    const userMapper = new UserMapper(connection);
    

    5.插入数据

    describe("#insert", () => {
            it("should return seq after inserting a new row", (done) => {
                const newUser = new User();
                newUser.username = "frankTest";
                newUser.password = "pwd";
                userMapper.insert(newUser)
                    .then((id) => {
                        const searchUser = new User();
                        searchUser.id = id;
                        return userMapper.selectByExample(searchUser);
                    })
                    .then((users) => {
                        if (users.length === 0) {
                            done("cannot find user");
                            return;
                        }
    
                        const user = users[0];
                        if (newUser.username === user.username
                            && newUser.password === user.password) {
                            done();
                        } else {
                            done("user is invalid");
                        }
                    })
                    .catch((err) => {
                        done(err);
                    });
            });
    });
    

    6.输出, Sqlite 直接拿不到自增id,需要在查询一下 sqlite_sequence

    sql:  INSERT INTO users (username,password) VALUES (?, ?)
    params:  [ 'frankTest', 'pwd' ]
    sql:  SELECT seq FROM sqlite_sequence WHERE name = ?
    params:  [ 'users' ]
    sql:  SELECT id AS id, username AS username, password AS password FROM users WHERE id = ?
    params:  [ 3 ]
    √ should return seq after inserting a new row (92ms)
    

    依赖注入(inversify)实战

    老实说inversify这个东西我还不是非常明白,如果有错误欢迎指正。
    1.构造一个可注入的sqlitedb 封装

    import { injectable } from "inversify";
    import * as path from "path";
    // 这个必须引入
    import "reflect-metadata";
    import * as sqlite3 from "sqlite3";
    
    @injectable()
    export class InjectableSqlitedb {
        public readonly sqlitedb: sqlite3.Database;
    
        constructor() {
            const dbPath = path.join(__dirname, "../../northwind.db");
            this.sqlitedb = new sqlite3.Database(dbPath);
        }
    }
    

    2.构造一个可注入的SqliteConnection

    import { injectable } from "inversify";
    import * as path from "path";
    import "reflect-metadata";
    import * as sqlite3 from "sqlite3";
    import { SqliteConnection } from "../../../src";
    import { InjectableSqlitedb } from "./injectableSqlitedb";
    
    @injectable()
    export class InjectableSqliteConnection extends SqliteConnection {
        constructor(db: InjectableSqlitedb) {
            super(db.sqlitedb);
        }
    }
    

    3.建立视图实体

    export class NorthwindProductView extends Entity {
        @column("Id", "Product")
        public productId: number;
        @column("ProductName", "Product")
        public productName: string;
        @column("UnitPrice", "Product")
        public unitPrice: number;
        @column("CategoryName", "Category")
        public categoryName: string;
    }
    

    4.构造一个可注入的mapper

    import { inject, injectable } from "inversify";
    import "reflect-metadata";
    import { BaseMybatisMapper, ISqlConnection } from "../../../src";
    import { InjectableSqliteConnection } from "../connection/injectableSqliteConnection";
    import { NorthwindProductView } from "../entity/view/NothwindProductView";
    
    @injectable()
    export class ProductViewMapper extends BaseMybatisMapper<NorthwindProductView> {
        constructor(connection: InjectableSqliteConnection) {
            super(connection);
        }
    
        public getEntityClass(): new () => NorthwindProductView {
            return NorthwindProductView;
        }
    }
    

    5.创造一个专门生成sql 的js 文件 (类似于mybatis xml文件)

    import { CommonHelper, DynamicQuery, Entity, EntityHelper, SqlTemplate, SqlTemplateProvider } from "../../../src";
    import { NorthwindProductView } from "../entity/view/NothwindProductView";
    
    export class ProductViewTemplate {
        public static getSelectProductViewByDynamicQuery(dynamicQuery: DynamicQuery<NorthwindProductView>): SqlTemplate {
            const columnAs = SqlTemplateProvider.getColumnsExpression(NorthwindProductView);
            const filterSqlTemplate = SqlTemplateProvider.getFilterExpression(NorthwindProductView, dynamicQuery.filters);
            const sortSqlTemplate = SqlTemplateProvider.getSortExpression(NorthwindProductView, dynamicQuery.sorts);
            const params = [];
            const wherePlaceholder = CommonHelper.isNotBlank(filterSqlTemplate.sqlExpression)
                ? `WHERE ${filterSqlTemplate.sqlExpression}`
                : ``;
            const orderByPlaceholder = CommonHelper.isNotBlank(sortSqlTemplate.sqlExpression)
                ? `ORDER BY ${sortSqlTemplate.sqlExpression}`
                : ``;
    
            const query = `SELECT ${columnAs} FROM Product LEFT JOIN Category ` +
                `ON Product.CategoryId = Category.Id ${wherePlaceholder} ${orderByPlaceholder}`;
    
            const sqlTemplate = new SqlTemplate();
            sqlTemplate.sqlExpression = query;
            sqlTemplate.params = sqlTemplate.params.concat(filterSqlTemplate.params).concat(sortSqlTemplate.params);
            return sqlTemplate;
        }
    }
    

    6.绑定

    import { Container } from "inversify";
    import "reflect-metadata";
    import { InjectableSqliteConnection } from "./connection/injectableSqliteConnection";
    import { InjectableSqlitedb } from "./connection/injectableSqlitedb";
    import { NorthwindProductView } from "./entity/view/NothwindProductView";
    import { ProductViewMapper } from "./mapper/productViewMapper";
    import { ProductViewTemplate } from "./template/productViewTemplate";
    
    const myContainer = new Container();
    myContainer.bind<InjectableSqliteConnection>(InjectableSqliteConnection).toSelf();
    myContainer.bind<ProductViewMapper>(ProductViewMapper).toSelf();
    myContainer.bind<InjectableSqlitedb>(InjectableSqlitedb).toSelf();
    

    7.测试注入

    describe("inject Test", () => {
        it("should get inject value", () => {
                const productViewMapper = myContainer.get<ProductViewMapper>(ProductViewMapper);
                expect(false).to.be.eq(CommonHelper.isNullOrUndefined(productViewMapper));
            });
    });
    

    mybatis 风格查询

    1.添加模板, 这里我们看到 #{price} 就是一个站位

    export class ProductViewTemplate {
        public static getSelectPriceGreaterThan20(): string {
            const columnAs = SqlTemplateProvider.getColumnsExpression(NorthwindProductView);
            const query = `SELECT ${columnAs} FROM Product LEFT JOIN Category ` +
                `ON Product.CategoryId = Category.Id WHERE Product.UnitPrice > #{price}`;
            return query;
        }
    }
    

    2.查询,这里我们把参数放到一个map里面

    describe("base Mapper test", () => {
        const productViewMapper = myContainer.get<ProductViewMapper>(ProductViewMapper);
            it("mybatis style sql template", (done) => {
                const query = ProductViewTemplate.getSelectPriceGreaterThan20();
                const paramMap: { [key: string]: any } = {};
                // 这里属性为price 的值为20
                paramMap.price = 20;
                productViewMapper.mybatisSelectEntities(query, paramMap)
                    .then((priceViews) => {
                        if (priceViews.length > 0) {
                            done();
                        } else {
                            done("should have items");
                        }
                    })
                    .catch((err) => {
                        done(err);
                    });
            });
    }
    

    3.查询结果

    base Mapper test
    sql:  SELECT Product.Id AS product_id, Product.ProductName AS product_name, Product.UnitPrice AS unit_price, Category.CategoryName AS category_name FROM Product LEFT JOIN Category ON Product.CategoryId = Categor
    y.Id WHERE Product.UnitPrice > ?
    params:  [ 20 ]
    √ mybatis style sql template
    

    分页

    基于上面的注入实战,
    1.添加动态查询模板

    export class ProductViewTemplate {
        // 这个就是以前的动态查询,请查看 mybatis-dynamic-query
        public static getSelectProductViewByDynamicQuery(dynamicQuery: DynamicQuery<NorthwindProductView>): SqlTemplate {
            const columnAs = SqlTemplateProvider.getColumnsExpression(NorthwindProductView);
            const filterSqlTemplate = SqlTemplateProvider.getFilterExpression(NorthwindProductView, dynamicQuery.filters);
            const sortSqlTemplate = SqlTemplateProvider.getSortExpression(NorthwindProductView, dynamicQuery.sorts);
            const params = [];
            const wherePlaceholder = CommonHelper.isNotBlank(filterSqlTemplate.sqlExpression)
                ? `WHERE ${filterSqlTemplate.sqlExpression}`
                : ``;
            const orderByPlaceholder = CommonHelper.isNotBlank(sortSqlTemplate.sqlExpression)
                ? `ORDER BY ${sortSqlTemplate.sqlExpression}`
                : ``;
    
            const query = `SELECT ${columnAs} FROM Product LEFT JOIN Category ` +
                `ON Product.CategoryId = Category.Id ${wherePlaceholder} ${orderByPlaceholder}`;
    
            const sqlTemplate = new SqlTemplate();
            sqlTemplate.sqlExpression = query;
            sqlTemplate.params = sqlTemplate.params.concat(filterSqlTemplate.params).concat(sortSqlTemplate.params);
            return sqlTemplate;
        }
    
        public static getSelectPriceGreaterThan20(): string {
            const columnAs = SqlTemplateProvider.getColumnsExpression(NorthwindProductView);
            const query = `SELECT ${columnAs} FROM Product LEFT JOIN Category ` +
                `ON Product.CategoryId = Category.Id WHERE Product.UnitPrice > #{price}`;
            return query;
        }
    }
    

    2.分页查询

    it("paging", (done) => {
                const priceFilter =
                    new FilterDescriptor<NorthwindProductView>((u) => u.unitPrice, FilterOperator.LESS_THAN, 20);
                const nameSort =
                    new SortDescriptor<NorthwindProductView>((u) => u.productName);
                const dynamicQuery = DynamicQuery.createIntance<NorthwindProductView>()
                    .addFilters(priceFilter).addSorts(nameSort);
    
                const sqlTemplate = ProductViewTemplate.getSelectProductViewByDynamicQuery(dynamicQuery);
                const pageRowBounds = new PageRowBounds(1, 20);
                productViewMapper
                    .selectEntitiesPageRowBounds(sqlTemplate.sqlExpression, sqlTemplate.params, pageRowBounds)
                    .then((page) => {
                        const pageIndexEq = page.getPageNum() === pageRowBounds.getPageNum();
                        const pageSizeEq = page.getPageSize() === pageRowBounds.getPageSize();
                        const entitiesCountEq = page.getEntities.length <= pageRowBounds.getPageSize();
                        const pagesEq = page.getPages() === Math.ceil(page.getTotal() / page.getPageSize());
                        if (pageIndexEq && pageSizeEq && entitiesCountEq && pagesEq) {
                            done();
                        } else {
                            done("something is invalid");
                        }
                    })
                    .catch((err) => {
                        done(err);
                    });
            });
    

    3.输出分页

    sql:  SELECT Product.Id AS product_id, Product.ProductName AS product_name, Product.UnitPrice AS unit_price, Category.CategoryName AS category_name FROM Product LEFT JOIN Category ON Product.CategoryId = Categor
    y.Id WHERE Product.UnitPrice < ? ORDER BY Product.ProductName ASC limit 0, 20
    params:  [ 20 ]
    selec Count sql:  SELECT COUNT(0) FROM (SELECT Product.Id AS product_id, Product.ProductName AS product_name, Product.UnitPrice AS unit_price, Category.CategoryName AS category_name FROM Product LEFT JOIN Catego
    ry ON Product.CategoryId = Category.Id WHERE Product.UnitPrice < ? ORDER BY Product.ProductName ASC) AS t
    params:  [ 20 ]
    √ paging
    

    结束

    为了这次1024,我真是有点赶想让大家早点看看这个项目,当然还有很多测试要做。
    而且我这里并没有讲动态查询,后面的文章会补上。
    祝大家节日快乐。

    关注我 ##

    最后大家可以关注我和 Mybatis-Dynamic-query项目 _
    Follow @wz2cool Star Fork

  • 相关阅读:
    linux机器间建立信任关系
    Linux shell逐行读取文件的方法
    linux强制用户下线命令
    Linux用户都应该了解的命令行省时技巧
    常用Linux Shell命令组合
    利用TensorFlow object_detection API 训练自己的检测器
    逻辑回归的梯度下降计算
    Android内存优化(使用SparseArray和ArrayMap代替HashMap)
    卷积的三种模式:full、same、valid + 卷积输出size的计算
    关于转置卷积(反卷积)的理解
  • 原文地址:https://www.cnblogs.com/wz2cool/p/7724376.html
Copyright © 2020-2023  润新知