android - 关于 Room DB 中的关系,我应该采用哪种方法?

标签 android database android-room

我正在做一个简单的商店应用程序来了解更多关于 Android 中的 Room DB 的信息,目前我对关系和嵌套对象的最佳方法感到有点困惑。

场景:客户从商店中选择一件商品并下单。之后,数据库使用订单 ID 更新客户表,以便可以在数据库中搜索客户订单。订单表具有该特定订单中的产品 ID。在客户“帐户”页面(在应用程序内),包括产品在内的所有订单都应显示所有必要的信息(例如订单 ID、产品名称、价格、数量等)。

我画了这个草图来说明三个表:客户、订单和产品 enter image description here

问题这里的@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/

相关文章:

java - DialogFragment 中的 getContentResolver() 和 getWindow()

android - 按钮文本在android中自动更改为大写字母

html - XML 中 "Unit Separator"的最佳值是多少?

java - 如何增加从java数据库中获取特定值的时间?

mysql - 需要帮助的初学者 sql 学生

android - 如何检查 ROOM 数据库中的记录是否被删除?

Android,带有嵌入式对象列表的ROOM查询

android - 使用 Picasso 和 Glide 获取黑色 ImageView

java - 为自定义查询实现 ViewModel 和 Repository

android - 收到错误 Room 无法验证数据完整性......即使在添加 fallbackToDestructiveMigration() 之后