gpt4 book ai didi

android - 用 Room 实现搜索

转载 作者:塔克拉玛干 更新时间:2023-11-02 19:56:20 25 4
gpt4 key购买 nike

最近一直在胡思乱想Android Architecture Components (更具体地说 Room )但我遇到了一些障碍。

我已经成功地构建了一个 Room 数据库,用于存储部门及其人员的列表。以前,此数据是从服务器中提取的,但未存储在本地。搜索功能也是远程处理的,所以现在我也希望在本地处理搜索功能,但我对 SQL 的了解有点欠缺。

查看服务器上的 SQL 代码,搜索语句使用一堆 REGEXP 函数根据提供的查询搜索两个数据库。这似乎不是处理搜索的最佳方式,但它运行良好并且响应迅速。所以我尝试在本地模仿它,但很快发现 Android 不支持 REGEXP(不使用 NDK)。

至于LIKEGLOB 运算符,它们的作用似乎非常有限。例如,我没有看到可以同时匹配多个关键字的方法;而对于 REGEXP,我只需将空格替换为 or (|) 运算符即可实现此功能。

因此,在寻找替代方案时我遇到了 full-text search (FTS);这是 Android documentation on implementing search 中演示的方法.虽然看起来 FTS 是为了搜索完整文档,而不是像我的用例那样的简单数据。

无论如何,FTS isn't supported by Room .

因此,很自然地,我试图通过创建 SupportSQLiteOpenHelper.Factory 的实现来强制 Room 创建 FTS 虚拟表而不是标准表。那就是那样。此实现几乎是默认 FrameworkSQLiteOpenHelperFactory 和相关框架类的直接副本。必要的代码位在 SupportSQLiteDatabase 中,我在其中覆盖 execSQL 以在必要时注入(inject)虚拟表代码。

class FTSSQLiteDatabase(
private val delegate: SQLiteDatabase,
private val ftsOverrides: Array<out String>
) : SupportSQLiteDatabase {

// Omitted code...

override fun execSQL(sql: String) {
delegate.execSQL(injectVirtualTable(sql))
}

override fun execSQL(sql: String, bindArgs: Array<out Any>) {
delegate.execSQL(injectVirtualTable(sql), bindArgs)
}

private fun injectVirtualTable(sql: String): String {
if (!shouldOverride(sql)) return sql

var newSql = sql

val tableIndex = sql.indexOf("TABLE")
if (tableIndex != -1) {
sql = sql.substring(0..(tableIndex - 1)) + "VIRTUAL " + sql.substring(tableIndex)

val argumentIndex = sql.indexOf('(')
if (argumentIndex != -1) {
sql = sql.substring(0..(argumentIndex - 1) + "USING fts4" + sql.substring(argumentIndex)
}
}

return newSql
}

private fun shouldOverride(sql: String): Boolean {
if (!sql.startsWith("CREATE TABLE")) return false

val split = sql.split('`')
if (split.size >= 2) {
val tableName = split[1]
return ftsOverrides.contains(tableName)
} else {
return false
}
}

}

虽然有点乱,但是很管用!好吧,它创建了虚拟表......

但随后我得到以下 SQLiteException:

04-04 10:54:12.146 20289-20386/com.example.app E/SQLiteLog: (1) cannot create triggers on virtual tables
04-04 10:54:12.148 20289-20386/com.example.app E/ROOM: Cannot run invalidation tracker. Is the db closed?
android.database.sqlite.SQLiteException: cannot create triggers on virtual tables (code 1): , while compiling: CREATE TEMP TRIGGER IF NOT EXISTS `room_table_modification_trigger_departments_UPDATE` AFTER UPDATE ON `departments` BEGIN INSERT OR REPLACE INTO room_table_modification_log VALUES(null, 0); END
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:890)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:501)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:31)
at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1752)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1682)
at com.example.app.data.FTSSQLiteDatabase.execSQL(FTSSQLiteDatabase.kt:164)
at android.arch.persistence.room.InvalidationTracker.startTrackingTable(InvalidationTracker.java:204)
at android.arch.persistence.room.InvalidationTracker.access$300(InvalidationTracker.java:62)
at android.arch.persistence.room.InvalidationTracker$1.run(InvalidationTracker.java:306)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)

Room 创建表,然后尝试在虚拟表上创建触发器,which is apparently not allowed .如果我尝试覆盖触发器(即只是阻止它们执行),我猜这会破坏 Room 的很多功能。我假设,这就是 Room 一开始不支持 FTS 的原因。

长见识

因此,如果 Room 不支持 FTS(我不能强制它支持),并且不支持 REGEXP(除非我使用 NDK);在使用 Room 时,还有其他方法可以实现搜索吗? FTS 是否是正确的方法(看起来有点矫枉过正),或者是否有其他更适合我的用例的方法?

最佳答案

我可以确认这有效。这会加重病情,但它有效。

首先,您需要创建表格。对于初始数据库创建,您可以为此使用 RoomDatabase.Callback:

RoomDatabase.Builder<BookDatabase> b=
Room.databaseBuilder(ctxt.getApplicationContext(), BookDatabase.class,
DB_NAME);

b.addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);

db.execSQL("CREATE VIRTUAL TABLE booksearch USING fts4(sequence, prose)");
}
});

BookDatabase books=b.build();

(另外:如果您需要在迁移中对其进行更改,请记住此表!)

然后您可以为此设置一个@Dao。您所有实际的数据库操作 DAO 方法都需要使用 @RawQuery 进行注释,因为其他所有方法都希望与实体一起使用。而且,由于 @RawQuery 方法只接受一个 SupportSQLiteQuery 参数,您可能希望将这些参数包装在创建 SupportSQLiteQuery 对象的其他方法中。

因此,例如,要将数据插入虚拟表,您可以:

  @RawQuery
protected abstract long insert(SupportSQLiteQuery queryish);

void insert(ParagraphEntity entity) {
insert(new SimpleSQLiteQuery("INSERT INTO booksearch (sequence, prose) VALUES (?, ?)",
new Object[] {entity.sequence, entity.prose}));
}

要进行搜索,您可以:

  @RawQuery
protected abstract List<BookSearchResult> _search(SupportSQLiteQuery query);

List<BookSearchResult> search(String expr) {
return _search(query(expr));
}

private SimpleSQLiteQuery query(String expr) {
return new SimpleSQLiteQuery("SELECT sequence, snippet(booksearch) AS snippet FROM booksearch WHERE prose MATCH ? ORDER BY sequence ASC",
new Object[] {expr});
}

在这两种情况下,我的 @RawQuery 方法都是 protected 并使用前导 _ 来强调“这些将是 private ,但是你不能有 private abstract 方法,所以请不要使用它们,好吗?

请注意,您的 FTS 搜索表达式需要跟在 the SQLite FTS documentation 之后.

关于android - 用 Room 实现搜索,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49656009/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com