Flutter开发进阶之并发操作数据库
尽管 Flutter 本身不包含任何数据库功能,但可以使用各种第三方库和插件来在 Flutter 应用程序中实现数据库功能;
以下将使用sqflite作为例子,sqflite允许在 Flutter 应用程序中执行 SQL 查询,创建和管理数据库表,以及执行其他常见的数据库操作。
在将sqflite添加到Flutter项目的依赖中后,就可以使用代码创建数据库和表了。
import 'package:sqflite/sqflite.dart';
Future<Database> getDatabase() async {
final dir = (await getDatabasesPath()).resolve('my_database.db');
return await openDatabase(dir.path, version: 1, onCreate: _onCreate);
}
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER
)
''');
}
然后我们可以做一些插入、查询、更新和删除数据库中数据的操作。
import 'package:sqflite/sqflite.dart';
Future<void> insertData() async {
final db = await getDatabase();
await db.execute(
'INSERT INTO user (name, age) VALUES (?, ?)',
['John', 25],
);
}
Future<List<Map<String, dynamic>>> queryData() async {
final db = await getDatabase();
return await db.query('user');
}
Future<int> updateData() async {
final db = await getDatabase();
return await db.update(
'user',
{'age': 26},
where: 'name = ?',
whereArgs: ['John'],
);
}
Future<int> deleteData() async {
final db = await getDatabase();
return await db.delete('user', where: 'name = ?', whereArgs: ['John']);
}
在实际应用中并不会这么简单,特别是当我们有需求在多个位置去操作数据库的时候,这时候可能会有线程安全的问题;
比如说在一个作用域内,有多台设备需要在我所在的这台设备操作数据库;
首先我所在的设备作为主机要监听子机的UDP广播,然后将通过我验证的子机向其发送我开放的TCP的地址和端口。
const String multicastGroup = '224.0.0.1'; // 定义多播组地址
const int port = 5000; // 定义端口号
const int bufferSize = 1024; // 定义缓冲区大小
final ByteData buffer = ByteData(bufferSize);
DatagramSocket socket = DatagramSocket();
socket.bind(port);
socket.joinMulticastGroup(multicastGroupIP);
socket.listen(buffer.length);
socket.onDatagramReceived = (Datagram datagram) async {
final String receivedData = datagram.data.toString();
// 处理接收到的数据...
};
然后合法的子机会收到我的信息,就可以通过TCP向主机发送命令;
这时就需要主机时刻监听TCP并对其响应。
const int port = 5000; // 定义端口号
final ServerSocket serverSocket = ServerSocket(port);
serverSocket.onAccept = (ServerSocket socket) async {
final Stream stream = socket.accept();
stream.transform(utf8.decoder).listen((String data) {
// 处理接收到的数据...
});
};
假设对其响应的本身是对数据库进行操作,而主机内部也同时对数据库有了操作,这时候就要注意数据库的线程安全了;
首先可以通过对数据库的操作加锁来保证,比如sqflite提供了事务(Transaction),在事务中执行数据库操作可以确保操作的原子性,即要么全部成功,要么全部失败;
通过使用事务,我们可以实现对数据库操作的加锁,确保同一时间只有一个线程可以访问数据库中的特定资源。
Future<void> insertData() async {
final db = await getDatabase();
await db.transaction((txn) async {
// 在事务中执行数据库操作
await txn.execute('INSERT INTO user (name, age) VALUES (?, ?)', ('John', 25));
// 提交事务
await txn.commit();
});
}
或者直接使用synchronized创建锁。
import 'package:synchronized/synchronized.dart';
还可以通过数据库连接池来限制最大连接数量。
import 'package:sqflite/sqflite.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
static Database? _db;
factory DatabaseHelper() {
return _instance;
}
Future<Database> get db async {
if (_db != null) return _db;
_db = await _openDatabase();
return _db;
}
Future<void> close() async {
if (_db != null) {
await _db!.close();
_db = null;
}
}
Future<Database> _openDatabase() async {
final pool = await SqliteConnectionPool.forDatabase('path/to/database.db');
pool.maxSize = 10; // 设置最大连接数为10
return pool.openDatabase();
}
}
这样同时还避免了直接使用Database实例。