Polymorphism With a Single Table
We could go the other route: have a single table for all Note
objects, regardless of whether they are a Comment
or a Link
. For small objects with few properties, with a lot of overlap between the properties of the concrete types, this is manageable. It becomes unwieldy for many concrete types with many disparate properties. It also puts limits on your SQL, as the only practical NOT NULL
columns are ones for which you can supply values for every possible concrete type. You also need some way of determining what concrete type to use for any given table row, and often that requires yet another column.
But, it is an option, if having multiple tables makes you concerned.
The Trips/RoomPolySingle
sample project employs this strategy. It has the same structure for Comment
, Link
, and Note
, but this time Note
is the @Entity
:
package com.commonsware.android.room;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.ForeignKey;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;
import static android.arch.persistence.room.ForeignKey.CASCADE;
@Entity(
tableName="notes",
foreignKeys=@ForeignKey(
entity=Trip.class,
parentColumns="id",
childColumns="tripId",
onDelete=CASCADE),
indices=@Index("tripId"))
public class Note {
public enum Type {
COMMENT(0),
LINK(1);
private final int value;
Type(int value) {
this.value=value;
}
public int value() {
return value;
}
}
@PrimaryKey
@NonNull
public final String id;
public final String title;
public final String url;
@NonNull public final String tripId;
public final Type type;
public Note(@NonNull String id, String title, @NonNull String url,
@NonNull String tripId, Type type) {
this.id=id;
this.title=title;
this.url=url;
this.tripId=tripId;
this.type=type;
}
}
In addition to the fields from Link
and Comment
, we also have a type
field, housing an enum
that indicates whether this Note
is a LINK
or COMMENT
. This requires @TypeConverter
methods, in this case added to the existing TypeTransmogrifier
class:
@TypeConverter
public static Integer fromType(Note.Type type) {
return type.value();
}
@TypeConverter
public static Note.Type toType(Integer value) {
return value==0 ? Note.Type.COMMENT : Note.Type.LINK;
}
Comment
is a subclass of Note
, using the title
field to hold the comment text:
package com.commonsware.android.room;
import android.arch.persistence.room.Ignore;
import android.support.annotation.NonNull;
import java.util.UUID;
public class Comment extends Note {
public Comment(@NonNull String id, String title, String url,
@NonNull String tripId, Type type) {
super(id, title, url, tripId, Type.COMMENT);
}
@Ignore
public Comment(String text, @NonNull Trip trip) {
this(UUID.randomUUID().toString(), text, null, trip.id, Type.COMMENT);
}
}
Link
is another subclass of Note
:
package com.commonsware.android.room;
import android.arch.persistence.room.Ignore;
import android.support.annotation.NonNull;
import java.util.UUID;
public class Link extends Note {
public Link(@NonNull String id, String title, String url,
@NonNull String tripId, Type type) {
super(id, title, url, tripId, Type.LINK);
}
@Ignore
public Link(String text, String url, @NonNull Trip trip) {
this(UUID.randomUUID().toString(), text, url, trip.id, Type.LINK);
}
}
This simplifies our @Dao
class (TripStore
). In effect, Room ignores the difference between Link
and Comment
, dealing only with the base Note
class, since that is the @Entity
. So for inserts, updates, and deletes, we can pass a Link
or Comment
to methods that take a Note
, and it all works fine.
/*
Note
*/
@Query("SELECT * FROM notes WHERE tripId=:tripId")
abstract List<Note> findNotesForTrip(String tripId);
@Insert
abstract void insert(Note... comments);
@Update
abstract void update(Note... comments);
@Delete
abstract void delete(Note... comments);
Retrieval becomes a bit more interesting, though. findNotesForTrip()
, shown above, nicely returns all links and comments… but as Note
objects, not as Link
and Comment
objects. If we want those, we need to have dedicated retrieval methods by type:
/*
Comment
*/
@Query("SELECT * FROM notes WHERE tripId=:tripId AND type=0")
abstract List<Comment> findCommentsForTrip(String tripId);
/*
Link
*/
@Query("SELECT * FROM notes WHERE tripId=:tripId AND type=1")
abstract List<Link> findLinksForTrip(String tripId);
And, as a result, we do not have a single method to retrieve both links and comments as Link
and Comment
objects. We would need another @Transaction
wrapper method as before.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.