Skip to content

新增便捷 SQLite 模式 #362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion docs/components/common/global-defines.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ container()->get('xxx');

### db()

获取 Database 操作类
获取 Database 数据库连接操作类

- 定义:`db(string $name = '')`
- 返回:`ZM\Store\Database\DBWrapper`
Expand Down Expand Up @@ -245,6 +245,16 @@ $result = db('mydb')->fetchAllAssociative('SELECT * FROM users WHERE username =
var_dump($result[0]); // 假设数据库表只有 id 和 username 两列,这里返回了 ['id' => 1, 'username' => 'jerry']
```

有关此处数据库更详细的内容,请看 [SQL 数据库组件](/components/store/mysql.md)。

### zm_sqlite()

> 仅限于炸毛框架 3.2.0 及以上版本使用。

获取一个便捷 SQLite 模式的数据库操作对象。

有关此处数据库更详细的内容,请看 [SQL 数据库组件](/components/store/mysql.md)。

### sql_builder()

使用 SQL 语句构建器构建一个查询。
Expand All @@ -261,6 +271,16 @@ $result = sql_builder('mydb')->select('*')->from('users')->where('username = :us
// 结果与上方相同
```

有关此处数据库更详细的内容,请看 [SQL 数据库组件](/components/store/mysql.md)。

### zm_sqlite_builder()

> 仅限于炸毛框架 3.2.0 及以上版本使用。

获取一个便捷 SQLite 模式的数据库 SQL 语句构造器。

有关此处数据库更详细的内容,请看 [SQL 数据库组件](/components/store/mysql.md)。

### redis()

获取 Redis 操作类。有关 Redis 的更多详情和配置,见 [Redis 数据库组件](/components/store/redis)。
Expand Down
40 changes: 36 additions & 4 deletions docs/components/store/mysql.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
| 1 | jack | man | 2021-10-12 |
| 2 | rose | woman | 2021-10-11 |

## 配置
## 连接池

炸毛框架的数据库组件支持原生 SQL、查询构造器,去掉了复杂的对象模型关联,同时默认为数据库连接池,使开发变得简单。

Expand Down Expand Up @@ -46,9 +46,9 @@ $config['database'] = [

在设置了 enable 为 true 后,将创建对应数据库的连接池。在框架所有插件加载后启用前会创建连接池。

## 执行 SQL 语句
## 连接池模式

框架对于不同种类的 SQL 采用了统一的 wrapper 层,保证不同数据库调用时的接口尽可能相同。获取数据库操作对象很简单,通过方法 `db()`:
框架对于不同种类的 SQL 采用了统一的 wrapper 层,保证不同数据库调用时的接口尽可能相同。从连接池拿取对象很简单,通过方法 `db()`:

```php
// 获取 default 名称的数据库连接
Expand All @@ -57,6 +57,34 @@ $db = db();
$sqlite = db('sqlite_db1');
```

返回的对象为 `DBWrapper` 对象。

## 便捷 SQLite 模式

对于 SQLite 数据库来说,使用连接池可能较为笨重,而且在开发者使用框架开发炸毛框架的插件分发时,可能需要使用 SQLite 数据库,但是又不想使用连接池。

框架在 3.2.0 版本开始提供了便捷 SQLite 访问,无需任何配置,仅需 `zm_sqlite('dbname.db')` 方式即可创建和访问一个 SQLite 数据库。

```php
// 连接一个 SQLite 数据库,在相对路径下,文件会保存到 zm_data/db/ 目录
$db = zm_sqlite('a.db');
// 连接一个 SQLite 数据库,可以是任意绝对路径
$db = zm_sqlite('/home/zhamao/a.db');
// 在连接 SQLite 文件时,如果设置了 create_new 参数为 False,文件不存在时将会抛出异常
$db = zm_sqlite('a.db', create_new: false);
// 在连接 SQLite 文件时,如果设置了 keep_alive 参数为 False,框架将不会缓存已经打开的 PDO 对象,而是每次都会重新打开。(默认为 True,为了提升性能)
$db = zm_sqlite('a.db', keep_alive: false);
```

返回的对象为 `DBWrapper` 对象。

::: tip 提示

无论是使用连接池的 `db()` 还是便捷 SQLite 模式的 `zm_sqlite()`,获取的都是 `DBWrapper` 对象,文档只是为了书写方便。
实际使用过程中如果要使用便捷 SQLite 模式只需将 `db` 替换为 `zm_sqlite` 即可。

:::

### 执行预处理 SQL 语句

预处理查询很巧妙地解决了 SQL 注入问题,并且可以方便地绑定参数进行查询。
Expand Down Expand Up @@ -241,13 +269,17 @@ $resultSet = sql_builder()->select(['username', 'gender'])->from('users')->where

### 获取 SQL Builder

使用全局函数 `sql_builder()` 即可。
连接池的访问模式,使用全局函数 `sql_builder()` 即可。便捷 SQLite 模式,使用全局函数 `zm_sqlite_builder()` 即可。

```php
// 获取 default 名称的数据库连接的 builder
$queryBuilder = sql_builder();
// 获取对应名称的数据库连接的 builder,名称等于上方配置中的键名
$queryBuilder = sql_builder('sqlite_db1');
// 使用便捷 SQLite 模式获取 builder
$queryBuilder = zm_sqlite_builder('mydb.db');
// 在使用便捷 SQLite 模式时,也可以传入 create_new 参数和 keep_alive 参数
$queryBuilder = zm_sqlite_builder('/home/a/d.db', create_new: false, keep_alive: false);
```

### 构建一个普通查询
Expand Down
1 change: 1 addition & 0 deletions docs/update/v3.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 更新日志


> 本页面由框架命令 `./zhamao generate:text update-log-md` 自动生成

## v3.1.14
Expand Down
3 changes: 3 additions & 0 deletions src/Globals/global_defines_app.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
const LOAD_MODE_SRC = 0; // 从 src 加载
const LOAD_MODE_VENDOR = 1; // 从 vendor 加载

const ZM_DB_POOL = 1; // 数据库连接池
const ZM_DB_PORTABLE = 2; // SQLite 便携数据库

/* 定义工作目录 */
define('WORKING_DIR', getcwd());

Expand Down
27 changes: 27 additions & 0 deletions src/Globals/global_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use ZM\Plugin\ZMPlugin;
use ZM\Schedule\Timer;
use ZM\Store\Database\DBException;
use ZM\Store\Database\DBPool;
use ZM\Store\Database\DBQueryBuilder;
use ZM\Store\Database\DBWrapper;
use ZM\Store\KV\KVInterface;
Expand Down Expand Up @@ -254,6 +255,32 @@ function sql_builder(string $name = ''): DBQueryBuilder
return (new DBWrapper($name))->createQueryBuilder();
}

/**
* 获取一个便携 SQLite 操作类
*
* @param string $name 使用的 SQLite 连接文件名
* @param bool $create_new 是否在文件不存在时创建新的
* @param bool $keep_alive 是否维持 PDO 对象以便优化性能
* @throws DBException
*/
function zm_sqlite(string $name, bool $create_new = true, bool $keep_alive = true): DBWrapper
{
return DBPool::createPortableSqlite($name, $create_new, $keep_alive);
}

/**
* 获取便携 SQLite 操作类的 SQL 语句构造器
*
* @param string $name 使用的 SQLite 连接文件名
* @param bool $create_new 是否在文件不存在时创建新的
* @param bool $keep_alive 是否维持 PDO 对象以便优化性能
* @throws DBException
*/
function zm_sqlite_builder(string $name, bool $create_new = true, bool $keep_alive = true): DBQueryBuilder
{
return zm_sqlite($name, $create_new, $keep_alive)->createQueryBuilder();
}

/**
* 获取 Redis 操作类
*
Expand Down
6 changes: 4 additions & 2 deletions src/ZM/Event/Listener/WorkerEventListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function onWorkerStart999(): void
});

// 注册各种池子
$this->initConnectionPool();
$this->initDBConnections();

// 加载用户代码资源
$this->initUserPlugins();
Expand Down Expand Up @@ -144,6 +144,7 @@ public function onWorkerStop999(): void
if (is_a(config('global.kv.use', \LightCache::class), LightCache::class, true)) {
LightCache::saveAll();
}
DBPool::resetPortableSQLite();
logger()->debug('{is_task}Worker 进程 #{id} 正在停止', ['is_task' => ProcessStateManager::isTaskWorker() ? 'Task' : '', 'id' => ProcessManager::getProcessId()]);

if (Framework::getInstance()->getDriver()->getName() !== 'swoole') {
Expand Down Expand Up @@ -260,9 +261,10 @@ private function dispatchInit(): void
*
* @throws DBException|RedisException
*/
private function initConnectionPool(): void
private function initDBConnections(): void
{
DBPool::resetPools();
DBPool::resetPortableSQLite();
RedisPool::resetPools();
}
}
2 changes: 1 addition & 1 deletion src/ZM/Framework.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Framework
public const VERSION_ID = 720;

/** @var string 版本名称 */
public const VERSION = '3.1.14';
public const VERSION = '3.2.0';

/**
* @var RuntimePreferences 运行时偏好(环境信息&参数)
Expand Down
46 changes: 40 additions & 6 deletions src/ZM/Store/Database/DBConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,49 @@

use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\ParameterType;
use ZM\Store\FileSystem;

class DBConnection implements Connection
{
private int $db_type;

/** @var \PDO */
private object $conn;

private $pool_name;

public function __construct($params)
public function __construct(private array $params)
{
logger()->debug('Constructing...');
$this->conn = DBPool::pool($params['dbName'])->get();
$this->pool_name = $params['dbName'];
$this->db_type = $params['dbType'] ?? ZM_DB_POOL;
if ($params['dbType'] === ZM_DB_POOL) {
// 默认连接池的形式,
logger()->debug('Constructing...');
$this->conn = DBPool::pool($params['dbName'])->get();
$this->pool_name = $params['dbName'];
} elseif ($params['dbType'] === ZM_DB_PORTABLE) {
$connect_str = 'sqlite:{filename}';
if (FileSystem::isRelativePath($params['filename'])) {
$params['filename'] = zm_dir(config('global.data_dir') . '/db/' . $params['filename']);
FileSystem::createDir(zm_dir(config('global.data_dir') . '/db'));
}
$table = [
'{filename}' => $params['filename'],
];
// 如果文件不存在则创建,但如果设置了 createNew 为 false 则不创建,不存在就直接抛出异常
if (!file_exists($params['filename']) && ($params['createNew'] ?? true) === false) {
throw new DBException("Database file {$params['filename']} not found!");
}
$connect_str = str_replace(array_keys($table), array_values($table), $connect_str);
$this->conn = new \PDO($connect_str);
}
}

public function __destruct()
{
logger()->debug('Destructing!!!');
DBPool::pool($this->pool_name)->put($this->conn);
if ($this->db_type === ZM_DB_POOL) {
logger()->debug('Destructing!!!');
DBPool::pool($this->pool_name)->put($this->conn);
}
}

/**
Expand Down Expand Up @@ -126,4 +150,14 @@ public function getPoolName()
{
return $this->pool_name;
}

public function getDbType(): int
{
return $this->db_type;
}

public function getParams(): array
{
return $this->params;
}
}
34 changes: 34 additions & 0 deletions src/ZM/Store/Database/DBPool.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ class DBPool
*/
private static array $pools = [];

/**
* @var array<string, DBWrapper> 持久化的便携 SQLite 连接对象缓存
*/
private static array $portable_cache = [];

/**
* 重新初始化连接池,有时候连不上某个对象时候可以使用,也可以定期调用释放链接
*
Expand All @@ -43,6 +48,16 @@ public static function resetPools(): void
}
}

/**
* 重新初始化所有的便携 SQLite 连接(其实就是断开)
*/
public static function resetPortableSQLite(): void
{
foreach (self::$portable_cache as $name => $wrapper) {
unset(self::$portable_cache[$name]);
}
}

/**
* 通过配置文件创建一个 MySQL 连接池
*
Expand Down Expand Up @@ -180,4 +195,23 @@ public static function checkMysqlExtension()
}
}
}

/**
* 创建一个便携的 SQLite 处理类
*
* @param string $name SQLite 文件名
* @param bool $create_new 如果数据库不存在,是否创建新的库
* @throws DBException
*/
public static function createPortableSqlite(string $name, bool $create_new = true, bool $keep_alive = true): DBWrapper
{
if ($keep_alive && isset(self::$portable_cache[$name])) {
return self::$portable_cache[$name];
}
$db = new DBWrapper($name, ['dbType' => ZM_DB_PORTABLE, 'createNew' => $create_new]);
if ($keep_alive) {
self::$portable_cache[$name] = $db;
}
return $db;
}
}
25 changes: 17 additions & 8 deletions src/ZM/Store/Database/DBWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,25 @@ class DBWrapper
* DBWrapper constructor.
* @throws DBException
*/
public function __construct(string $name)
public function __construct(string $name, array $options = [])
{
// 初始化配置
$db_type = $options['dbType'] ?? ZM_DB_POOL;
try {
$db_list = config()->get('global.database');
if (isset($db_list[$name]) || (is_countable($db_list) ? count($db_list) : 0) === 1) {
if ($name === '') {
$name = array_key_first($db_list);
if ($db_type === ZM_DB_POOL) {
// pool 为连接池格式
$db_list = config()->get('global.database');
if (isset($db_list[$name]) || (is_countable($db_list) ? count($db_list) : 0) === 1) {
if ($name === '') {
$name = array_key_first($db_list);
}
$this->connection = DriverManager::getConnection(['driverClass' => $this->getConnectionClass($db_list[$name]['type']), ...$options]);
} else {
throw new DBException('Cannot find database config named "' . $name . '" !');
}
$this->connection = DriverManager::getConnection(['driverClass' => $this->getConnectionClass($db_list[$name]['type']), 'dbName' => $name]);
} else {
throw new DBException('Cannot find database config named "' . $name . '" !');
} elseif ($db_type === ZM_DB_PORTABLE) {
// portable 为sqlite单文件模式
$this->connection = DriverManager::getConnection(['driverClass' => SQLiteDriver::class, 'filename' => $name, ...$options]);
}
} catch (\Throwable $e) {
throw new DBException($e->getMessage(), $e->getCode(), $e);
Expand All @@ -38,6 +46,7 @@ public function __construct(string $name)
public function __destruct()
{
$this->connection->close();
$this->connection->close();
}

/**
Expand Down