前言

在发布了 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

TsBatis 预览的更多相关文章

  1. Word/Excel 在线预览

    前言 近日项目中做到一个功能,需要上传附件后能够在线预览.之前也没做过这类似的,于是乎就查找了相关资料,.net实现Office文件预览大概有这几种方式: ① 使用Microsoft的Office组件 ...

  2. 预览github里面的网页或dome

    1.问题所在: 之前把项目提交到github都可以在路径前面加上http://htmlpreview.github.io/?来预览demo,最近发现这种方式预览的时候加载不出来css,js(原因不详) ...

  3. IE8/9 本地预览上传图片

    本地预览的意思是,在选择图片之后先不上传到服务器,而是由一个<img>标签来预览本地的图片,非 IE8/9 浏览器可以从<input type="file"/&g ...

  4. JS图片上传预览插件制作(兼容到IE6)

    其实,图片预览功能非常地常见.很意外,之前遇到上传图片的时候都不需要预览,也一直没有去实现过.现在手上的项目又需要有图片预览功能,所以就动手做了一个小插件.在此分享一下思路. 一.实现图片预览的一些方 ...

  5. [干货来袭]MSSQL Server on Linux预览版安装教程(先帮大家踩坑)

    前言 昨天晚上微软爸爸开了全国开发者大会,会上的内容,我就不多说了,园子里面很多.. 我们唐总裁在今年曾今透漏过SQL Server love Linux,果不其然,这次开发者大会上就推出了MSSQL ...

  6. 微软发布 Windows Server 2016 预览版第三版,开发者要重点关注Nano Server

    微软已经发布 Windows Server 2016 和 System Center 2016 第三个技术预览版,已经提供下载.Windows Server 2016 技术预览版第三版也是首个包括了容 ...

  7. Visual Studio Code预览版Ver 0.3.0试用体验

    当你开始阅读这篇文章时,请先不要把Visual Studio Code和.net.Windows联想到一起,因为VS Code是一个跨平台,支持30多种语言的开箱代码编辑器.不管你是.Net.Java ...

  8. UploadFile控件,提交图片后,页面预览显示刚刚提交的图片

    最近在用asp.net来写一个新闻系统后台,然后由于不用用网上的flash插件来上传图片什么的,我就用asp.net的控件来写,但是控件总归有一些用的不够灵活的地方.这次测试提出,文章在修改的时候,需 ...

  9. JS魔法堂之实战:纯前端的图片预览

    一.前言 图片上传是一个普通不过的功能,而图片预览就是就是上传功能中必不可少的子功能了.在这之前,我曾经通过订阅input[type=file]元素的onchange事件,一旦更改路径则将图片上传至服 ...

随机推荐

  1. postman: 用于网页调试和发送Http请求的chrome插件

    一 简介 Postman 是一款功能超级强大的用于发送 HTTP 请求的 Chrome插件 .做web页面开发和测试的人员应该是无人不晓无人不用!其主要特点 特点: 创建 + 测试:创建和发送任何的H ...

  2. 模拟实现一个ATM+购物商城程序

    记得上次小编上传了一个购物车程序,这次呢稍微复杂一点,还是那句话,上传在这里不是为了炫耀什么,只是督促小编学习,如果大神有什么意见和建议,欢迎指导.(PS:本次主要参考学习为主,自己原创的很少) 要求 ...

  3. 移动商城第四篇【Controller配置、添加品牌之文件上传和数据校验】

    Controller层配置 编写SpringMVC的配置文件 springmvc.xml <?xml version="1.0" encoding="UTF-8&q ...

  4. 自动化测试之 seleniumIDE,Selenium1,selenium2和testNG入门

    由于前期三个月公司的项目一直在改需求阶段,一直是手动测试,现在项目雏形以及基本页面功能都确定下来,为了不让自己陷入天天测同一功能的无限循环中,故开始自动化测试的学习之路,也为自己以后的发展铺铺路. 一 ...

  5. 一种Webconfig自动化升级方法

    1.方法功能 使用本方法,可以将开发环境最新版本的web.config结构与生产环境环境的config融合,而不用考虑两个config的版本差异值是多少.使用一种标记的方式,在开发环境webconfi ...

  6. MyEclipse/Eclipse 使用图文详解

    引言 某天在群里看到有小伙伴问MyEclipse/Eclipse的一些使用问题,虽然在我看来,问的问题很简单,但是如果对于刚刚学习的人来说,可能使用就不那么友好了.毕竟我在开始使用MyEclipse/ ...

  7. [解读REST] 4.基于网络应用的架构风格

    上篇文章介绍了一组自洽的术语来描述和解释软件架构:如何利用架构属性评估一个架构风格:以及对于基于网络的应用架构来说,那些架构属性是值得我们重点关注评估的.本篇在以上的基础上,列举一下一些常见的(RES ...

  8. 内核对象 windows操作系统

    问题: 什么是内核对象? 答:内核对象实际上时由内核分配的一块内存,而且只能由内核来访问.它时一个数据结构,成员包含有关于该对象的信息.一些成员对于所有对象类型都是一样的,比如对象名称.安全描述.使用 ...

  9. sql语句增删改查与子查询

    修改表 修改表 语法: Alter table <旧表名> rename [ TO] <新表名>; 例子:Alter table `demo01` rename `demo02 ...

  10. Python uwsgi 无法安装以及编译报错的处理方式

    之前安装uwsgi的时候编译一步有出错,因为比较早,部分错误代码已经找不到了,网上找了部分错误信息, 现把解决方式共享出来. 环境:CentOS release 6.4   Python 2.7.3 ...