Fremdschlüssel

Wie im ersten Beispiel gezeigt kann ein Fremdschlüssel in einer Bestellung die zum Kunden verweist wie folgt definiert werden:

/**
 * The foreign key field 'fkField' refers to {@link #customer},
 * it will be set via {@link #setCustomer(Customer)}.
 */
@SeifeField(foreignKey = @ForeignkeyDef(fkField = "customer", refKeyField="id", refClass=Customer.class))
private Long customerId;

private Customer customer;

Das Attribut foreignKey = @ForeignkeyDef( /* .. */ ) definiert die Eigenschaft der Tabellenreferenz.

  • fkField
    bezeichnet den Namen des Feldes zur Instanzreferenz private Customer customer das der generierte Code nutzt wenn die Referenz aufgelöst wird
  • refKeyField
    bezeichnet den Primärschlüsselteil auf das dieses Feld referenziert, der Datentyp muss übereinstimmen
  • refClass
    definiert den Typ der Instanzreferenz

Der Mechanismus ist so einfach wie möglich gehalten. Es ist keine zusätzliche Bibliothek als Zwischenschicht erforderlich und es werden keine generischen Datentypen oder spezielle Collections benutzt.
Es wird einfacher Android Quelltext basierend auf Vorlagen generiert, der leicht zu lesen ist da die Fremdschlüssel-Definitionen ähnlich und selbsterklärend sind.

Der generierte SchemaPeer hat die korrekten SQL foreign-key Referenzen definiert, jedoch wird nicht automatisch ein Index dazu erzeugt. (Siehe dazu SQLite documentation oder z.B. diese Frage auf StackOverflow)

/**
 * Table creation script
 */
private static final String SQL_CREATE_TABLE_ORDER =
    "create table " + TBL_ORDER + " (" + 
        COL_ID + " integer primary key autoincrement," +
        COL_FK_CUSTOMER_CUSTOMER_ID + " integer," +
        COL_ORDER_DATE + " integer not null" +
        ", " +
        " FOREIGN KEY(" + COL_FK_CUSTOMER_CUSTOMER_ID  + ")" +
        " REFERENCES " + CustomerSchema.TBL_CUSTOMER 
        +"(" + CustomerSchema.COL_ID  + ")" +			 		
        ")";

Referenzierung von zusammengesetzten Primärschlüsseln

Wenn der Fremdschlüssel auf ein eindeutiges Tuple von Spalten verweist, also technisch korrekt ist, so kann es auch modelliert werden. Dazu werden alle technischen Schlüsselteile in fkField mit dem gleichen Instanznamen konfiguriert.
Die folgende Klasse ‚Language‘ hat einen zusammengesetzten Primärschlüssel bestehend aus Land und Sprache

@SeifeClass(sqlTablename="locale", version=2, generatorOptions={GeneratorOption.BOCLASS, GeneratorOption.SCHEMA_PEER})
public class Language {

  @SeifeField(isPrimaryKey=true, sqlOptions=@SqlFieldOptions(sqlAutoIncrement=false, sqlColumn="_country"))
  private String countryId;
  @SeifeField(isPrimaryKey=true, sqlOptions=@SqlFieldOptions(sqlAutoIncrement=false, sqlColumn="_language"))
  private String languageId;

  // ..
}

Der Fremdschlüssel wird dann wie unten defniniert, jeweils ein technisches Feld für die Referenzen und ein Instanzfeld „Language“ auf das beides Mal in fkField eingetragen wird. Diesesmal wird wie oben schon erwähnt auch ein Index ‚locale‘ erstellt.

@SeifeClass(generatorOptions={GeneratorOption.BOCLASS_PARCELABLE, GeneratorOption.SCHEMA_PEER, GeneratorOption.DB_HELPER+"=LocaleOpenHelper", 
    GeneratorOption.DATA_PROVIDER+"=LocaleProvider"})
public class LocaleData {

  private Language language;

  @SeifeField(
      foreignKey = @ForeignkeyDef(fkField="language", refKeyField="countryId", refClass=Language.class),
          sqlOptions=@SqlFieldOptions(sqlIndex="locale"))
  private String countryId;
  
  @SeifeField(
      foreignKey = @ForeignkeyDef(fkField="language", refKeyField="languageId", refClass=Language.class),
          sqlOptions=@SqlFieldOptions(sqlIndex="locale"))
  private String languageId;
  
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  @SeifeField(isPrimaryKey=true, sqlOptions=@SqlFieldOptions(sqlColumn="_id"))
  private int id;
  // ..
}

Das erzeugte Skript für die Tabelle im LocaleOpenHelper enthält dann die definierte Fremdschlüsselreferenz und es existiert eine SQL-Index Definition.

private static final String SQL_CREATE_TABLE_LOCALE_DATA =
    "create table " + TBL_LOCALE_DATA + " (" + 
        COL_ID + " integer primary key autoincrement," +
        COL_FK_LANGUAGE_COUNTRY_ID + " text," +
        COL_FK_LANGUAGE_LANGUAGE_ID + " text" +
        ", " +
        " FOREIGN KEY(" + COL_FK_LANGUAGE_COUNTRY_ID + "," + COL_FK_LANGUAGE_LANGUAGE_ID  + ")" +
        " REFERENCES " + LanguageSchema.TBL_LANGUAGE 
        +"(" + LanguageSchema.COL_COUNTRY_ID + "," + LanguageSchema.COL_LANGUAGE_ID  + ")" +			 		
        ")";

  /**
   * Index for locale
   */
  public static final String SQL_CREATE_IDX_LOCALE_DATA_LOCALE = 
      "create index if not exists "
          + TBL_LOCALE_DATA
          + "_idx_locale ON " + TBL_LOCALE_DATA
          + "(" + COL_FK_LANGUAGE_COUNTRY_ID + "," + COL_FK_LANGUAGE_LANGUAGE_ID + ")";

Versionierung

Wie im Abschnitt zur Versionierung von Tabellen beschrieben, werden auch der Fremdschlüssel und der Tabellenindex in der Upgrade Logik mit berücksichtigt. Im Beispiel ist der Foreign Key in Version 2 hinzugekommen;

private Language language;

@SeifeField(
    foreignKey = @ForeignkeyDef(fkField="language", refKeyField="countryId", refClass=Language.class),
        sqlOptions=@SqlFieldOptions(sqlIndex="locale"), version=2)
private String countryId;

@SeifeField(
    foreignKey = @ForeignkeyDef(fkField="language", refKeyField="languageId", refClass=Language.class),
        sqlOptions=@SqlFieldOptions(sqlIndex="locale"), version=2)
private String languageId;

Der Programmteil wird dann neben der Erstellung der neuen Spalten auch das SQL_CREATE_IDX_LOCALE_DATA_LOCALE script ausführen.

if (oldVersion < 2 && newVersion >= 2) { 
  db.execSQL("ALTER TABLE " + LocaleDataSchema.TBL_LOCALE_DATA + " ADD COLUMN " + LocaleDataSchema.COL_FK_LANGUAGE_COUNTRY_ID + " text");
  db.execSQL("ALTER TABLE " + LocaleDataSchema.TBL_LOCALE_DATA + " ADD COLUMN " + LocaleDataSchema.COL_FK_LANGUAGE_LANGUAGE_ID + " text");
  db.execSQL(LocaleDataSchema.SQL_CREATE_IDX_LOCALE_DATA_LOCALE);
}