【问题标题】:SQLite - Deleting a row that is referenced as a foreign keySQLite - 删除作为外键引用的行
【发布时间】:2019-08-27 12:47:56
【问题描述】:

在尝试删除作为外键引用的行时(出于实验目的),我遇到了运行时异常。我知道我试图违反外键约束,我只是对 logcat 消息感到困惑,因为它指的是我代码的完全不同的部分。

这是我的SQLiteOpenHelperonCreate 方法:

@Override
public void onCreate(SQLiteDatabase db) {
    this.db = db;

    final String SQL_CREATE_CATEGORIES_TABLE = "CREATE TABLE " +
            CategoriesTable.TABLE_NAME + "( " +
            CategoriesTable._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            CategoriesTable.COLUMN_NAME + " TEXT " +
            ")";

    final String SQL_CREATE_QUESTIONS_TABLE = "CREATE TABLE " +
            QuestionsTable.TABLE_NAME + " ( " +
            QuestionsTable._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            QuestionsTable.COLUMN_QUESTION + " TEXT, " +
            QuestionsTable.COLUMN_OPTION1 + " TEXT, " +
            QuestionsTable.COLUMN_OPTION2 + " TEXT, " +
            QuestionsTable.COLUMN_OPTION3 + " TEXT, " +
            QuestionsTable.COLUMN_ANSWER_NR + " INTEGER, " +
            QuestionsTable.COLUMN_DIFFICULTY + " TEXT, " +
            QuestionsTable.COLUMN_CATEGORY + " INTEGER, " +
            "FOREIGN KEY(" + QuestionsTable.COLUMN_CATEGORY + ") REFERENCES " +
            CategoriesTable.TABLE_NAME + "(" + QuestionsTable._ID + ")" + 
            ")";

    db.execSQL(SQL_CREATE_CATEGORIES_TABLE);
    db.execSQL(SQL_CREATE_QUESTIONS_TABLE);
    fillCategoriesTable();
    fillQuestionsTable();
}

这里我用最初的问题填充表格,然后我尝试从类别表中删除一行,故意违反外键约束(测试它):

private void fillQuestionsTable() {
    Question q1 = new Question("Easy: A is correct",
            "A", "B", "C", 1, Question.DIFFICULTY_EASY, 1);
    addQuestion(q1);
    Question q2 = new Question("Medium: B is correct",
            "A", "B", "C", 2, Question.DIFFICULTY_MEDIUM, 2);
    addQuestion(q2);
    Question q3 = new Question("Medium: C is correct",
            "A", "B", "C", 3, Question.DIFFICULTY_MEDIUM, 3);
    addQuestion(q3);
    Question q4 = new Question("Hard: A is correct",
            "A", "B", "C", 1, Question.DIFFICULTY_HARD, 4);
    addQuestion(q4);
    Question q5 = new Question("Hard: B is correct",
            "A", "B", "C", 2, Question.DIFFICULTY_HARD, 5);
    addQuestion(q5);
    Question q6 = new Question("Hard: C is correct",
            "A", "B", "C", 3, Question.DIFFICULTY_HARD, 6);
    addQuestion(q6);

    db.execSQL("DELETE FROM " + CategoriesTable.TABLE_NAME + " WHERE ID = 1");
}

我得到一个运行时异常。 请注意,我故意尝试违反外键约束,只是想了解异常。

我的第一个问题是:

1)crash是因为删了,但是为什么logcat只说加4,5和6类的题(我的categories表里本来就没有)?

插入错误难度=Hard option1=A 问题=Hard: C 正确 category_id=6 answer_nr=3 option3=C option2=B android.database.sqlite.SQLiteConstraintException: FOREIGN KEY 约束失败(代码 787)

为什么它没有像我期望的那样谈论删除引用的列?

2) 为什么它会抛出运行时异常,而尝试添加一个类别表中不存在的类别的问题,只是忽略 SQLite 命令,但不会使应用程序崩溃? 通过尝试删除引用的列而违反外键约束时,运行时异常是否是预期行为?

【问题讨论】:

    标签: android sqlite android-sqlite sqliteopenhelper


    【解决方案1】:

    1) 崩溃是因为删除,但是为什么logcat只说话 关于将问题添加到类别 4,5 和 6(不在我的 首先是类别表)?

    没有。崩溃,或者至少显示的消息,是因为它所说的:违反 FOREIGN KEY 约束,发生在删除之前(在addQuestion(q6);),因此删除永远不会发生。

    解释 FOREIGN KEY 约束及其失败的原因有点困难,因为您似乎混淆了表列。但是,我的猜测是这本身不是问题。

    更具体地说,您使用定义 FOREIGN KEY 约束

    FOREIGN KEY(column_in_this_table_which_you_have_right)
        REFERENCES the_other_table_which_you_have_right
            (column_in_the_other_table_????)
    

    对于另一个表中的列,您有QuestionsTable._ID,它应该是CategoriesTable._ID。但是,它们可能被称为相同。所以这不会引起问题。

    错误信息包括category_id=6

    假设它们都解析为 ID,那么您实际上是在说

    仅当 **questions 表的 category_id* 列与 questions 表中的一行匹配(可以引用)时,才允许在 questions 表中插入一行>categories 表,其中 ID 列与 category_id 相同(即 6)。

    简而言之,显示的消息说没有 id 为 6 的类别。


    为什么它不像我那样谈论删除引用的列 期待?

    因为它在到达db.execSQL("DELETE FROM " + CategoriesTable.TABLE_NAME + " WHERE ID = 1");之前就崩溃了


    2) 为什么它会抛出运行时异常,而试图添加一个 具有类别中不存在的类别的问题 表,只是忽略 SQLite 命令,但不会导致应用崩溃?

    因为你的应用很可能会在很多地方崩溃。你是说这对设计来说非常重要。我毫不怀疑,如果忽略选项是这种情况,那么您将与此相反(即可用的最佳选项没有人强迫您定义约束)。虽然如果这就是你想要的,你可能希望查看4.2 Deferred Foreign Key Constraints


    运行时异常是违反外部规则时的预期行为吗 通过尝试删除引用的列来进行键约束?

    重申一下,您并没有走到那一步。

    不过,您不妨查看4.3 ON DELETE and ON UPDATE Actions

    编辑 - 混淆解释。

    简而言之,崩溃实际上是由于 DELETE 约束。 INSERT 约束被捕获,但显示为正在使用标准insert 方法。日志,如果您仔细查看,将显示这一点,如下所述。

    这是一个基于我整理的代码的示例:-

    04-11 21:47:10.241 1927-1927/? E/SQLiteDatabase: Error inserting option1=A category_id=6 option2=B option3=C difficulty=Hard answer_nr=3 question=Hard: C is correct
        android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
            at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
            at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:775)
            at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
            at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
            at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1469)
            at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1339)
            at askit.questionaire.DatabaseHelper.addQuestion(DatabaseHelper.java:70)
            at askit.questionaire.DatabaseHelper.fillQuestionsTable(DatabaseHelper.java:97)
            at askit.questionaire.DatabaseHelper.onCreate(DatabaseHelper.java:52)
            at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:252)
            at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
            at askit.questionaire.DatabaseHelper.<init>(DatabaseHelper.java:17)
            at askit.questionaire.MainActivity.onCreate(MainActivity.java:14)
            at android.app.Activity.performCreate(Activity.java:5008)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
            at android.app.ActivityThread.access$600(ActivityThread.java:130)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4745)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
            at dalvik.system.NativeStart.main(Native Method)
    04-11 21:47:10.245 1927-1927/? D/AndroidRuntime: Shutting down VM
    04-11 21:47:10.245 1927-1927/? W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0xa6294288)
    04-11 21:47:10.245 1927-1927/? E/AndroidRuntime: FATAL EXCEPTION: main
        java.lang.RuntimeException: Unable to start activity ComponentInfo{askit.questionaire/askit.questionaire.MainActivity}: android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2059)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
            at android.app.ActivityThread.access$600(ActivityThread.java:130)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4745)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
            at dalvik.system.NativeStart.main(Native Method)
         Caused by: android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
            at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
            at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:727)
            at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
            at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
            at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1665)
            at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1594)
            at askit.questionaire.DatabaseHelper.fillQuestionsTable(DatabaseHelper.java:99)
            at askit.questionaire.DatabaseHelper.onCreate(DatabaseHelper.java:52)
            at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:252)
            at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
            at askit.questionaire.DatabaseHelper.<init>(DatabaseHelper.java:17)
            at askit.questionaire.MainActivity.onCreate(MainActivity.java:14)
            at android.app.Activity.performCreate(Activity.java:5008)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084) 
            at android.app.ActivityThread.access$600(ActivityThread.java:130) 
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195) 
            at android.os.Handler.dispatchMessage(Handler.java:99) 
            at android.os.Looper.loop(Looper.java:137) 
            at android.app.ActivityThread.main(ActivityThread.java:4745) 
            at java.lang.reflect.Method.invokeNative(Native Method) 
            at java.lang.reflect.Method.invoke(Method.java:511) 
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553) 
            at dalvik.system.NativeStart.main(Native Method) 
    

    您看到的是:-

    04-11 21:47:10.241 1927-1927/? E/SQLiteDatabase: Error inserting option1=A category_id=6 option2=B option3=C difficulty=Hard answer_nr=3 question=Hard: C is correct
        android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
    

    但是,这不会导致应用程序崩溃。

    • 如果您在删除数据库后运行应用程序,将 DELETE 注释掉并查看日志,您将在日志中看到相同的内容,即使应用程序没有崩溃。

    你需要做的是继续浏览日志,你会发现相当于:-

    04-11 21:47:10.245 1927-1927/? D/AndroidRuntime: Shutting down VM
    04-11 21:47:10.245 1927-1927/? W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0xa6294288)
    04-11 21:47:10.245 1927-1927/? E/AndroidRuntime: FATAL EXCEPTION: main
        java.lang.RuntimeException: Unable to start activity ComponentInfo{askit.questionaire/askit.questionaire.MainActivity}: android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
    

    这是崩溃,即 FATAL EXCEPTION:

    如果你继续,你会看到caused by,这就是你看到的地方:-

     Caused by: android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
        at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
        at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:727)
        at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
        at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
        at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1665)
        at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1594)
        at askit.questionaire.DatabaseHelper.fillQuestionsTable(DatabaseHelper.java:99)
        at askit.questionaire.DatabaseHelper.onCreate(DatabaseHelper.java:52)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:252)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
        at askit.questionaire.DatabaseHelper.<init>(DatabaseHelper.java:17)
        at askit.questionaire.MainActivity.onCreate(MainActivity.java:14)
        at android.app.Activity.performCreate(Activity.java:5008) 
    

    如果您查看日志caused by,您会看到提到您的包的第一行是:-

    at askit.questionaire.DatabaseHelper.fillQuestionsTable(DatabaseHelper.java:99)
    

    第 99 行(您的行号与包裹不同)指向

    • 请注意,我将行更改为使用 CONSTANT (CategoriesTable._ID) 作为类别 ID 列。

    【讨论】:

    • 但问题是,如果我跳过删除部分,我的应用程序在尝试添加不存在类别的问题时不会崩溃。它只是不添加问题(并保持应用程序运行)。这让我感到困惑。如果我不尝试删除该类别,则不会出现此运行时异常。
    • 那么您可能正在做的不是查看整个日志/正确的堆栈跟踪。我的猜测是您将插入包装在 try/catch 中,因此您将它们捕获,以便继续处理并进一步向下获得实际的崩溃堆栈跟踪。
    • 这可以解释为什么如果你删除删除它一切似乎都很好。
    • 有道理,但我没有任何 try/catch 块
    • @FlorianWalther 解决了,至少是原因。答案已被编辑。
    猜你喜欢
    • 1970-01-01
    • 2010-11-20
    • 2021-06-14
    • 2019-07-11
    • 1970-01-01
    • 1970-01-01
    • 2020-10-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多