我正在做一个简单的商店应用程序来了解更多关于 Android 中的 Room DB 的信息,目前我对关系和嵌套对象的最佳方法感到有点困惑。
场景:客户从商店中选择一件商品并下单。之后,数据库使用订单 ID 更新客户表,以便可以在数据库中搜索客户订单。订单表具有该特定订单中的产品 ID。在客户“帐户”页面(在应用程序内),包括产品在内的所有订单都应显示所有必要的信息(例如订单 ID、产品名称、价格、数量等)。
问题这里的@Foreign key
、@Embedded
和@Relation
是什么?
最佳答案
第一件事是您的场景/结果架构可能缺失。
也就是说订单到产品的关系应该是多对多的关系。也就是很多产品可以被很多客户引用。这种关系通常由引用表处理。
因此,您将有一个客户表、一个包含引用客户的列的订单表、一个不引用任何内容的产品表和一个包含两列的引用表,一列引用产品,另一列引用产品。
@ForeignKey 定义了一个要求,即用于引用/关联/关联的一个或多个列必须引用父级中的值。
假设客户有一个列(或多个唯一标识客户的列,比如 1 代表一个这样的客户,2 代表另一个客户,依此类推。那么订单将有一个列引用客户是谁下的订单(假设一个Order是per a Customer)那么外键添加一个约束(规则),要求Order(子)中的值必须是一个值并且存在于Customer表(父)的引用列中. 如果插入(新订单)或更新(客户或订单)或删除(客户)导致此要求,则将导致冲突(错误)。
为了简化引用/关系完整性的维护,@ForeignKey 还可以包括在更新或删除父值时采取的 ON UPDATE 和 ON DELETE 操作(CASCADE 可能是最常用的选项)(CASCADE 进行更改或删除给 parent 的 child ,即将变化级联给 child )。
此类关系不需要外键,但可以提供帮助。
@Embedded 包括要包含在类或实体中的实体(或非实体类)的字段(从数据库角度来看的列)。
@Reltionship 允许提取/包含相关数据(实体)。
示例/演示
考虑以下定义表的实体(根据建议的模式):-
客户.java :-
@Entity
public class Customer {
@PrimaryKey()
Long customerId;
String customerName;
public Customer(){}
@Ignore
public Customer(String customerName) {
this.customerName = customerName;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
}
产品.java
@Entity
public class Product {
@PrimaryKey
Long productId;
String productName;
public Product(){}
@Ignore
public Product(String productName) {
this.productName = productName;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
}
Order.java(一个订单可以有一个客户的外键)
@Entity(
foreignKeys = @ForeignKey(
entity = Customer.class,
parentColumns = "customerId",
childColumns = "customerReference",
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
),
indices = {@Index(value = "customerReference")}
)
public class Order {
@PrimaryKey
Long orderId;
Long customerReference;
public Order(){}
@Ignore
public Order(long customerReference) {
this.customerReference = customerReference;
}
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getCustomerReference() {
return customerReference;
}
public void setCustomerReference(Long customerReference) {
this.customerReference = customerReference;
}
}
OrderProductReference.java(引用表)
@Entity(
primaryKeys = {"orderIdReference","productIdReference"},
foreignKeys = {
@ForeignKey(
entity = Order.class,
parentColumns = {"orderId"},
childColumns = "orderIdReference",
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
),
@ForeignKey(
entity = Product.class,
parentColumns = {"productId"},
childColumns = "productIdReference",
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
)
},
indices = {@Index(value = "productIdReference")}
)
public class OrderProductReference {
long orderIdReference;
long productIdReference;
public OrderProductReference(){}
@Ignore
public OrderProductReference(long customerIdReference, long productIdReference) {
this.orderIdReference = customerIdReference;
this.productIdReference = productIdReference;
}
public long getCustomerIdReference() {
return orderIdReference;
}
public void setCustomerIdReference(long customerIdReference) {
this.orderIdReference = customerIdReference;
}
public long getProductIdReference() {
return productIdReference;
}
public void setProductIdReference(long productIdReference) {
this.productIdReference = productIdReference;
}
}
OrderWithProduct.java
这嵌入了 OrderProductReference(表)并包含关系以包含(如嵌入)引用的订单和产品。
public class OrderWithProduct {
@Embedded
OrderProductReference orderProductReference;
@Relation( entity = Order.class, parentColumn = "orderIdReference", entityColumn = "orderId")
Order order;
@Relation(entity = Product.class, parentColumn = "productIdReference", entityColumn = "productId")
Product product;
}
- 即这是问题的第 2 部分和第 3 部分
AllDao.java(所有 Dao 的组合为了方便)
@Dao
public interface AllDao {
@Insert
long insertCustomer(Customer customer);
@Insert
long insertProduct(Product product);
@Insert
long insertOrder(Order order);
@Insert
long insertProductInOrder(OrderProductReference orderProductReference);
@Transaction
@Query("SELECT * FROM OrderProductReference")
List<OrderWithProduct> getAllOrdersWithProducts();
@Query("SELECT * FROM Customer WHERE customerId = :customerId")
Customer getCustomerById(long customerId);
}
数据库.java
@androidx.room.Database(entities = {Customer.class,Product.class,Order.class, OrderProductReference.class}, version = 1)
public abstract class Database extends RoomDatabase {
abstract AllDao allDao();
}
并将它们结合在一起并进行演示
主 Activity .java
public class MainActivity extends AppCompatActivity {
Database database;
AllDao allDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
database = Room.databaseBuilder(this,Database.class,"mydatabase")
.allowMainThreadQueries()
.build();
allDao = database.allDao();
long custid_fred = allDao.insertCustomer(new Customer("Fred"));
long custid_mary = allDao.insertCustomer(new Customer("Mary"));
long prod_x = allDao.insertProduct(new Product("X"));
long prod_y = allDao.insertProduct(new Product("Y"));
long prod_z = allDao.insertProduct(new Product("Z"));
long order1_fred = allDao.insertOrder(new Order(custid_fred));
long order2_fred = allDao.insertOrder(new Order(custid_fred));
long order1_mary = allDao.insertOrder(new Order(custid_mary));
long opr_ord1_prdx_fred = allDao.insertProductInOrder(new OrderProductReference(order1_fred,prod_x));
long opr_ord1_prdz_fred = allDao.insertProductInOrder(new OrderProductReference(order1_fred,prod_z));
long opr_ord1_prdy_mary = allDao.insertProductInOrder(new OrderProductReference(order1_mary,prod_y));
long opr_ord2_prdy_fred = allDao.insertProductInOrder(new OrderProductReference(order2_fred,prod_y));
List<OrderWithProduct> orderWithProducts = allDao.getAllOrdersWithProducts();
for (OrderWithProduct owp: orderWithProducts) {
Customer currentCustomer = allDao.getCustomerById(owp.order.getCustomerReference());
Order currentOrder = owp.order;
Product currentProduct = owp.product;
Log.d("DBINFO",
"Customer = " + currentCustomer.getCustomerName() +
" Order = " + currentOrder.getOrderId() +
" Product = " + currentProduct.getProductName()
);
}
/*##### INSERT INVALID FOREIGN KEY #####*/
long ooops = allDao.insertOrder(new Order(1000 /*<<<<<<<<<< NOT A CUSTOMER ID */));
}
}
- 注意最后一行将打破外键规则(但在添加数据和提取数据之后)
- 也不是为了演示的方便/简洁而使用
allowMainThreadQueries()
。
以上结果:-
2019-12-31 23:51:56.715 D/DBINFO: Customer = Fred Order = 1 Product = X
2019-12-31 23:51:56.716 D/DBINFO: Customer = Fred Order = 1 Product = Z
2019-12-31 23:51:56.717 D/DBINFO: Customer = Mary Order = 3 Product = Y
2019-12-31 23:51:56.718 D/DBINFO: Customer = Fred Order = 2 Product = Y
2019-12-31 23:51:56.719 D/AndroidRuntime: Shutting down VM
2019-12-31 23:51:56.721 E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so59542439roomcustomerorderproducts, PID: 28703
java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.....MainActivity}: android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:879)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:88)
at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.java:51)
at androidx.room.EntityInsertionAdapter.insertAndReturnId(EntityInsertionAdapter.java:114)
at a.a.so59542439roomcustomerorderproducts.AllDao_Impl.insertOrder(AllDao_Impl.java:139)
at a.a.so59542439roomcustomerorderproducts.MainActivity.onCreate(MainActivity.java:53)
at android.app.Activity.performCreate(Activity.java:7802)
at android.app.Activity.performCreate(Activity.java:7791)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
2019-12-31 23:51:56.742 I/Process: Sending signal. PID: 28703 SIG: 9
关于android - 关于 Room DB 中的关系,我应该采用哪种方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59542439/