Flutter数据库操作插件simple_mysql_orm的使用
Flutter数据库操作插件simple_mysql_orm的使用
simple_mysql_orm
提供了一个轻量级的包装器,用于 galileo_mysql
包,添加了一个对象关系映射(ORM)层。以下是该库的一些特性:
- 完整的事务支持
- 数据库连接池
- 简单的代码生成器(有待改进)
代码生成器
Simple ORM
现在有一个非常粗糙的代码生成器。
它可以从数据库表生成模型和DAO类。
要使用生成器,请执行以下命令:
dart pub global activate simple_mysql_orm
build_dao --host <host> --port <port> --database <db> --user <user> --password <password> --table <table>
上述命令将在当前目录下生成文件:
<table>.dart
dao_<table>.dart
如果这两个文件已存在,生成将失败。
你可以通过传递 --no-dao
参数来排除DAO文件的生成。
你可以通过传递 --file
参数来控制输出文件的名称。
示例用法
这是一个示例用法。完整的实现可以在示例目录中找到。
对于每个表,你需要创建一个数据访问对象(DAO),其中包含所有业务规则,以及一个实体,其中仅包含实体的字段。
DAO示例
import 'package:simple_mysql_orm/simple_mysql_orm.dart';
import '../../test_dao/model/system.dart';
class DaoSystem extends Dao<System> {
DaoSystem() : super(tablename);
DaoSystem.withDb(Db db) : super.withDb(db, tablename);
static String get tablename => 'system';
@override
System fromRow(Row row) => System.fromRow(row);
Future<String?> getByKey(String keyName) async =>
(await getByField('key', keyName)).value;
Future<String?> tryByKey(String keyName) async =>
(await tryByField('key', keyName))?.value;
}
实体示例
import 'package:simple_mysql_orm/simple_mysql_orm.dart';
class System extends Entity<System> {
factory System({required String key, required String value}) =>
System._internal(
key: key,
value: value,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
id: -1);
factory System.fromRow(Row row) {
final id = row.asInt('id');
final key = row.asString('key');
final value = row.tryAsString('value');
final createdAt = row.asDateTime('createdAt');
final updatedAt = row.asDateTime('updatedAt');
return System._internal(
id: id,
key: key,
value: value,
createdAt: createdAt,
updatedAt: updatedAt);
}
System._internal({
required int id,
required this.key,
required this.value,
required this.createdAt,
required this.updatedAt,
}) : super(id);
late String key;
late String? value;
late DateTime createdAt;
late DateTime updatedAt;
@override
FieldList get fields => [
'key',
'value',
'createdAt',
'updatedAt',
];
@override
ValueList get values => [
key,
value,
createdAt,
updatedAt,
];
}
使用DAO和实体
import 'package:logging/logging.dart';
import 'package:settings_yaml/settings_yaml.dart';
import 'package:simple_mysql_orm/simple_mysql_orm.dart';
import 'dao/package_dao.dart';
import 'model/package.dart';
Future<void> main() async {
/// 配置日志记录以输出每条SQL命令。
Logger.root.level = Level.INFO;
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
/// 创建设置文件。
await SettingsYaml.fromString(
content: settingsYaml, filePath: 'settings.yaml')
.save();
/// 从设置初始化数据库连接池。
DbPool.fromSettings(pathToSettings: 'settings.yaml');
/// 在事务中运行一组查询。
await withTransaction<void>(action: () async {
final dao = PackageDao();
/// 创建并保存一个包。
final package = Package(name: 'dcli', private: false);
await dao.persist(package);
/// 更新包为公开。
package.private = false;
await dao.update(package);
/// 使用非常基础且不完整的生成器查询包。
var rows = await dao.select().where().eq('name', 'dcli').run();
for (final row in rows) {
print('name: ${row.name} private: ${row.private}');
}
/// 执行自定义查询。
rows = await dao.query('select * from package where id = ?', [package.id]);
for (final row in rows) {
print('name: ${row.name} private: ${row.private}');
}
// 删除包。
await dao.remove(package);
/// 改变主意。
await Transaction.current.rollback();
});
}
const settingsYaml = '''
mysql_user: root
mysql_password: my root password
mysql_host: localhost
mysql_port: 3306
mysql_db: some_db_name
''';
多租户
SMO
支持多租户数据库的概念。
这是一些简单的实现,并且需要你确保所有的查询都是完全多租户的。
模式
每个多租户表必须有一个租户ID。
你可以选择列名,并且可以为每个表不同,但最好使用相同的列名。
租户ID通常是定义租户的表的主键。
例如:
Company
int id
String name
Staff Member
int id
int companyId
String name
Team
int id
int companyId
String name
DAO租户
对于每个具有租户ID的表,你必须创建一个 DaoTenant
而不是 Dao
。
DaoTenant
需要你提供该表的租户字段。
class DaoMember extends DaoTenant<Member> {
DaoMember() : super(tableName: tablename, tenantFieldName: 'companyId');
DaoMember.withDb(Db db)
: super.withDb(db, tableName: tablename, tenantFieldName: 'companyId');
/// 如果 [email] 不属于租户的一个成员,则抛出 [UnknownMemberException]。
Future<Member> getByEmail({required String email}) async {
final member = await trySingle(
await query('select * from $tablename '
'where email = ? '
'and companyId = ?', [email, Tenant.tenantId]));
if (member == null) {
throw UnknownMemberException(
'The email address $email is not for a know member');
}
return member;
}
}
Company
表应该从 Dao
而不是 DaoTenant
衍生。
你也可能有一些非租户表,例如用于存储全局值(如SMTP服务器主机地址)的系统表。
这些表应使用 Dao
并不需要租户ID。
租户实体
租户实体就像普通的实体一样,只是它们不会暴露租户ID字段(而普通实体应该暴露所有字段)。
租户ID不应该在租户实体中提及,因为它会被注入。
import 'package:date_time/date_time.dart';
import 'package:simple_mysql_orm/simple_mysql_orm.dart';
/// What unpud refers to as an uploader
class Member extends EntityTenant<Member> {
factory Member({
required String email,
}) =>
Member._internal(
email: email,
startDate: DateExtension.now(),
enabled: true,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
id: -1);
factory Member.fromRow(Row row) {
final id = row.asInt('id');
final email = row.asString('email');
final startDate = row.asDate('startDate');
final enabled = row.asBool('enabled');
final createdAt = row.asDateTime('createdAt');
final updatedAt = row.asDateTime('updatedAt');
return Member._internal(
email: email,
startDate: startDate,
enabled: enabled,
createdAt: createdAt,
updatedAt: updatedAt,
id: id);
}
Member._internal({
required int id,
required this.email,
required this.startDate,
required this.enabled,
required this.createdAt,
required this.updatedAt,
}) : super(id);
late String email;
late Date startDate;
late bool enabled;
late DateTime createdAt;
late DateTime updatedAt;
@override
FieldList get fields => [
'email',
'startDate',
'enabled',
'createdAt',
'updatedAt'
];
@override
ValueList get values => [email, startDate, enabled, createdAt, updatedAt];
}
typedef MemberId = int;
访问租户表
要访问租户表,你需要将所有对这些表的访问放在租户范围中。
我们通过 withTenant
方法来实现这一点。
/// 公司不是租户,因此我们可以在 `withTenant` 之外访问它。
await DaoCompany().getByName('noojee');
await withTenant(
tenantId: company.id,
action: () async {
/// 插入一个成员,租户ID将自动设置。
const noojeeEmail = 'sales@noojee.com.au';
final noojeeMember =
Member(publisherId: noojeeId, email: noojeeEmail);
final noojeeMemberId = await daoMember.persist(noojeeMember);
/// 获取一个成员,租户ID将被添加到WHERE子句中。
final member = await daoMember.getByEmail(email: noojeeEmail);
/// 系统表是非租户表。
/// 我们可以在或不在作用域内访问它。
await DaoSystem().getByKey('smtp')
});
绕过租户访问
默认情况下,SMO
将尝试检查你是否总是过滤查询以包含租户ID。
这种方法并不完全可靠(见下文)。
但在某些情况下,你可能希望访问实现 DaoTenant
的表而不使用租户ID来限制结果。
例如,系统管理员需要访问所有租户的员工表。
你也很可能需要在租户模式和绕过租户模式下访问用户表。
在登录过程中,直到你查询用户表之前,你都不知道租户。但在正常操作中,用户表应始终作为租户表访问。
对于这些场景,你可以使用 withTenantBypass
。
当你使用 withTenantBypass
时,DaoTenant
将不会将租户ID注入你的查询。
Future<User?> loginUser(String username) async {
return await withTenantByPass(
action: () async {
/// 获取一个成员,租户ID将被添加到WHERE子句中。
return await daoMember.tryByEmail(email: username);
});
为了使这有效,你需要确保用户名在所有租户中是唯一的。
你也可以在 withTenantBypass
中嵌套 withTenant
到任意级别。
租户查询验证
SMO
允许你编写自定义查询。当你写自定义查询时,由你负责确保遵循租户规则。
SMO
尝试验证所有查询是否包含租户字段,但这只是一个粗略的检查(我们只是查找列名的存在)。
编写自定义查询
当你编写自定义查询时,你通常知道你是在租户模式还是非租户模式,因为你的自定义代码应该存在于继承自 Dao
或 TenantDao
的类中。
对于那些可能不是这种情况的情况,你可以使用以下代码:
if (Tenant.inTenantScope)
{
/// 被调用在 [withTenant] 调用中。
}
if (Tenant.inTenantBypassScope)
{
/// 被调用在 [withTenantBypass] 中。
}
if (dao is DaoTenant)
{
}
日志记录
要输出发送到数据库的每个查询,请将日志记录设置为 FINE
。
要获取所有数据库交互,请将日志记录设置为 FINER
。
测试
单元测试期望一个预存在的模式。要创建模式,请运行:
dmysql restore smo < schema\createv3.sql
更多关于Flutter数据库操作插件simple_mysql_orm的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter数据库操作插件simple_mysql_orm的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用simple_mysql_orm
插件进行数据库操作的示例代码。请注意,simple_mysql_orm
通常用于与MySQL数据库交互,而Flutter是一个用于构建跨平台移动应用的框架。因此,在Flutter应用中与MySQL数据库通信通常涉及网络请求。
首先,确保你已经在pubspec.yaml
文件中添加了simple_mysql_orm
依赖项:
dependencies:
flutter:
sdk: flutter
simple_mysql_orm: ^最新版本号 # 替换为实际最新版本号
然后,运行flutter pub get
来安装依赖项。
以下是一个简单的示例,展示了如何使用simple_mysql_orm
在Flutter应用中与MySQL数据库进行交互。
1. 配置MySQL连接
你需要配置MySQL数据库的连接信息。这通常在应用的某个初始化部分完成。
import 'package:simple_mysql_orm/simple_mysql_orm.dart';
// 配置MySQL连接信息
final MySQLConnectionInfo connectionInfo = MySQLConnectionInfo(
host: '你的数据库主机地址',
port: 3306, // MySQL默认端口
user: '你的数据库用户名',
password: '你的数据库密码',
database: '你的数据库名称',
);
// 创建MySQL客户端
final MySQLClient client = MySQLClient(connectionInfo);
2. 定义数据模型
使用@Table
和@Column
注解定义你的数据模型。
import 'package:simple_mysql_orm/annotations.dart';
@Table('users')
class User {
@Column(primaryKey: true, autoIncrement: true)
int id;
@Column()
String name;
@Column()
String email;
// 构造函数和其他方法...
User({required this.name, required this.email});
// 从数据库结果映射到对象
User.fromMap(Map<String, dynamic> map) {
id = map['id'] as int;
name = map['name'] as String;
email = map['email'] as String;
}
// 将对象转换为Map,用于插入或更新数据库
Map<String, dynamic> toMap() {
return {
'name': name,
'email': email,
};
}
}
3. 执行数据库操作
现在,你可以使用MySQLClient
实例执行数据库操作,如插入、查询、更新和删除。
void main() async {
// 初始化MySQL客户端(通常在实际应用中,这会在某个服务或依赖注入容器中完成)
final MySQLClient client = MySQLClient(connectionInfo);
// 打开连接
await client.open();
// 插入数据
User user = User(name: 'John Doe', email: 'john.doe@example.com');
await client.insert(user.toMap(), into: 'users');
// 查询数据
List<Map<String, dynamic>> results = await client.selectAllFrom('users');
List<User> users = results.map((e) => User.fromMap(e)).toList();
print('Users: $users');
// 更新数据
User userToUpdate = users.firstWhere((user) => user.id == 1);
userToUpdate.email = 'new.email@example.com';
await client.update(userToUpdate.toMap(), where: 'id = ?', whereArgs: [userToUpdate.id], table: 'users');
// 删除数据
await client.deleteFrom('users', where: 'id = ?', whereArgs: [userToUpdate.id]);
// 关闭连接
await client.close();
}
注意事项
- 异步操作:所有数据库操作都是异步的,因此使用
async
和await
关键字。 - 错误处理:在实际应用中,添加适当的错误处理逻辑来捕获和处理可能发生的异常。
- 连接管理:确保正确管理数据库连接的生命周期,特别是在应用进入后台或关闭时关闭连接。
- 安全性:不要在客户端代码中硬编码敏感信息,如数据库密码。考虑使用环境变量或安全的配置管理工具。
由于simple_mysql_orm
是一个第三方库,其API和用法可能会随着版本的更新而发生变化。因此,建议查阅最新的官方文档以获取最准确的信息。