进阶-数据库
Z1h内嵌了数据库操作, 最主要的包括mysql
和sqlite3
如需支持Postgres
/ Tidb
/ MsSql
/ Oracle
, 请联系作者
开启
参考运行配置, 并配置好db
信息, 即可连接数据库
...
{
"db": {
"type": "mysql", // 数据库类型
"address": "" // 数据库连接地址,sqlite3时可写文件名
}
}
...
有两种方式可以执行数据库操作: SQL
方式和 ORM
方式
SQL操作
通过$db
可以操作SQL
// 建表
assert($db(`CREATE TABLE test_db(name VARCHAR)`))
// 插入
assert($db(`INSERT INTO test_db(name) VALUES("aaa")`))
// 插入时用占位符
assert($db(`INSERT INTO test_db(name) VALUES(?)`, "bbb"))
// 删除
assert($db(`DELETE FROM test_db WHERE name = ?`, "aaa"))
// 查询
assert($db(`SELECT * FROM test_db WHERE name != ?`, "aaa"))
// 修改
assert($db(`UPDATE test_db SET name = ? WHERE name != ?`, "aaa", "aaa"))
// ...
// 更多操作请自学SQL
ORM
对象关系映射(Object Relational Mapping, 简称ORM)是通过使用描述对象和数据库之间映射的元数据, 将面向对象语言程序中的对象自动持久化到关系数据库中
$db
对象有以下函数:
- 建表结构:
sync
- 插入数据:
insert
- 查询相关:
find
,count
,rows
,select
- 更新数据:
update
- 删除数据:
delete
- 直接执行:
exec
,query
- 事务相关:
begin
,commit
,rollback
, - 语句支持:
expr
,where
以下示例代码的运行环境是递进的, 请一步步跟着示例代码来测试(尤其是建表和插入数据):
sync - 创建表
参数为表名、结构
// 简单模式
assert($db.sync('test', {
name: '',
age: 0,
}))
// 复杂模式
assert($db.sync('test', {
name: `VARCHAR(32) NOT NULL DEFAULT ''`,
age: `INT(11) NOT NULL DEFAULT 18`,
}))
insert - 插入数据
参数为表名、数据(可多条数据)
// 插入两行数据, 返回的是插入成功的行数
assert($db.insert('test', {
name: 'zwr',
age: 22,
}, {
name: 'zq',
age: 21,
}))
// 插入一行数据时, 返回的是id
assert($db.insert('test', {
name: 'xxx',
age: 10,
}))
查询相关
find
find
函数传入参数为表名、过滤条件、其它参数, 返回的数据格式为(datas []map[string]string, error)
assert($db.find('test')) // 查询全表数据
assert($db.find('test', 1)) // 指定id查询
assert($db.find('test', [1, 2])) // 指定多个id查询
assert($db.find('test', {age: 22})) // 匹配条件
assert($db.find('test', {age: {$ne: 22}})) // 比较条件(不等于22)
assert($db.find('test', {age: {$lt: 22}})) // 比较条件(小于22)
assert($db.find('test', {name: $db.expr(`LIKE 'z%'`)})) // 复杂条件
// 第三个参数的用法
assert($db.find('test', {}, {
desc: 'age', // 按照字段从大到小排列(asc为从小到大)
offset: 1, // 跳过几行数据
limit: 2, // 查询返回几行数据
keys: ['name', 'age'], // 只要某些字段,
}))
count
以上的find
示例(除指定单个/多个id外)均可替换成count
, 返回值为吻合条件的行数, 例如
assert($db.count('test')) // 统计全表数据行数
assert($db.count('test', {age: {$ne: 22}})) // 统计年龄不为22的数据数
assert($db.count('test', {age: {$lt: 22}})) // 统计年龄不足22的数据数
select
find
函数是一次性将数据完整请求下来, 但如果符合条件的数据量过大并且没有设置合理的limit, 一次性请求下来容易造成内存爆满(OOM), 此时建议使用select
函数
select
函数的最后一个参数为一个处理函数, 用于处理逐行扫描的结果(其它参数与find相同)
第三个参数可以指定reuse/empty/result(均为true/false), 分别代表: 是否重用一个指针、是否需要NULL字段、是否需要返回全部处理结果(类似array.map方法)
// 查全表, 逐行处理
$db.select(`test`, e=>{
print(e)
})
// 带条件或排序等其它限制
$db.select(`test`, {
age: {$gt: 2}, // 年龄 > 2
}, {
keys: ['name'], // 只要name字段
asc: 'age', // 按年龄升序
limit: 2, // 只要2条
reuse: true, // 复用指针
empty: false, // 空字段是否也要
result: true, // 是否需要返回值
}, ({name}, index) => { // 展开对象
print(index, name)
})
update - 更新数据
传入参数为表名、过滤条件(可为id或id列表)、更新内容
// 返回更新成功的行数
assert($db.update('test', 3, {
name: 'aaaaa',
}))
// 根据条件
print(`修改前:`, assert($db.find(`test`, 3))[0].age)
assert($db.update('test', {
name: 'aaaaa',
}, {
age: $db.expr(` = age - 3`),
}))
print(`修改后:`, assert($db.find(`test`, 3))[0].age)
delete - 删除数据
传入参数为表名、过滤条件(也可以是id)
// 删除id=3的数据
assert($db.delete(`test`, 3))
// 删除符合筛选条件的数据
assert($db.delete(`test`, {
age: 88,
}))
执行SQL
Exec
方法直接执行一条SQL
Query
方法进行一次SQL查询
assert($db.Exec(`alter table test add column tall INT(11) NOT NULL DEFAULT 0`))
assert($db.Query(`SELECT COUNT(*) FROM test`))
begin、commit、rollback
通过begin
函数开启一个事务, 返回一个和$db有相同函数的对象, 最后通过调用commit
来提交修改或调用rollback
回滚所有操作
示例代码:
// 场景: 修改zwr的age为100, 当zq的age为22时commit, 否则回滚
// 创建事务
var tx = assert($db.begin())
// 默认进行回滚(如果commit成功, 回滚会被无视)
defer tx.rollback()
// 尝试第一次
assert(tx.update('test', 1, {
age: 100
}))
// 看一下修改情况
print(
`事务内数据: ${assert(tx.find('test', 1))}
事务外数据: ${assert($db.find('test', 1))}`,
)
if (int(assert(tx.find('test', 2))[0].age) == 22) {
assert(tx.commit())
print(`查询到id=2的年龄吻合, 提交修改成功`)
print(`事务外数据: ${assert($db.find('test', 1))}`)
} else {
print(`查询到id=2的年龄不吻合, 不提交修改, 会触发defer的回滚`)
}
然后修改数据, 再运行一次上面的代码
assert($db.update('test', 2, {
age: 22
}))
// 再运行上面那一段代码
语句支持
expr
expr
可以实现(不安全的)SQL修改
使用示例
// 企图让id=2的数据的age字段自增3, 以下做法是错误的
assert($db.update('test', 2, {
age: 'age + 3',
}))
// 在mysql时, 会报错: 类型错误
// 在sqlite3下, age的内容会变成"age + 3"
// 都是不符合我们预期的
// 先把age设置回数字
assert($db.update('test', 2, {
age: 23
}))
// 正确的做法是用$db.expr
assert($db.update('test', 2, {
age: $db.expr('= age + 3'),
}))
// 看一看修改后的数据
assert($db.find('test', 2))
同理, 在find、update的时候也可以
// 模糊查询
assert($db.find('test', {
name: $db.expr(`LIKE 'z%'`),
}))
// 更新时使用子查询或者内置函数
assert($db.update('test', 1, {
age: $db.expr(`= (SELECT COUNT(*) FROM test)`),
// create_at: $db.expr(`= datetime()`), // sqlite时间函数
// update_at: $db.expr(`= now()`), // mysql时间函数
}))
where(非必学)
where
可以查看筛选条件的编译结果
$db.where({
$or: [{
age: {$gt: 2},
name: $db.expr(`LIKE 'z%'`),
id: {$in: [1, 2, 3]},
}, {
age: {$between: [6, 10]},
}],
})
// 返回值第一项是SQL语句, 第二项是占位符的实参, 最后一项是error
go-xorm
如果你使用的是Go版Z1h, 环境内置了https://github.com/go-xorm/xorm 第三方库
通过$xorm
可以用ORM的方式操作数据库
// 建表
var table_struct = new('struct', {
id: 0,
username: ``,
password: ``,
age: 0,
updateAt: now(),
createAt: now(),
}, {
id: `xorm:"not null pk autoincr INT(11)"`,
username: `xorm:"not null VARCHAR(64) unique(openid)"`,
password: `xorm:"not null VARCHAR(64)"`,
age: `xorm:"not null int(11) default 18"`,
updateAt: `xorm:"updated"`,
createAt: `xorm:"created"`,
});
assert($xorm.Table('test_db2').Sync2(table_struct))
// 插入
assert($xorm.Table('test_db2').Insert({
username: 'admin',
password: md5("Password").hex(),
age: 12,
}))
// 插入时带创建时间
assert($xorm.Table('test_db2').Insert(new(table_struct, {
username: 'admin2',
password: md5("Password2").hex(),
age: 12,
})))
// 删除
assert($xorm.Table('test_db2').Id(1).Delete(new(table_struct)))
assert($xorm.Table('test_db2').Where('username = ?', 'admin').Delete(new(table_struct)))
// 查询
var results = &new(table_struct, '[]');
assert($xorm.Table('test_db2').Where('username = ?', 'admin1').Find(results));
results = *results;
print(results.map(e=>{e.username}).join(' and '));
// 修改
assert($xorm.Table('test_db2').Id(2).Update(new(table_struct, {
username: 'hello',
})))
// ...
// 更多操作请参考xorm官方文档 https://github.com/go-xorm/xorm