最近我一直在讨论Android Architecture Components(更具体地说是Room),但我遇到了一些障碍。
我已经成功建立了一个房间数据库,用于存储部门及其人员的列表。以前,此数据是从服务器中提取的,但未在本地存储。搜索功能也是远程处理的,所以现在我也希望在本地处理搜索功能,但我对SQL的了解有点缺乏。
查看服务器上的SQL代码,搜索语句使用一堆REGEXP
函数根据提供的查询搜索两个数据库。这似乎不是处理搜索的最佳方式,但它运作得相当好,并给出了快速响应。所以我尝试在本地模仿这个,但很快就发现Android上不支持REGEXP
(不使用NDK)。
至于LIKE
和GLOB
运营商,他们似乎非常有限。例如,我没有看到一种方法可以同时匹配多个关键字;而使用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
以在必要时注入虚拟表代码。
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是否是正确的方法(看起来有点矫枉过正),还是有其他方法更适合我的用例?
答案 0 :(得分:4)
我可以确认这是有效的。这是加重的,但它确实有效。
首先,您需要创建表格。对于初始数据库创建,您可以使用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
,并使用前导_
来强调&#34;这些方法为private
,但您不能private
{1}} abstract
方法,所以请不要使用它们,m&#39; kay?&#34;。
请注意,您的FTS搜索表达式需要遵循the SQLite FTS documentation。
答案 1 :(得分:2)
我们终于得到了它,并且从版本2.1.0-alpha01开始,Room支持具有映射FTS3或FTS4表的实体。有关更多信息和示例用法,您可以转到其文档:@Fts3和@Fts4