Flutter嵌入式数据库插件nitrite的使用
Flutter嵌入式数据库插件Nitrite的使用
Nitrite Database
Nitrite 是一个开源的 NoSQL 嵌入式文档存储库,支持内存和文件持久化存储。它是一个嵌入式的数据库,非常适合桌面、移动或小型Web应用程序。
Nitrite 的特点:
- 嵌入式,无服务器
- 简单的API
- 文档导向
- 无模式的文档集合和对象仓库
- 索引和全文搜索
- 简单的查询API
- 内存存储
- 事务支持
- 模式迁移支持
- 加密支持
安装 Nitrite
要在Flutter项目中使用Nitrite,需要在pubspec.yaml
文件中添加以下依赖:
dart pub add nitrite
快速示例
初始化数据库
// 初始化Nitrite数据库,使用内存存储
var db = await Nitrite.builder()
.openOrCreate(username: 'user', password: 'pass123');
创建集合/对象仓库
// 创建一个Nitrite集合
var collection = await db.getCollection("test");
// 创建一个对象仓库
var repository = await db.getRepository<Book>();
实体类代码生成器
Nitrite生成器包可以自动从Dart类生成实体类。它使用source_gen
包来生成代码。要使用生成器,添加以下依赖:
dart pub add nitrite_generator --dev
dart pub add build_runner --dev
并在Dart类中使用以下注解:
import 'package:nitrite/nitrite.dart';
part 'book.no2.dart';
[@Convertable](/user/Convertable)(className: 'MyBookConverter')
[@Entity](/user/Entity)(name: 'books', indices: [
Index(fields: ['tags'], type: IndexType.nonUnique),
Index(fields: ['description'], type: IndexType.fullText),
Index(fields: ['price', 'publisher']),
])
class Book with _$BookEntityMixin {
// id字段
[@Id](/user/Id)(fieldName: 'book_id', embeddedFields: ['isbn', 'book_name'])
[@DocumentKey](/user/DocumentKey)(alias: 'book_id')
BookId? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
}
// 复合id类
[@Convertable](/user/Convertable)()
class BookId {
String? isbn;
// 设置文档中的不同字段名
[@DocumentKey](/user/DocumentKey)(alias: "book_name")
String? name;
// 忽略文档中的字段
[@IgnoredKey](/user/IgnoredKey)()
String? author;
}
CRUD操作
// 创建一个文档以填充数据
var doc = createDocument("firstName", "fn1")
.put("lastName", "ln1")
.put("birthDay", DateTime.parse("2012-07-01T16:02:48.440Z"))
.put("data", [1, 2, 3])
.put("list", ["one", "two", "three"])
.put("body", "a quick brown fox jump over the lazy dog")
.put("books", [
createDocument("name", "Book ABCD").put("tag", ["tag1", "tag2"]),
createDocument("name", "Book EFGH").put("tag", ["tag3", "tag1"]),
createDocument("name", "No Tag")
]);
// 插入文档
await collection.insert(doc);
// 从集合中查找文档
var cursor = collection.find(filter: and([
where('lastName').eq('ln1'),
where("firstName").notEq("fn1"),
where("list").eq("four"),
]),
);
// 更新文档
await collection.update(
where('firstName').eq('fn1'),
createDocument('firstName', 'fn1-updated'),
updateOptions(insertIfAbsent: true),
);
// 删除文档
await collection.remove(where('firstName').eq('fn1-updated'));
// 在仓库中插入对象
var bookId = BookId();
bookId.isbn = 'abc123';
bookId.author = 'xyz';
bookId.name = 'Book 1';
var book = Book();
book.bookId = bookId;
book.tags = ['tag1', 'tag2'];
book.description = 'A book about nitrite database';
book.publisher = 'rando publisher';
book.price = 150.5;
await repository.insert(book);
创建索引
// 创建文档索引
await collection.createIndex(['firstName', 'lastName']); // 唯一索引
await collection.createIndex(['firstName'], indexOptions(IndexType.nonUnique));
// 创建对象索引,也可以通过注解提供
await repository.createIndex("publisher", indexOptions(IndexType.NonUnique));
查询集合
var cursor = collection.find(
filter: and([
where('lastName').eq('ln2'),
where("firstName").notEq("fn1"),
where("list").eq("four")
]),
);
await for (var d in cursor) {
print(d);
}
// 通过Nitrite ID获取文档
var document = await collection.getById(nitriteId);
// 查询对象仓库并创建第一个结果
var cursor = repository.find(
filter: where('book_id.isbn').eq('abc123'),
);
var book = await cursor.first();
事务
Nitrite支持事务,但仅限于基于文件的存储。
var session = db.createSession();
var tx = await session.beginTransaction();
var txRepo = await tx.getRepository<Book>();
await txRepo.insert(book);
await tx.commit();
或者,另一种方式:
var session = db.createSession();
await session.executeTransaction((tx) async {
var txRepo = await tx.getRepository<Book>();
await txRepo.insertMany([book1, book2, book3]);
});
模式迁移
Nitrite支持模式迁移,可以在不丢失现有数据的情况下更改应用程序的模式。
var migration = Migration(
3,
4,
(instructionSet) {
instructionSet
.forCollection('test')
.addField('age', defaultValue: 10);
},
);
db = await Nitrite.builder()
.loadModule(storeModule)
.schemaVersion(4)
.addMigrations([migration])
.openOrCreate();
完整示例Demo
以下是一个完整的示例,展示了如何在Flutter项目中使用Nitrite进行CRUD操作、事务处理和模式迁移。
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:faker/faker.dart';
import 'package:nitrite/nitrite.dart';
part 'example.no2.dart';
void main() async {
// 创建一个Nitrite数据库
var db = await createDatabase();
// 执行集合操作
await collectionExample(db);
// 执行对象仓库操作
await objectRepositoryExample(db);
// 执行事务操作
await transactionExample(db);
// 关闭数据库
await db.close();
}
Future<Nitrite> createDatabase() async {
// 创建一个内存数据库,默认选项
var db = await Nitrite.builder()
.registerEntityConverter(MyBookConverter())
.registerEntityConverter(BookIdConverter())
.openOrCreate();
return db;
}
Future<void> collectionExample(Nitrite db) async {
// 获取一个Nitrite集合
var coll = await db.getCollection('test');
// 创建文档
var doc1 = createDocument("firstName", "fn1")
.put("lastName", "ln1")
.put("birthDay", DateTime.parse("2012-07-01T16:02:48.440Z"))
.put("data", [1, 2, 3])
.put("list", ["one", "two", "three"])
.put("body", "a quick brown fox jump over the lazy dog")
.put("books", [
createDocument("name", "Book ABCD")..put("tag", ["tag1", "tag2"]),
createDocument("name", "Book EFGH")..put("tag", ["tag3", "tag1"]),
createDocument("name", "No Tag")
]);
var doc2 = createDocument("firstName", "fn2")
.put("lastName", "ln2")
.put("birthDay", DateTime.parse("2010-06-12T16:02:48.440Z"))
.put("data", [3, 4, 3])
.put("list", ["three", "four", "five"])
.put("body", "quick hello world from nitrite")
.put("books", [
createDocument("name", "Book abcd")..put("tag", ["tag4", "tag5"]),
createDocument("name", "Book wxyz")..put("tag", ["tag3", "tag1"]),
createDocument("name", "No Tag 2")
]);
var doc3 = createDocument("firstName", "fn3")
.put("lastName", "ln2")
.put("birthDay", DateTime.parse("2014-04-17T16:02:48.440Z"))
.put("data", [9, 4, 8])
.put(
"body",
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nunc mi, '
'mattis ullamcorper dignissim vitae, condimentum non lorem.')
.put("books", [
createDocument("name", "Book Mnop")..put("tag", ["tag6", "tag2"]),
createDocument("name", "Book ghij")..put("tag", ["tag3", "tag7"]),
createDocument("name", "No Tag")
]);
// 插入文档
await coll.insert(doc1);
// 插入多个文档
await coll.insertMany([doc2, doc3]);
// 在firstName上创建索引
await coll.createIndex(['firstName']);
// 在body上创建全文索引
await coll.createIndex(['body'], indexOptions(IndexType.fullText));
// 在书籍标签上创建非唯一索引
await coll.createIndex(['books.tag'], indexOptions(IndexType.nonUnique));
// 查找所有文档
var cursor = coll.find(filter: where('firstName').eq('fn1'));
print('First document where firstName is fn1: ${await cursor.toList()}');
cursor = coll.find(filter: where('body').text('Lorem'));
print('Documents where body contains Lorem: ${await cursor.toList()}');
cursor = coll.find(filter: where('books.tag').eq('tag2'));
print('Documents where books.tag is tag2: ${await cursor.toList()}');
// 删除所有索引
await coll.dropAllIndices();
// 创建复合索引
await coll.createIndex(['list', 'lastName', 'firstName']);
cursor = coll.find(
filter: and([
where('lastName').eq('ln2'),
where("firstName").notEq("fn1"),
where("list").eq("four"),
]),
);
print(
'Documents where lastName is ln2, firstName is not fn1 and list contains'
' four: ${await cursor.toList()}');
// 更新集合
await coll.update(
where('firstName').eq('fn1'),
createDocument('firstName', 'fn1-updated'),
updateOptions(insertIfAbsent: true),
);
// 查找所有更新后的firstName
cursor = coll.find(filter: where('firstName').eq('fn1-updated'));
print('Documents where firstName is fn1-updated: ${await cursor.toList()}');
// 删除
await coll.remove(where('firstName').eq('fn1-updated'));
cursor = coll.find(filter: where('firstName').eq('fn1-updated'));
print('Documents where firstName is fn1-updated: ${await cursor.toList()}');
// 清空集合
await coll.clear();
// 删除集合
await coll.drop();
}
Future<void> objectRepositoryExample(Nitrite db) async {
// 获取仓库
var repo = await db.getRepository<Book>();
// 创建一本书
var book = randomBook();
// 插入一本书
await repo.insert(book);
// 插入多本书
await repo.insertMany([randomBook(), randomBook(), randomBook()]);
// 查找所有书
var cursor = repo.find();
print('All books: ${await cursor.toList()}');
// 按标签查找书
cursor = repo.find(filter: where('tags').eq('tag2'));
print('Books where tags is tag2: ${await cursor.toList()}');
// 按描述查找书
cursor = repo.find(filter: where('description').text('lorem'));
print('Books where description contains lorem: ${await cursor.toList()}');
// 按价格和出版社查找书
cursor = repo.find(
filter: and([
where('price').gt(100),
where('publisher').eq('publisher1'),
]),
);
print('Books where price is greater than 100 and publisher is publisher1: '
'${await cursor.toList()}');
// 按ISBN查找书
cursor = repo.find(
filter: where('book_id.isbn').eq(book.bookId!.isbn),
);
print('Books where bookId is ${book.bookId}: '
'${await cursor.toList()}');
// 更新一本书
await repo.updateDocument(
where('book_id').eq(book.bookId!),
createDocument('price', 100.0),
justOnce: false,
);
// 查找所有更新后的价格为100的书
cursor = repo.find(filter: where('price').eq(100.0));
print('Books where price is 100: ${await cursor.toList()}');
// 删除
await repo.remove(where('price').eq(100.0));
cursor = repo.find(filter: where('price').eq(100.0));
print('Books where price is 100: ${await cursor.toList()}');
// 清空仓库
await repo.clear();
// 删除仓库
await repo.drop();
}
Future<void> transactionExample(Nitrite db) async {
// 获取仓库
var repo = await db.getRepository<Book>();
// 创建一本书
var book = randomBook();
var session = db.createSession();
var tx = await session.beginTransaction();
var txRepo = await tx.getRepository<Book>();
await txRepo.insert(book);
var txCursor = txRepo.find();
print('Books inserted in transaction: ${await txCursor.toList()}');
var cursor = repo.find();
print('Books in the original repository: ${await cursor.toList()}');
await tx.commit();
// 在事务中插入多本书
await session.executeTransaction((tx) async {
var txRepo = await tx.getRepository<Book>();
await txRepo.insertMany([randomBook(), randomBook(), randomBook()]);
var cursor = repo.find();
print('Books before committing 2nd transaction: ${await cursor.toList()}');
});
// 查找所有书
cursor = repo.find();
print('All books after transaction: ${await cursor.toList()}');
// 删除仓库
await repo.drop();
}
// ==============================================================
// Entity classes
// ==============================================================
[@Convertable](/user/Convertable)(className: 'MyBookConverter')
[@Entity](/user/Entity)(name: 'books', indices: [
Index(fields: ['tags'], type: IndexType.nonUnique),
Index(fields: ['description'], type: IndexType.fullText),
Index(fields: ['price', 'publisher']),
])
class Book with _$BookEntityMixin {
// id字段
[@Id](/user/Id)(fieldName: 'book_id', embeddedFields: ['isbn', 'book_name'])
[@DocumentKey](/user/DocumentKey)(alias: 'book_id')
BookId? bookId;
String? publisher;
double? price;
List<String> tags = [];
String? description;
Book([
this.bookId,
this.publisher,
this.price,
this.tags = const [],
this.description,
]);
[@override](/user/override)
bool operator ==(Object other) =>
identical(this, other) ||
other is Book &&
runtimeType == other.runtimeType &&
bookId == other.bookId &&
publisher == other.publisher &&
price == other.price &&
ListEquality().equals(tags, other.tags) &&
description == other.description;
[@override](/user/override)
int get hashCode =>
bookId.hashCode ^
publisher.hashCode ^
price.hashCode ^
ListEquality().hash(tags) ^
description.hashCode;
[@override](/user/override)
String toString() {
return 'Book{'
'bookId: $bookId, '
'publisher: $publisher, '
'price: $price, '
'tags: $tags, '
'description: $description'
'}';
}
}
// 复合id类
[@Convertable](/user/Convertable)()
class BookId {
String? isbn;
// 设置文档中的不同字段名
[@DocumentKey](/user/DocumentKey)(alias: "book_name")
String? name;
// 忽略文档中的字段
[@IgnoredKey](/user/IgnoredKey)()
String? author;
[@override](/user/override)
bool operator ==(Object other) =>
identical(this, other) ||
other is BookId &&
runtimeType == other.runtimeType &&
isbn == other.isbn &&
name == other.name &&
author == other.author;
[@override](/user/override)
int get hashCode => isbn.hashCode ^ name.hashCode ^ author.hashCode;
[@override](/user/override)
String toString() {
return 'BookId{isbn: $isbn, name: $name, author: $author}';
}
}
// ==============================================================
// Data generator
// ==============================================================
var faker = Faker(seed: DateTime.now().millisecondsSinceEpoch);
var random = Random(DateTime.now().millisecondsSinceEpoch);
var tags = [
'tag1',
'tag2',
'tag3',
'tag4',
];
var publisher = [
'publisher1',
'publisher2',
'publisher3',
'publisher4',
];
Book randomBook() {
var book = Book();
book.bookId = randomBookId();
book.tags = (tags.toList()..shuffle(random)).take(3).toList();
book.description = faker.lorem.sentence();
book.publisher = (publisher.toList()..shuffle(random)).first;
book.price = random.nextDouble() * 1000;
return book;
}
BookId randomBookId() {
var bookId = BookId();
bookId.isbn = faker.guid.guid();
bookId.author = faker.person.name();
bookId.name = faker.conference.name();
return bookId;
}
更多关于Flutter嵌入式数据库插件nitrite的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter嵌入式数据库插件nitrite的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用Nitrite嵌入式数据库插件的示例代码。Nitrite是一个轻量级的嵌入式NoSQL数据库,专为移动和桌面应用设计。它提供了类似MongoDB的文档存储功能,非常适合Flutter应用。
首先,你需要在你的pubspec.yaml
文件中添加Nitrite的依赖:
dependencies:
flutter:
sdk: flutter
nitrite: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
接下来,我们将编写一个简单的Flutter应用来演示如何使用Nitrite数据库。
1. 初始化数据库
import 'package:flutter/material.dart';
import 'package:nitrite/nitrite.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 创建或打开名为'mydb'的数据库
final Nitrite db = await Nitrite.open('mydb');
// 创建一个集合(类似于MongoDB中的集合)
final NitriteCollection collection = db.getCollection('users');
// 示例数据
final Map<String, dynamic> user = {
'name': 'Alice',
'age': 30,
'email': 'alice@example.com',
};
// 插入数据
await collection.insert(user);
// 关闭数据库
await db.close();
runApp(MyApp());
}
2. 查询数据
在主应用逻辑中,我们可以添加查询数据的逻辑:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Nitrite Demo'),
),
body: FutureBuilder<List<Map<String, dynamic>>>(
future: _fetchUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
final List<Map<String, dynamic>> users = snapshot.data ?? [];
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final Map<String, dynamic> user = users[index];
return ListTile(
title: Text('Name: ${user['name']}'),
subtitle: Text('Email: ${user['email']}'),
);
},
);
}
},
),
),
);
}
Future<List<Map<String, dynamic>>> _fetchUsers() async {
// 创建或打开数据库
final Nitrite db = await Nitrite.open('mydb');
final NitriteCollection collection = db.getCollection('users');
// 查询所有数据
final List<Map<String, dynamic>> users = await collection.find().toList();
// 关闭数据库
await db.close();
return users;
}
}
3. 更新数据
你可以在需要的地方添加更新数据的逻辑,例如通过按钮点击事件:
FloatingActionButton(
onPressed: () async {
final Nitrite db = await Nitrite.open('mydb');
final NitriteCollection collection = db.getCollection('users');
// 更新数据(假设我们要更新第一个用户的年龄)
final Document userDoc = await collection.findOne({'name': 'Alice'});
if (userDoc != null) {
await collection.update(userDoc.id, {'age': 31});
}
await db.close();
// 重新获取数据
setState(() {}); // 如果你在StatefulWidget中使用这个逻辑,确保调用setState来刷新UI
},
tooltip: 'Update Data',
child: Icon(Icons.edit),
)
4. 删除数据
同样地,你可以添加删除数据的逻辑:
FloatingActionButton(
onPressed: () async {
final Nitrite db = await Nitrite.open('mydb');
final NitriteCollection collection = db.getCollection('users');
// 删除数据(假设我们要删除名为Alice的用户)
await collection.delete({'name': 'Alice'});
await db.close();
// 重新获取数据
setState(() {}); // 如果你在StatefulWidget中使用这个逻辑,确保调用setState来刷新UI
},
tooltip: 'Delete Data',
child: Icon(Icons.delete),
)
请注意,为了简化示例,上述代码在每次操作后都关闭了数据库连接。在实际应用中,你可能希望管理数据库连接的生命周期,以避免频繁打开和关闭连接带来的性能开销。你可以考虑使用单例模式或依赖注入来管理数据库实例。
此外,确保在实际项目中处理异常和错误情况,以提供更好的用户体验。