【问题标题】:Efficient way for Inheritance Table schemes继承表方案的有效方法
【发布时间】:2021-06-10 19:38:27
【问题描述】:

我计划显示 CarServiceEntries 的列表视图。 CarServiceEntry 类包含服务的基本数据:

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

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

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

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

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();

有没有办法做到这一点?还是创建一个包含大量字段的表是唯一的方法?

根据:sql inheritance 为我的 subentrytype 表创建 FOREIGN KEY 是一种继承,但这并不能解决我的问题,因为我仍然需要加载每个表。我想唯一的解决方案是使用一个包含许多空值字段的表?

【问题讨论】:

    标签: sqlite inheritance android-room android-room-relation


    【解决方案1】:

    SQLite 不支持继承,我相信利用 SQLite 和 Room 支持的关系会很简单,甚至更简单。

    通过房间创建多个表非常简单,创建和处理关系也是如此。所以我建议采用典型的方法。

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

    首先是 CarServiceEntry 表(稍后将包含与之相关的费用和收入):-

    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

    接下来是收入表:-

    @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。

    接下来是费用表(和收入表差不多):-

    @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(不是表),用于提取名为 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;
    }
    
    • 是的就是这样

    Now the Dao's (All in One) 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 个都没有)。然后它提取 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()
                    );
                }
            }
        }
    }
    

    结果

    以下输出到日志:-

    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
    

    【讨论】:

    • 感谢非常详细的回答!建议的解决方案肯定会奏效。我使用该方法意识到的唯一一点是 CarServiceEntry 应该只将日期和 id 作为属性,因为我可以在同一日期使用不同的里程表值(155k 英里和可能 155.1 英里)获得费用和收入。因此,我想将里程表存储在收入和费用类中是唯一的方法。所以 CarServiceEntry 包含一个我们可以说的特定的 CarEntries 列表。这是我可以采用的最佳方法吗? :)
    • 我刚刚意识到这种方法可能不是可选的,如果我想查询最后一个让我们说最后 25 个 carentries(不管它的收入/支出)。查询 CarServiceEntry 日期无法正常工作,因为我必须另外查询每个费用/收入表(其中包含一个创建时间戳)行。也许一张桌子最终是要走的路,虽然我更喜欢你的解决方案,但查询可能更烦人? ://
    猜你喜欢
    • 2023-03-17
    • 2012-08-14
    • 1970-01-01
    • 1970-01-01
    • 2020-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-01
    相关资源
    最近更新 更多