如何在 Join table 接受 "zero" 值中设置 Android 房间外键?
How do I setup Android Room Foreign Keys in a Join table accept "zero" values?
问题: 对于 Android Room,当输入零 (0) 时,我在连接 table 上收到外键约束错误。我仍在努力学习 Android Room,所以我确定我忽略了文档和教程中的某些内容。
我尝试过的方法: 我确实在使用 SQLite 的桌面版本中安装了此应用程序,我没有遇到任何问题,并已尝试对其进行相同的设置。我在这里和其他地方阅读了一些 posts 的例子,甚至删除了 return 零的外键声明,但后来我很难开始在不同的 [=37] 上触发模式=] 完全。我尝试了默认值,但设置为 null 但似乎无法正常工作。在写这个 post 时,我仍然会重新阅读一些内容,但我确定我只是忽略了一些东西 - 旧的 80/20 规则。
我正在尝试做的事情: 此联接 table 从 6 table 中捕获 ID。但是,根据用户条目,有 3 个外键,用户可能没有添加任何要引用的内容。由于 Android Room 本质上是包装 SQLite 以便于使用,到目前为止,我还无法确定如何让它接受外键上的零值。我想尝试使用 OnConflict
注释,但想寻求一些想法和建议。
实体
@Entity(tableName = "Notes", foreignKeys = {
@ForeignKey(entity = Sources.class, parentColumns = "SourceID", childColumns = "SourceID"),
@ForeignKey(entity = Comments.class, parentColumns = "CommentID", childColumns = "CommentID"),
@ForeignKey(entity = Questions.class, parentColumns = "QuestionID", childColumns = "QuestionID"),
@ForeignKey(entity = Quotes.class, parentColumns = "QuoteID", childColumns = "QuoteID"),
@ForeignKey(entity = Terms.class, parentColumns = "TermID", childColumns = "TermID"),
@ForeignKey(entity = Topics.class, parentColumns = "TopicID", childColumns = "TopicID")},
indices = {@Index("SourceID"), @Index("CommentID"), @Index("QuestionID"), @Index("QuoteID"),
@Index("TermID"), @Index("TopicID")})
public class Notes {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "NoteID")
private int noteID;
@ColumnInfo(name = "SourceID")
private int sourceID;
@ColumnInfo(name = "CommentID")
private int commentID;
@ColumnInfo(name = "QuestionID", defaultValue = "0")
private int questionID;
@ColumnInfo(name = "QuoteID", defaultValue = "0")
private int quoteID;
@ColumnInfo(name = "TermID", defaultValue = "0")
private int termID;
@ColumnInfo(name = "TopicID")
private int topicID;
@ColumnInfo(name = "Deleted", defaultValue = "0")
private int deleted;
public Notes(int noteID, int sourceID, int commentID, int questionID, int quoteID, int termID, int topicID, int deleted){
this.noteID = noteID;
this.sourceID = sourceID;
this.commentID = commentID;
this.questionID = questionID;
this.quoteID = quoteID;
this.termID = termID;
this.topicID = topicID;
this.deleted = deleted;
}
架构部分
"tableName": "Notes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`NoteID` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `SourceID` INTEGER NOT NULL, `CommentID` INTEGER NOT NULL, `QuestionID` INTEGER NOT NULL DEFAULT 0, `QuoteID` INTEGER NOT NULL DEFAULT 0, `TermID` INTEGER NOT NULL DEFAULT 0, `TopicID` INTEGER NOT NULL, `Deleted` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`SourceID`) REFERENCES `Sources`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`CommentID`) REFERENCES `Comments`(`CommentID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`QuestionID`) REFERENCES `Questions`(`QuestionID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`QuoteID`) REFERENCES `Quotes`(`QuoteID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`TermID`) REFERENCES `Terms`(`TermID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`TopicID`) REFERENCES `Topics`(`TopicID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "noteID",
"columnName": "NoteID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sourceID",
"columnName": "SourceID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "commentID",
"columnName": "CommentID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "questionID",
"columnName": "QuestionID",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "quoteID",
"columnName": "QuoteID",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "termID",
"columnName": "TermID",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "topicID",
"columnName": "TopicID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "Deleted",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"columnNames": [
"NoteID"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_Notes_SourceID",
"unique": false,
"columnNames": [
"SourceID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_SourceID` ON `${TABLE_NAME}` (`SourceID`)"
},
{
"name": "index_Notes_CommentID",
"unique": false,
"columnNames": [
"CommentID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_CommentID` ON `${TABLE_NAME}` (`CommentID`)"
},
{
"name": "index_Notes_QuestionID",
"unique": false,
"columnNames": [
"QuestionID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_QuestionID` ON `${TABLE_NAME}` (`QuestionID`)"
},
{
"name": "index_Notes_QuoteID",
"unique": false,
"columnNames": [
"QuoteID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_QuoteID` ON `${TABLE_NAME}` (`QuoteID`)"
},
{
"name": "index_Notes_TermID",
"unique": false,
"columnNames": [
"TermID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_TermID` ON `${TABLE_NAME}` (`TermID`)"
},
{
"name": "index_Notes_TopicID",
"unique": false,
"columnNames": [
"TopicID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_TopicID` ON `${TABLE_NAME}` (`TopicID`)"
}
],
"foreignKeys": [
{
"table": "Sources",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"SourceID"
],
"referencedColumns": [
"SourceID"
]
},
{
"table": "Comments",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"CommentID"
],
"referencedColumns": [
"CommentID"
]
},
{
"table": "Questions",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"QuestionID"
],
"referencedColumns": [
"QuestionID"
]
},
{
"table": "Quotes",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"QuoteID"
],
"referencedColumns": [
"QuoteID"
]
},
{
"table": "Terms",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"TermID"
],
"referencedColumns": [
"TermID"
]
},
{
"table": "Topics",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"TopicID"
],
"referencedColumns": [
"TopicID"
]
}
]
},
@ForeginKey
是添加 SQLite FOREIGN KEY
子句的注解。 SQLite FOREIGN KEY
子句是一个 rule (约束),表示列的值必须是指定的 table/column(s) 中的现有值。如果相关 table 中没有 0 值,则无法插入该行,而是发生冲突。
FOREIGN KEY
不是形成关系所必需的,而是用于确保参照完整性,即没有孤立行。
如果您必须使用 0 表示没有相关项目(并非真正必需),那么您有两个选择。
- by 不是真的需要见example/demo,输出包括0个关系。
选项是:-
- 不要使用外键,因此不存在阻止 0(或使用 null)或
的规则
- 或者在相关的 table 中有一行有 0 被相应地处理。
I thought about try an OnConflict annotation but wanted to reach out for some thoughts and suggestions.
使用 @OnConflict
不适用于外键冲突,但适用于由违反唯一非空检查和主键约束引起的冲突。
I tried default values but and setting up for null but can't seem to get it to work.
此外,使用 @ColumnInfo
的 defaultValue
不会使用 @Insert 插入默认值,因为始终为所有列提供值(在某些情况下禁止主键) .要应用 defaultValue
,您需要使用使用 INSERT INTO the_table (csv_list_of_columns) VALUES(csv_list_of_values_for_each_column)
的 @Query
,其中要默认的列不在列列表中,因此没有相应的值(所以将使用默认值)。
工作 example/demo(为简洁起见,只有一个关系注 > 来源)
该示例使用两个解决方案,第一个(实体 NotesV1/SourceV1)不使用外键,第二个使用(NotesV2/SourceV2)。两种解决方案都允许空值,后者根据 :-
利用 SQLite 中外键的空值处理
There is one exception: if the foreign key column in the track table is NULL, then no corresponding entry in the artist table is required. https://sqlite.org/foreignkeys.html
还应注意使用对象而不是基元,因此使用 Integer 而不是 int。但是,由于 id 高达 64 位签名,我使用了 Long/long。使用 Long/Integer 允许 null,基元不能为 null。
我还添加了一个名称列以允许显示描述。
所以两组实体:-
NotesV1:-
@Entity(
indices = {
@Index("SourceIDMap")
}
)
class NotesV1 {
@PrimaryKey
@ColumnInfo(name = "NoteID")
Long noteId = null;
@ColumnInfo(name = "SourceIDMap",defaultValue = "0")
Long sourceId = null;
@ColumnInfo(name = "SourceName",defaultValue = "Not Given")
String noteName;
NotesV1(){}
@Ignore
NotesV1(Long noteId, Long sourceId, String noteName) {
this.noteId = noteId;
this.sourceId = sourceId;
this.noteName = noteName;
}
@Ignore
NotesV1(long sourceId, String noteName) {
this(null,sourceId,noteName);
}
@Ignore
NotesV1(String noteName) {
this(null,null,noteName);
}
}
- 被忽略的构造函数提供了更大的灵活性
NotesV2(带外键):-
@Entity(
foreignKeys = {
@ForeignKey(entity = SourceV2.class,parentColumns = {"SourceID"}, childColumns = {"SourceIDMap"})
},
indices = {
@Index("SourceIDMap")
}
)
class NotesV2 {
@PrimaryKey
@ColumnInfo(name = "NoteID")
Long noteId = null;
// suggest to always have unique column names so SourceIDMap will map to SourceID
@ColumnInfo(name = "SourceIDMap",defaultValue = "0")
Long sourceId = null;
@ColumnInfo(name = "SourceName",defaultValue = "Not Given")
String noteName;
}
- 没有添加额外的忽略构造函数
SourceV1
@Entity
class SourceV1 {
@PrimaryKey
@ColumnInfo(name = "SourceID")
Long sourceId = null;
String sourceName;
}
SourceV2(除非 class 名称与 SourceV1 相同):-
@Entity
class SourceV2 {
@PrimaryKey
@ColumnInfo(name = "SourceID")
Long sourceId = null;
String sourceName;
}
通过满足 Notes 和 Source 之间的连接 (@Relation
) 的两个 POJOS 的关系显示结果:-
NotesV1WithSources
class NotesV1WithSources {
@Embedded
NotesV1 notesV1;
@Relation(
entity = SourceV1.class,parentColumn = "SourceIDMap",entityColumn = "SourceID"
)
List<SourceV1> sourceV1List;
}
和基本相同的NotesV2WithSources :-
class NotesV2WithSources {
@Embedded
NotesV2 notesV2;
@Relation(
entity = SourceV2.class,parentColumn = "SourceIDMap",entityColumn = "SourceID"
)
List<SourceV2> sourceV2List;
}
单个 @DAO
class AllDao (注意包括仅针对 NotesV1 使用 defaultValue
的插入查询) :-
@Dao
abstract class AllDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(NotesV1 notesV1);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(NotesV2 notesV2);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(SourceV1 sourceV1);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(SourceV2 sourceV2);
/* Using @ColumnInfo's defaultValue examples */
@Query("INSERT INTO notesV1 (SourceIdMap) VALUES(:sourceId)")
abstract void insertNotesV1OnlyBySourceId(long sourceId);
@Query("INSERT INTO notesV1 (SourceName) VALUES(:sourceName)")
abstract void insertNotesV1OnlyBySourceName(String sourceName);
@Transaction
@Query("SELECT * FROM notesv1")
abstract List<NotesV1WithSources> getAllNotesV1WithSources();
@Transaction
@Query("SELECT * FROM notesv2")
abstract List<NotesV2WithSources> getAllNotesV2WithSources();
}
漂亮 standard/simple @Database class TheDatabase :-
@Database(entities = {NotesV1.class,NotesV2.class,SourceV1.class,SourceV2.class},version = 1)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
private static volatile TheDatabase instance = null;
public static TheDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase.class,"notes.db")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
最后一个 Activity 即 uses/demonstrates 以上,MainActivity :-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
private static final String TAGV1 = "NOTESV1INFO";
private static final String TAGV2 = "NOTESV2INFO";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
/* Without Foreign keys and shows defaultValue */
SourceV1 s1 = new SourceV1();
s1.sourceName = "Source 1";
s1.sourceId = dao.insert(s1);
dao.insert(new NotesV1()); // All defaults
dao.insert(new NotesV1()); /// and again
dao.insert(new NotesV1("Just the Note Name")); // Via @Ignore'd constructor
dao.insert(new NotesV1(1000L,s1.sourceId,"All provided")); // Via @Ignore'd constrcutor
dao.insertNotesV1OnlyBySourceId(0); // Via @Query with 0 passed as SourceId (so name is default)
dao.insertNotesV1OnlyBySourceName("Again just the note name"); //Via @Query with name passed (so SourceId is 0)
for(NotesV1WithSources nv1: dao.getAllNotesV1WithSources()) {
Log.d(TAGV1,
"Note ID is " + nv1.notesV1.noteId +
" Name is " + nv1.notesV1.noteName +
" SourceID is " + nv1.notesV1.sourceId +
"\nSources(" + nv1.sourceV1List.size() + ") Are:-");
for (SourceV1 s: nv1.sourceV1List) {
Log.d(TAGV1,"\tSource ID is " + s.sourceId + " Source Name is " + s.sourceName );
}
}
/* With Foreign Keys */
// SPECIAL ROW 0 INDICATIVE OF NO RELATION
SourceV2 s2 = new SourceV2();
s2.sourceId = 0L;
s2.sourceName = "I SHOULD BE IGNORED";
Log.d(TAGV2,"1. Special Source row Inserted with ID = " + dao.insert(s2));
Log.d(TAGV2,"2. NotesV2 insert ID was " + dao.insert(new NotesV2()) + " (note -1 then not inserted)");
NotesV2 n2 = new NotesV2();
n2.sourceId = 0L;
n2.noteName ="references special source";
Log.d(TAGV2,"3. NotesV2 insert ID was " + dao.insert(n2) + " (note -1 then not inserted)");
/*
//OUCH NO such Source2 so Foreign Key conflict NOT trapped by IGNORE so abends
e.g. android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
n2.sourceId = 100L;
Log.d(TAGV2,"4. NotesV2 insert ID was " + dao.insert(n2) + " (note -1 then not inserted)");
*/
s2 = new SourceV2();
s2.sourceName = "I am a normal source";
long s2Id = -99;
Log.d(TAGV2,"5. Normal Source row Inserted with ID = " + (s2Id = dao.insert(s2)));
n2.sourceId = s2Id;
for(NotesV2WithSources nv2: dao.getAllNotesV2WithSources()) {
Log.d(TAGV2,
"Note ID is " + nv2.notesV2.noteId +
" Name is " + nv2.notesV2.noteName +
" SourceID is " + nv2.notesV2.sourceId +
"\nSources(" + nv2.sourceV2List.size() + ") Are:-");
for (SourceV2 s: nv2.sourceV2List) {
Log.d(TAGV2,"\tSource ID is " + s.sourceId + " Source Name is " + s.sourceName );
}
}
}
}
- 注意以上设计为运行一次而已
- 运行 在主线程上为简洁起见
- 上半场用V1的下半场用V2的
- 请注意注释掉的插入,它会崩溃,因为 IGNORE 不会忽略外键约束冲突。
- 评论包含此类崩溃的摘录
当运行输出为:-
D/NOTESV1INFO: Note ID is 1 Name is null SourceID is null
Sources(0) Are:-
D/NOTESV1INFO: Note ID is 2 Name is null SourceID is null
Sources(0) Are:-
D/NOTESV1INFO: Note ID is 3 Name is Just the Note Name SourceID is null
Sources(0) Are:-
D/NOTESV1INFO: Note ID is 1000 Name is All provided SourceID is 1
Sources(1) Are:-
D/NOTESV1INFO: Source ID is 1 Source Name is Source 1
D/NOTESV1INFO: Note ID is 1001 Name is Not Given SourceID is 0
Sources(0) Are:-
D/NOTESV1INFO: Note ID is 1002 Name is Again just the note name SourceID is 0
Sources(0) Are:-
D/NOTESV2INFO: 1. Special Source row Inserted with ID = 0
D/NOTESV2INFO: 2. NotesV2 insert ID was 1 (note -1 then not inserted)
D/NOTESV2INFO: 3. NotesV2 insert ID was 2 (note -1 then not inserted)
D/NOTESV2INFO: 5. Normal Source row Inserted with ID = 1
D/NOTESV2INFO: Note ID is 1 Name is null SourceID is null
Sources(0) Are:-
D/NOTESV2INFO: Note ID is 2 Name is references special source SourceID is 0
Sources(1) Are:-
D/NOTESV2INFO: Source ID is 0 Source Name is I SHOULD BE IGNORED
- 可以看出关系的存在不需要外键(即 V1 根本没有定义 FK)。
- 还可以看出,空值存在并且不会导致任何问题。
使用 Application Inspector(以前称为 Database Inspector)然后 :-
问题: 对于 Android Room,当输入零 (0) 时,我在连接 table 上收到外键约束错误。我仍在努力学习 Android Room,所以我确定我忽略了文档和教程中的某些内容。
我尝试过的方法: 我确实在使用 SQLite 的桌面版本中安装了此应用程序,我没有遇到任何问题,并已尝试对其进行相同的设置。我在这里和其他地方阅读了一些 posts 的例子,甚至删除了 return 零的外键声明,但后来我很难开始在不同的 [=37] 上触发模式=] 完全。我尝试了默认值,但设置为 null 但似乎无法正常工作。在写这个 post 时,我仍然会重新阅读一些内容,但我确定我只是忽略了一些东西 - 旧的 80/20 规则。
我正在尝试做的事情: 此联接 table 从 6 table 中捕获 ID。但是,根据用户条目,有 3 个外键,用户可能没有添加任何要引用的内容。由于 Android Room 本质上是包装 SQLite 以便于使用,到目前为止,我还无法确定如何让它接受外键上的零值。我想尝试使用 OnConflict
注释,但想寻求一些想法和建议。
实体
@Entity(tableName = "Notes", foreignKeys = {
@ForeignKey(entity = Sources.class, parentColumns = "SourceID", childColumns = "SourceID"),
@ForeignKey(entity = Comments.class, parentColumns = "CommentID", childColumns = "CommentID"),
@ForeignKey(entity = Questions.class, parentColumns = "QuestionID", childColumns = "QuestionID"),
@ForeignKey(entity = Quotes.class, parentColumns = "QuoteID", childColumns = "QuoteID"),
@ForeignKey(entity = Terms.class, parentColumns = "TermID", childColumns = "TermID"),
@ForeignKey(entity = Topics.class, parentColumns = "TopicID", childColumns = "TopicID")},
indices = {@Index("SourceID"), @Index("CommentID"), @Index("QuestionID"), @Index("QuoteID"),
@Index("TermID"), @Index("TopicID")})
public class Notes {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "NoteID")
private int noteID;
@ColumnInfo(name = "SourceID")
private int sourceID;
@ColumnInfo(name = "CommentID")
private int commentID;
@ColumnInfo(name = "QuestionID", defaultValue = "0")
private int questionID;
@ColumnInfo(name = "QuoteID", defaultValue = "0")
private int quoteID;
@ColumnInfo(name = "TermID", defaultValue = "0")
private int termID;
@ColumnInfo(name = "TopicID")
private int topicID;
@ColumnInfo(name = "Deleted", defaultValue = "0")
private int deleted;
public Notes(int noteID, int sourceID, int commentID, int questionID, int quoteID, int termID, int topicID, int deleted){
this.noteID = noteID;
this.sourceID = sourceID;
this.commentID = commentID;
this.questionID = questionID;
this.quoteID = quoteID;
this.termID = termID;
this.topicID = topicID;
this.deleted = deleted;
}
架构部分
"tableName": "Notes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`NoteID` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `SourceID` INTEGER NOT NULL, `CommentID` INTEGER NOT NULL, `QuestionID` INTEGER NOT NULL DEFAULT 0, `QuoteID` INTEGER NOT NULL DEFAULT 0, `TermID` INTEGER NOT NULL DEFAULT 0, `TopicID` INTEGER NOT NULL, `Deleted` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`SourceID`) REFERENCES `Sources`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`CommentID`) REFERENCES `Comments`(`CommentID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`QuestionID`) REFERENCES `Questions`(`QuestionID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`QuoteID`) REFERENCES `Quotes`(`QuoteID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`TermID`) REFERENCES `Terms`(`TermID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`TopicID`) REFERENCES `Topics`(`TopicID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "noteID",
"columnName": "NoteID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sourceID",
"columnName": "SourceID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "commentID",
"columnName": "CommentID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "questionID",
"columnName": "QuestionID",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "quoteID",
"columnName": "QuoteID",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "termID",
"columnName": "TermID",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "topicID",
"columnName": "TopicID",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "Deleted",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"columnNames": [
"NoteID"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_Notes_SourceID",
"unique": false,
"columnNames": [
"SourceID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_SourceID` ON `${TABLE_NAME}` (`SourceID`)"
},
{
"name": "index_Notes_CommentID",
"unique": false,
"columnNames": [
"CommentID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_CommentID` ON `${TABLE_NAME}` (`CommentID`)"
},
{
"name": "index_Notes_QuestionID",
"unique": false,
"columnNames": [
"QuestionID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_QuestionID` ON `${TABLE_NAME}` (`QuestionID`)"
},
{
"name": "index_Notes_QuoteID",
"unique": false,
"columnNames": [
"QuoteID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_QuoteID` ON `${TABLE_NAME}` (`QuoteID`)"
},
{
"name": "index_Notes_TermID",
"unique": false,
"columnNames": [
"TermID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_TermID` ON `${TABLE_NAME}` (`TermID`)"
},
{
"name": "index_Notes_TopicID",
"unique": false,
"columnNames": [
"TopicID"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Notes_TopicID` ON `${TABLE_NAME}` (`TopicID`)"
}
],
"foreignKeys": [
{
"table": "Sources",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"SourceID"
],
"referencedColumns": [
"SourceID"
]
},
{
"table": "Comments",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"CommentID"
],
"referencedColumns": [
"CommentID"
]
},
{
"table": "Questions",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"QuestionID"
],
"referencedColumns": [
"QuestionID"
]
},
{
"table": "Quotes",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"QuoteID"
],
"referencedColumns": [
"QuoteID"
]
},
{
"table": "Terms",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"TermID"
],
"referencedColumns": [
"TermID"
]
},
{
"table": "Topics",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"TopicID"
],
"referencedColumns": [
"TopicID"
]
}
]
},
@ForeginKey
是添加 SQLite FOREIGN KEY
子句的注解。 SQLite FOREIGN KEY
子句是一个 rule (约束),表示列的值必须是指定的 table/column(s) 中的现有值。如果相关 table 中没有 0 值,则无法插入该行,而是发生冲突。
FOREIGN KEY
不是形成关系所必需的,而是用于确保参照完整性,即没有孤立行。
如果您必须使用 0 表示没有相关项目(并非真正必需),那么您有两个选择。
- by 不是真的需要见example/demo,输出包括0个关系。
选项是:-
- 不要使用外键,因此不存在阻止 0(或使用 null)或 的规则
- 或者在相关的 table 中有一行有 0 被相应地处理。
I thought about try an OnConflict annotation but wanted to reach out for some thoughts and suggestions.
使用 @OnConflict
不适用于外键冲突,但适用于由违反唯一非空检查和主键约束引起的冲突。
I tried default values but and setting up for null but can't seem to get it to work.
此外,使用 @ColumnInfo
的 defaultValue
不会使用 @Insert 插入默认值,因为始终为所有列提供值(在某些情况下禁止主键) .要应用 defaultValue
,您需要使用使用 INSERT INTO the_table (csv_list_of_columns) VALUES(csv_list_of_values_for_each_column)
的 @Query
,其中要默认的列不在列列表中,因此没有相应的值(所以将使用默认值)。
工作 example/demo(为简洁起见,只有一个关系注 > 来源)
该示例使用两个解决方案,第一个(实体 NotesV1/SourceV1)不使用外键,第二个使用(NotesV2/SourceV2)。两种解决方案都允许空值,后者根据 :-
利用 SQLite 中外键的空值处理There is one exception: if the foreign key column in the track table is NULL, then no corresponding entry in the artist table is required. https://sqlite.org/foreignkeys.html
还应注意使用对象而不是基元,因此使用 Integer 而不是 int。但是,由于 id 高达 64 位签名,我使用了 Long/long。使用 Long/Integer 允许 null,基元不能为 null。
我还添加了一个名称列以允许显示描述。
所以两组实体:-
NotesV1:-
@Entity(
indices = {
@Index("SourceIDMap")
}
)
class NotesV1 {
@PrimaryKey
@ColumnInfo(name = "NoteID")
Long noteId = null;
@ColumnInfo(name = "SourceIDMap",defaultValue = "0")
Long sourceId = null;
@ColumnInfo(name = "SourceName",defaultValue = "Not Given")
String noteName;
NotesV1(){}
@Ignore
NotesV1(Long noteId, Long sourceId, String noteName) {
this.noteId = noteId;
this.sourceId = sourceId;
this.noteName = noteName;
}
@Ignore
NotesV1(long sourceId, String noteName) {
this(null,sourceId,noteName);
}
@Ignore
NotesV1(String noteName) {
this(null,null,noteName);
}
}
- 被忽略的构造函数提供了更大的灵活性
NotesV2(带外键):-
@Entity(
foreignKeys = {
@ForeignKey(entity = SourceV2.class,parentColumns = {"SourceID"}, childColumns = {"SourceIDMap"})
},
indices = {
@Index("SourceIDMap")
}
)
class NotesV2 {
@PrimaryKey
@ColumnInfo(name = "NoteID")
Long noteId = null;
// suggest to always have unique column names so SourceIDMap will map to SourceID
@ColumnInfo(name = "SourceIDMap",defaultValue = "0")
Long sourceId = null;
@ColumnInfo(name = "SourceName",defaultValue = "Not Given")
String noteName;
}
- 没有添加额外的忽略构造函数
SourceV1
@Entity
class SourceV1 {
@PrimaryKey
@ColumnInfo(name = "SourceID")
Long sourceId = null;
String sourceName;
}
SourceV2(除非 class 名称与 SourceV1 相同):-
@Entity
class SourceV2 {
@PrimaryKey
@ColumnInfo(name = "SourceID")
Long sourceId = null;
String sourceName;
}
通过满足 Notes 和 Source 之间的连接 (@Relation
) 的两个 POJOS 的关系显示结果:-
NotesV1WithSources
class NotesV1WithSources {
@Embedded
NotesV1 notesV1;
@Relation(
entity = SourceV1.class,parentColumn = "SourceIDMap",entityColumn = "SourceID"
)
List<SourceV1> sourceV1List;
}
和基本相同的NotesV2WithSources :-
class NotesV2WithSources {
@Embedded
NotesV2 notesV2;
@Relation(
entity = SourceV2.class,parentColumn = "SourceIDMap",entityColumn = "SourceID"
)
List<SourceV2> sourceV2List;
}
单个 @DAO
class AllDao (注意包括仅针对 NotesV1 使用 defaultValue
的插入查询) :-
@Dao
abstract class AllDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(NotesV1 notesV1);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(NotesV2 notesV2);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(SourceV1 sourceV1);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(SourceV2 sourceV2);
/* Using @ColumnInfo's defaultValue examples */
@Query("INSERT INTO notesV1 (SourceIdMap) VALUES(:sourceId)")
abstract void insertNotesV1OnlyBySourceId(long sourceId);
@Query("INSERT INTO notesV1 (SourceName) VALUES(:sourceName)")
abstract void insertNotesV1OnlyBySourceName(String sourceName);
@Transaction
@Query("SELECT * FROM notesv1")
abstract List<NotesV1WithSources> getAllNotesV1WithSources();
@Transaction
@Query("SELECT * FROM notesv2")
abstract List<NotesV2WithSources> getAllNotesV2WithSources();
}
漂亮 standard/simple @Database class TheDatabase :-
@Database(entities = {NotesV1.class,NotesV2.class,SourceV1.class,SourceV2.class},version = 1)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
private static volatile TheDatabase instance = null;
public static TheDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase.class,"notes.db")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
最后一个 Activity 即 uses/demonstrates 以上,MainActivity :-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
private static final String TAGV1 = "NOTESV1INFO";
private static final String TAGV2 = "NOTESV2INFO";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
/* Without Foreign keys and shows defaultValue */
SourceV1 s1 = new SourceV1();
s1.sourceName = "Source 1";
s1.sourceId = dao.insert(s1);
dao.insert(new NotesV1()); // All defaults
dao.insert(new NotesV1()); /// and again
dao.insert(new NotesV1("Just the Note Name")); // Via @Ignore'd constructor
dao.insert(new NotesV1(1000L,s1.sourceId,"All provided")); // Via @Ignore'd constrcutor
dao.insertNotesV1OnlyBySourceId(0); // Via @Query with 0 passed as SourceId (so name is default)
dao.insertNotesV1OnlyBySourceName("Again just the note name"); //Via @Query with name passed (so SourceId is 0)
for(NotesV1WithSources nv1: dao.getAllNotesV1WithSources()) {
Log.d(TAGV1,
"Note ID is " + nv1.notesV1.noteId +
" Name is " + nv1.notesV1.noteName +
" SourceID is " + nv1.notesV1.sourceId +
"\nSources(" + nv1.sourceV1List.size() + ") Are:-");
for (SourceV1 s: nv1.sourceV1List) {
Log.d(TAGV1,"\tSource ID is " + s.sourceId + " Source Name is " + s.sourceName );
}
}
/* With Foreign Keys */
// SPECIAL ROW 0 INDICATIVE OF NO RELATION
SourceV2 s2 = new SourceV2();
s2.sourceId = 0L;
s2.sourceName = "I SHOULD BE IGNORED";
Log.d(TAGV2,"1. Special Source row Inserted with ID = " + dao.insert(s2));
Log.d(TAGV2,"2. NotesV2 insert ID was " + dao.insert(new NotesV2()) + " (note -1 then not inserted)");
NotesV2 n2 = new NotesV2();
n2.sourceId = 0L;
n2.noteName ="references special source";
Log.d(TAGV2,"3. NotesV2 insert ID was " + dao.insert(n2) + " (note -1 then not inserted)");
/*
//OUCH NO such Source2 so Foreign Key conflict NOT trapped by IGNORE so abends
e.g. android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
n2.sourceId = 100L;
Log.d(TAGV2,"4. NotesV2 insert ID was " + dao.insert(n2) + " (note -1 then not inserted)");
*/
s2 = new SourceV2();
s2.sourceName = "I am a normal source";
long s2Id = -99;
Log.d(TAGV2,"5. Normal Source row Inserted with ID = " + (s2Id = dao.insert(s2)));
n2.sourceId = s2Id;
for(NotesV2WithSources nv2: dao.getAllNotesV2WithSources()) {
Log.d(TAGV2,
"Note ID is " + nv2.notesV2.noteId +
" Name is " + nv2.notesV2.noteName +
" SourceID is " + nv2.notesV2.sourceId +
"\nSources(" + nv2.sourceV2List.size() + ") Are:-");
for (SourceV2 s: nv2.sourceV2List) {
Log.d(TAGV2,"\tSource ID is " + s.sourceId + " Source Name is " + s.sourceName );
}
}
}
}
- 注意以上设计为运行一次而已
- 运行 在主线程上为简洁起见
- 上半场用V1的下半场用V2的
- 请注意注释掉的插入,它会崩溃,因为 IGNORE 不会忽略外键约束冲突。
- 评论包含此类崩溃的摘录
当运行输出为:-
D/NOTESV1INFO: Note ID is 1 Name is null SourceID is null
Sources(0) Are:-
D/NOTESV1INFO: Note ID is 2 Name is null SourceID is null
Sources(0) Are:-
D/NOTESV1INFO: Note ID is 3 Name is Just the Note Name SourceID is null
Sources(0) Are:-
D/NOTESV1INFO: Note ID is 1000 Name is All provided SourceID is 1
Sources(1) Are:-
D/NOTESV1INFO: Source ID is 1 Source Name is Source 1
D/NOTESV1INFO: Note ID is 1001 Name is Not Given SourceID is 0
Sources(0) Are:-
D/NOTESV1INFO: Note ID is 1002 Name is Again just the note name SourceID is 0
Sources(0) Are:-
D/NOTESV2INFO: 1. Special Source row Inserted with ID = 0
D/NOTESV2INFO: 2. NotesV2 insert ID was 1 (note -1 then not inserted)
D/NOTESV2INFO: 3. NotesV2 insert ID was 2 (note -1 then not inserted)
D/NOTESV2INFO: 5. Normal Source row Inserted with ID = 1
D/NOTESV2INFO: Note ID is 1 Name is null SourceID is null
Sources(0) Are:-
D/NOTESV2INFO: Note ID is 2 Name is references special source SourceID is 0
Sources(1) Are:-
D/NOTESV2INFO: Source ID is 0 Source Name is I SHOULD BE IGNORED
- 可以看出关系的存在不需要外键(即 V1 根本没有定义 FK)。
- 还可以看出,空值存在并且不会导致任何问题。
使用 Application Inspector(以前称为 Database Inspector)然后 :-