Flutter嵌入式数据库插件nitrite的使用

发布于 1周前 作者 yuanlaile 来自 Flutter

Flutter嵌入式数据库插件Nitrite的使用

Nitrite Database

Logo

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

1 回复

更多关于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),
)

请注意,为了简化示例,上述代码在每次操作后都关闭了数据库连接。在实际应用中,你可能希望管理数据库连接的生命周期,以避免频繁打开和关闭连接带来的性能开销。你可以考虑使用单例模式或依赖注入来管理数据库实例。

此外,确保在实际项目中处理异常和错误情况,以提供更好的用户体验。

回到顶部