Hive 在我个人项目的使用上出现了问题,目前遇到两种错误:
- 多次 open 同一
Collection
下的 Box,除第一个 Box 能正常使用外,其他的 Box 都不可用。
- 使用
BoxCollection
来创建多个 Box,当使用Box
put 数据时会报数据类型错误,即使我们提供了正确的TypeAdapter
。
经过一段时间的摸索后终于找到一条能正常使用的方法。
前言
为什么我们需要用 Hive 创建多个Box
呢,因为业务上的需求我们要在 Web 端实现在关系型数据库上实现的效果,大致思路可以概括为:
- 创建一个共享的 Collections 里面包含用户信息的 Box 和其他通用的 Box
- 每当用户登录就用用户的 id 命名创建一个 Collections,它里面的多个 Box 是这个用户单独拥有的数据。
这样做的好处就是每个用户的数据是隔离的,不会都放在一个 Box 里造成数据量太多影响性能。
Hive 还是文件型数据存储方案,内存压力和 CPU 性能是绕不开的话题。所以,Hive 不适合存储过多的数据,Hive 的作者在issue中建议 1000 ~ 5000;超过这个值性能会逐渐降低。
错误的使用方式 1
使用 BoxCollection 的方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
import 'package:hive/hive.dart';
abstract class UserBox { static String boxName = 'users'; Future<void> saveUser(User user); Future<User?> findUserById(String userId); void closeBox(); }
@Injectable(as: UserBox) class UserBoxImpl extends UserBox { CollectionBox<User>? _cachedBox; final BoxCollection _collections; UserBoxImpl(this._collections);
@override Future<void> saveUser(User user) async { CollectionBox<User> innerBox = await _getCachedBox(); await innerBox.put(user.id, user); }
@override Future<User?> findUserById(String userId) async { CollectionBox<User> innerBox = await _getCachedBox(); return Future.value(innerBox.get(userId)); }
Future<CollectionBox<User>> _getCachedBox() async { return _cachedBox ??= await _collections.openBox<User>(UserBox.boxName, collection: HiveManager.hiveBoxName); } }
import 'package:hive/hive.dart';
abstract class PersonBox { static String boxName = 'persons'; Future<void> savePerson(Person person); Future<Person?> findPersonById(String personId); void closeBox(); }
class PersonBoxImpl extends PersonBox { LazyBox<Person>? _cachedBox; final String userId; final BoxCollection? _collections; PersonBoxImpl(this.userId, this._collections);
@override Future<void> savePerson(Person person) async { LazyBox<Person>? innerBox = await _getCachedBox(); await innerBox?.put(person.id, person); }
@override Future<Person?> findPersonById(String personId) async { LazyBox<Person>? innerBox = await _getCachedBox(); return Future.value(innerBox?.get(personId)); }
@override void closeBox() { _cachedBox?.close(); _cachedBox = null; }
Future<Box<Person>?> _getCachedBox() async { return _cachedBox ??= await _collections?.openBox<Person>(PersonBox.boxName, collection: userId); } }
import 'package:hive/hive.dart';
class HiveManager { static String hiveBoxName = 'hive-database'; static Future<HiveManager> initInstance() async { _registerClassAdapter(); await Hive.initFlutter(); BoxCollection collection = await BoxCollection.open( hiveBoxName, { UserBox.boxName, ... }, path: './', ); UserBox userBox = UserBoxImpl(collection); HiveManager hiveManager = HiveManager(userBox); return Future.value(hiveManager); }
static void _registerClassAdapter() { Hive.registerAdapter<User?>(NullableUserAdapter()); Hive.registerAdapter<Person?>(NullablePersonAdapter()); }
final UserBox _userBox; final PersonBox? _personBox; const HiveManager(this._userBox,[this.personBox = null]);
UserBox get userBox => _userBox; PersonBox? get personBox => _personBox; Future<void> initBox(String userId) async { BoxCollection collection = await BoxCollection.open( userId, { PersonBox.boxName, ... }, path: './', ); personBox = PersonBoxImpl(userId, collection); } void closeBox() { personBox?.closeBox(); } }
|
这里的 CollectionBox,它内部持有一个 Box,从而可以对数据库进行数据处理。
错误的使用方式 2
在同一Collection
下打开不同 Box 的方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
|
import 'package:hive/hive.dart';
abstract class UserBox { static String boxName = 'users'; Future<void> saveUser(User user); Future<User?> findUserById(String userId); void closeBox(); }
@Injectable(as: UserBox) class UserBoxImpl extends UserBox { Box<User>? _cachedBox;
@override Future<void> saveUser(User user) async { Box<User> innerBox = await _getCachedBox(); await innerBox.put(user.id, user); }
@override Future<User?> findUserById(String userId) async { Box<User> innerBox = await _getCachedBox(); return Future.value(innerBox.get(userId)); }
Future<Box<User>> _getCachedBox() async { return _cachedBox ??= await Hive.openBox<User>(UserBox.boxName, collection: HiveManager.hiveBoxName); } }
import 'package:hive/hive.dart';
abstract class ConfigBox { static String boxName = 'configs'; Future<void> saveConfig(Config config); Future<Config?> findConfigByKey(String configId); void closeBox(); }
@Injectable(as: ConfigBox) class ConfigBoxImpl extends ConfigBox { Box<Config>? _cachedBox; ConfigBoxImpl(this._localStorage);
@override Future<void> saveConfig(Config config) async { Box<Config> innerBox = await _getCachedBox(); await innerBox.put(config.id, config); }
@override Future<Config?> findConfigByKey(String configId) async { Box<Config> innerBox = await _getCachedBox(); return Future.value(innerBox.get(configId)); }
Future<Box<Config>> _getCachedBox() async { return _cachedBox ??= await Hive.openBox<Config>(ConfigBox.boxName, collection: HiveManager.hiveBoxName); } }
import 'package:hive/hive.dart';
class HiveManager { static String hiveBoxName = 'hive-database'; static Future<HiveManager> initInstance(UserBox userBox, ConfigBox configBox) async { _registerClassAdapter(); await Hive.initFlutter(); HiveManager hiveManager = HiveManager(userBox, configBox); return Future.value(hiveManager); }
static void _registerClassAdapter() { Hive.registerAdapter<User?>(NullableUserAdapter()); Hive.registerAdapter<Config?>(NullableConfigAdapter()); }
final UserBox _userBox; final ConfigBox _configBox; const HiveManager(this.userBox,this._configBox);
UserBox get userBox => _userBox; ConfigBox get configBox => _configBox;
void closeBox() { } }
|
同一个Collection
下的第二个第一次 Box 打开后并不会创建 database,导致后续数据库操作的方法无限等待。
正确的使用方式
其实能正常工作的方式也是上面两种方式的组合。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
|
import 'package:hive/hive.dart';
abstract class UserBox { static String boxName = 'users'; Future<void> saveUser(User user); Future<User?> findUserById(String userId); void closeBox(); }
@Injectable(as: UserBox) class UserBoxImpl extends UserBox { Box<User>? _cachedBox;
@override Future<void> saveUser(User user) async { Box<User> innerBox = await _getCachedBox(); await innerBox.put(user.id, user); }
@override Future<User?> findUserById(String userId) async { Box<User> innerBox = await _getCachedBox(); return Future.value(innerBox.get(userId)); }
Future<Box<User>> _getCachedBox() async { return _cachedBox ??= await Hive.openBox<User>(UserBox.boxName, collection: HiveManager.hiveBoxName); } }
import 'package:hive/hive.dart';
abstract class ConfigBox { static String boxName = 'configs'; Future<void> saveConfig(Config config); Future<Config?> findConfigByKey(String configId); void closeBox(); }
@Injectable(as: ConfigBox) class ConfigBoxImpl extends ConfigBox { Box<Config>? _cachedBox; ConfigBoxImpl(this._localStorage);
@override Future<void> saveConfig(Config config) async { Box<Config> innerBox = await _getCachedBox(); await innerBox.put(config.id, config); }
@override Future<Config?> findConfigByKey(String configId) async { Box<Config> innerBox = await _getCachedBox(); return Future.value(innerBox.get(configId)); }
Future<Box<Config>> _getCachedBox() async { return _cachedBox ??= await Hive.openBox<Config>(ConfigBox.boxName, collection: HiveManager.hiveBoxName); } }
import 'package:hive/hive.dart';
class HiveManager { static String hiveBoxName = 'hive-database'; static Future<HiveManager> initInstance(UserBox userBox, ConfigBox configBox) async { _registerClassAdapter(); await Hive.initFlutter(); await BoxCollection.open( hiveBoxName, { UserBox.boxName, ConfigBox.boxName, ... }, path: './', ); HiveManager hiveManager = HiveManager(userBox, configBox); return Future.value(hiveManager); }
static void _registerClassAdapter() { Hive.registerAdapter<User?>(NullableUserAdapter()); Hive.registerAdapter<Config?>(NullableConfigAdapter()); }
final UserBox _userBox; final ConfigBox _configBox; const HiveManager(this.userBox,this._configBox);
UserBox get userBox => _userBox; ConfigBox get configBox => _configBox;
Future<void> initBox(String userId) async { ... }
void closeBox() { }
|
思路就是先用BoxCollection
创建所有需要的 Box,然后以正常的方式打开对应的 Box,放弃官方提供的CollectionBox
的方式。
总结
Hive 官方给的文档还是很好的,可能没想到有人会以这种方式来用吧。如果上面的方案不奏效我还会尝试其他方案。
其他的解决思路:
- 使用 BoxCollection 时,是不是我们
TypeAdapter
里类型转换的方式不正确,比如尝试在自定义对象里用@HiveField
注解,然后在write()
和read()
方法对每个属性分别处理。
- 放弃在同一个
Collections
里创建多个 Box 的方案,每个用户的数据都单独对应一个Collections
,多个用户的数据存放到同一个 Box 中。