继承 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
我打算显示 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