继承 Table 方案的有效方法

Efficient way for Inheritance Table schemes

我打算显示 CarServiceEntries 的列表视图。 CarServiceEntry class 包含服务的基本数据:

@Entity
abstract class CarServiceEntry(
    private int id;
    private Date date;
    private float odometer;
    
    /*
    getters and setters constructor....
    */
)

CarServiceEntry 是抽象的,因为继承它的 classes 具有更详细的信息:

@Entity
abstract class Income extends CarServiceEntry(
    @Converter(....)
    private PaymentType paymentType;
    private float totalAmount;
    
    /*
    getters and setters constructor....
    */
)

构建 SQLScheme 时出现问题。 因为我想一起加载所有条目而不是为每个 CarServiceEntry 创建自己的 sqltable 并查询每个 table (getAllIncomes() getAllExpenses() 等),我如何加载每个 CarServiceEntry(income,expense , 服务)使用一个 sqltabletable 进行加载(如果可能)。 我不喜欢的当前方法如下所示:

CREATE TABLE CarServiceEntry(
    id INTEGER PRIMARY KEY,
    serviceType TEXT CHECK CONSTRAINT (....) //expense, income, service)
    date,
    odometer,
    
    /*
    A LOT of fields as each service has its own "unique" fields and I put it all together which I don' really like :( loading 30+ fields just for one sql statement is not something I like, unless I don't have any other option.
    */
)

我宁愿搜索这样的解决方案:

CREATE TABLE CarServiceEntry(
    id INTEGER PRIMARY KEY,
    date DATE,
    odometer NUMBER
)

CREATE TABLE Income INHERITS CarServiceEntry(
    paymentType TEXT,
    totalAmount NUMBER
    /*some other fields*/
)

CREATE TABLE Expense INHERITS CarServiceEntry(
    location TEXT
    totalCost NUMBER
    /*some other fields*/
)

==>

@Query("SELECT * FROM CarServiceEntry") //this should also return Income and Expense table
Flowable<List<CarServiceEntry>> getAllEntries();

有这样的方法吗?或者创建一个包含大量字段的 table 是唯一的方法吗?

根据:sql inheritance 为我的子条目类型创建外键 tables 将是一种继承,但这并不能解决我的问题,因为我仍然需要加载每个table。我想唯一的解决方案是使用一个 table 和许多空值字段?

SQLite 不支持继承,我相信利用 SQLite 和 Room 支持的关系,即使不是更简单,也同样简单。

通过房间创建多个 table 与创建和处理关系一样简单。所以我建议采用典型的方法。

这是一个基于我认为您正在努力完成的目标的示例。

首先是 CarServiceEntry table(稍后会有与之相关的费用和收入):-

public class CarServiceEntry {
    @PrimaryKey
    private Long id;
    private String date;
    private float odometer;

    public CarServiceEntry(){}

    .... getters and setters removed for brevity

    /* more convenient constructor (see cse3 in example) */
    /* @Ignore to supress Room warning                   */
    @Ignore
    public CarServiceEntry(String date, Float odometer) {
        this.date = date;
        this.odometer = odometer;
    }
}
  • 注释已简化,因此不需要 TypeConverters

接下来收入table:-

@Entity(tableName = "income",
        foreignKeys = {
            @ForeignKey(
                    entity = CarServiceEntry.class,
                    parentColumns = "id",
                    childColumns = "incomeCarServiceEntryId",
                    onDelete = CASCADE,
                    onUpdate = CASCADE
            )
        },
        indices = {@Index(
                value = {"incomeCarServiceEntryId"}
                )}
        )
public class Income {
    @PrimaryKey
    private Long incomeId;
    private Long incomeCarServiceEntryId;
    private int paymentType;
    private float totalAmount;

    .... getters and setters
}
  • 注意外键 = { .... } 和指标 = { .... } 都不是必需的,但建议将它们用作确保参照完整性的帮助
  • 注意附加栏incomeCarServiceEntryId这是相关CarServiceEntry的ID。

接下来支出table(与收入table非常相似):-

@Entity(tableName = "expense",
        foreignKeys = {
                @ForeignKey(
                        entity = CarServiceEntry.class,
                        parentColumns = {"id"},
                        childColumns = {"expenseCarServiceEntryId"},
                        onDelete = CASCADE,
                        onUpdate = CASCADE
                )
        },
        indices = {
                @Index(
                        value = {"expenseCarServiceEntryId"}
                        )}
)
public class Expense {
    @PrimaryKey
    private Long expenseId;
    private long expenseCarServiceEntryId;
    private String location;
    private float totalCost;
    
    .... getters and setters

}

现在一个 POJO(不是 table)用于提取名为 CarServiceEntryWithIncomeWithExpense 的相关数据(即具有所有相关收入和所有相关费用的 CarServiceEntry): -

public class CarServiceEntryWithIncomeWithExpense {

    @Embedded
    CarServiceEntry carServiceEntry;
    @Relation(entity = Income.class,parentColumn = "id",entityColumn = "incomeCarServiceEntryId")
    List<Income> incomeList;
    @Relation(entity = Expense.class,parentColumn = "id",entityColumn = "expenseCarServiceEntryId")
    List<Expense> expenseList;
}
  • 就是这样

现在道的(合二为一)AllDao :-

@Dao
interface AllDao {
    @Insert
    long insert(CarServiceEntry carServiceEntry);
    @Insert
    long insert(Expense expense);
    @Insert
    long insert(Income income);
    @Query("SELECT * FROM car_service_entry")
    List<CarServiceEntryWithIncomeWithExpense> getAllCarServiceEntriesWithIncomesAndWithExpenses();
}

@Database(包括单例方法)名为 Database(使用其他名称可能更好):-

@Database(entities = {CarServiceEntry.class,Income.class,Expense.class},version = 1)
public abstract class Database extends RoomDatabase {
    abstract AllDao getAllDao();

    private static volatile Database instance;

    public static Database getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(context,Database.class,"carservice.db")
                    .allowMainThreadQueries()
                    .build();
        }
        return instance;
    }
}
  • 注意为了演示的简洁和方便,使用了主线程。

终于把它们放在一起并演示 MainActivity

该演示添加了 3 个 carServiceEntries,包含收入和支出(第 3 个有 none 个)。然后提取 3 个包含所有收入和支出的 carServiceEntries。遍历提取的内容,将提取的内容输出到日志中。

:-

public class MainActivity extends AppCompatActivity {

    Database  db;
    AllDao dao;
    private static final String TAG = "CSEINFO";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        db = Database.getInstance(this);
        dao = db.getAllDao();
        /* Add a Service Entry noting it's ID */
        CarServiceEntry cse1 = new CarServiceEntry();
        cse1.setDate("2021-06-01");
        cse1.setOdometer(5120.78F);
        long cse1Id = dao.insert(cse1);
        /* Add another Service Entry noting it's id */
        CarServiceEntry cse2 = cse1;
        cse2.setDate("2021-06-02");
        cse2.setOdometer(7065.83F);
        long cse2Id = dao.insert(cse2);

        /* Use a single Income to add 3 Income Entries for (linked to) the 1st Service Entry */
        Income incomeEntry = new Income();
        incomeEntry.setIncomeCarServiceEntryId(cse1Id);
        incomeEntry.setPaymentType(10);
        incomeEntry.setTotalAmount(120.00F);
        dao.insert(incomeEntry);
        incomeEntry.setPaymentType(20);
        incomeEntry.setTotalAmount(230.00F);
        dao.insert(incomeEntry);
        incomeEntry.setPaymentType(15);
        incomeEntry.setTotalAmount(75.55F);
        dao.insert(incomeEntry);

        /* Use the same Income Entry to add 1 Entry for the 2nd Service Entry */
        incomeEntry.setIncomeCarServiceEntryId(cse2Id);
        incomeEntry.setPaymentType(25);
        incomeEntry.setTotalAmount(134.56F);
        dao.insert(incomeEntry);

        /* Add some Expense Entries */
        Expense expenseEntry = new Expense();
        expenseEntry.setExpenseCarServiceEntryId(cse1Id);
        expenseEntry.setLocation("London");
        expenseEntry.setTotalCost(500.00F);
        dao.insert(expenseEntry);
        expenseEntry.setLocation("New York");
        expenseEntry.setTotalCost(60.66F);
        dao.insert(expenseEntry);
        expenseEntry.setExpenseCarServiceEntryId(cse2Id);
        expenseEntry.setLocation("Paris");
        dao.insert(expenseEntry);
        expenseEntry.setLocation("Hamburg");
        dao.insert(expenseEntry);
        expenseEntry.setLocation("Madrid");
        dao.insert(expenseEntry);

        dao.insert(new CarServiceEntry("2021-06-03",1765.34F));

        for (CarServiceEntryWithIncomeWithExpense cse: dao.getAllCarServiceEntriesWithIncomesAndWithExpenses()) {
            Log.d(
                    TAG,
                    "CSE ID = " + cse.carServiceEntry.getId() +
                            " Date = " + cse.carServiceEntry.getDate() +
                            " ODO = " + cse.carServiceEntry.getOdometer()
            );
            for (Income i: cse.incomeList) {
                Log.d(
                        TAG,
                        "\tIncome Payment Type is " + i.getPaymentType() + " Total is " + i.getTotalAmount()
                        );
            }
            for(Expense e: cse.expenseList) {
                Log.d(
                        TAG,
                        "\tExpense Location is " + e.getLocation() + " Total is " + e.getTotalCost()
                );
            }
        }
    }
}

结果

Log输出如下:-

2021-06-11 13:01:35.116 D/CSEINFO: CSE ID = 1 Date = 2021-06-01 ODO = 5120.78
2021-06-11 13:01:35.116 D/CSEINFO:  Income Payment Type is 10 Total is 120.0
2021-06-11 13:01:35.116 D/CSEINFO:  Income Payment Type is 20 Total is 230.0
2021-06-11 13:01:35.116 D/CSEINFO:  Income Payment Type is 15 Total is 75.55
2021-06-11 13:01:35.116 D/CSEINFO:  Expense Location is London Total is 500.0
2021-06-11 13:01:35.116 D/CSEINFO:  Expense Location is New York Total is 60.66
2021-06-11 13:01:35.116 D/CSEINFO: CSE ID = 2 Date = 2021-06-02 ODO = 7065.83
2021-06-11 13:01:35.117 D/CSEINFO:  Income Payment Type is 25 Total is 134.56
2021-06-11 13:01:35.117 D/CSEINFO:  Expense Location is Paris Total is 60.66
2021-06-11 13:01:35.117 D/CSEINFO:  Expense Location is Hamburg Total is 60.66
2021-06-11 13:01:35.117 D/CSEINFO:  Expense Location is Madrid Total is 60.66
2021-06-11 13:01:35.117 D/CSEINFO: CSE ID = 3 Date = 2021-06-03 ODO = 1765.34