如何构建嵌套一对一关系的实体,以便您可以在 Android Room 中获得所有查询?

时间:2021-04-07 19:21:12

标签: android android-room

我有用户实体。

import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.PrimaryKey;

import com.google.gson.annotations.SerializedName;

@Entity(foreignKeys = {
        @ForeignKey(entity = Address.class, parentColumns = "addressId", childColumns = "fk_addressId", onDelete = ForeignKey.CASCADE),
        @ForeignKey(entity = Company.class, parentColumns = "companyId", childColumns = "fk_companyId", onDelete = ForeignKey.CASCADE)
})
public class User {

    @PrimaryKey(autoGenerate = true)
    public int userId;

    public String name;
    public String username;
    public String email;


    public int fk_addressId;
    public String phone;
    public String website;
    public int fk_companyId;

    public User(@NonNull String name, @NonNull String username, @NonNull String email, int fk_addressId,
                @NonNull String phone, @NonNull String website, int fk_companyId) {
        this.name = name;
        this.username = username;
        this.email = email;
        this.fk_addressId = fk_addressId;
        this.phone = phone;
        this.website = website;
        this.fk_companyId = fk_companyId;
    }
}

用户有两个外键,分别指向 Address 和 Company 实体。用户与地址、用户与公司之间是一对一的关系。

地址有一个外键指向 Geo(用于地理定位)。 Address 和 Geo 之间存在一对一的关系。

我想使用 Android 中的 Room 在一次查询中获取用户的地址、地理位置和公司信息。

我应该如何构建关系类来做到这一点?

此链接中有信息: https://developer.android.com/training/data-storage/room/relationships#java

但在这种情况下,用户实体有两个外键,一个指向地址,另一个指向公司。 (地址是指地理)。

我应该如何构建关系类以使用 Android 中的 Room 在一次查询中获取用户的地址、地理位置和公司信息?

这是地址实体:

import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.PrimaryKey;

import com.google.gson.annotations.SerializedName;

@Entity(foreignKeys = {
        @ForeignKey(entity = Geo.class, parentColumns = "geoId", childColumns = "fk_geoId", onDelete = ForeignKey.CASCADE)
})
public class Address {

    @PrimaryKey(autoGenerate = true)
    public int addressId;

    public String street;
    public String suite;
    public String city;
    public String zipCode;

    public int fk_geoId;

    public Address(@NonNull String street, @NonNull String suite, @NonNull String city,
                   @NonNull String zipCode, int fk_geoId) {
        this.street = street;
        this.suite = suite;
        this.city = city;
        this.zipCode = zipCode;
        this.fk_geoId = fk_geoId;
    }
}

这是地理实体:

import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity
public class Geo {

    @PrimaryKey(autoGenerate = true)
    public int geoId;
    
    public double lat;
    public double lng;

    public Geo(@NonNull double lat, @NonNull double lng) {
        this.lat = lat;
        this.lng = lng;
    }
}

这里是公司实体:

import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity
public class Company {
    @PrimaryKey(autoGenerate = true)
    public int companyId;

    public String name;
    public String catchPhrase;
    public String bs;

    public Company(@NonNull String name, @NonNull String catchPhrase, @NonNull String bs) {
        this.name = name;
        this.catchPhrase = catchPhrase;
        this.bs = bs;
    }
}

我应该如何构建关系类以使用 Android 中的 Room 在一次查询中获取用户的地址、地理位置和公司信息?

1 个答案:

答案 0 :(得分:1)

首先,我建议更改一些列名称以使其独一无二,例如

  • name 类/实体中的 Company 更改为 companyName

    • 这是为了消除name类/实体中的User的歧义
    • 另一种方法是使用 @Embeddedprefix 参数,例如用于组合的 POJO 中的 @Embedded(prefix = "cmpny_")(请参阅下面的 UserWithCompanyWithAddressWithGeo)

所以 Company 可能是 :-

@Entity
public class Company {
    @PrimaryKey(autoGenerate = true)
    public int companyId;

    public String companyName;
    public String catchPhrase;
    public String bs;

    public Company(@NonNull String companyName, @NonNull String catchPhrase, @NonNull String bs) {
        this.companyName = companyName;
        this.catchPhrase = catchPhrase;
        this.bs = bs;
    }
}

其次,您并不需要单独的表来建立一对一的关系,您拥有事物的方式可以是一对多的(1 家公司可以拥有多个用户,1 个地址可以拥有多个用户,1 个地理区域可以拥有)许多地址)。请参阅答案末尾的其他内容。

接下来添加一个类 (POJO) 以获取所有组合数据,例如UserWithCompanyWithAddressWithGeo。使用此方法,JOINs 将用于查询中,因此只需按照 :-

嵌入所有实体(用户、公司、地址和地理)
public class UserWithCompanyWithAddressWithGeo {

    @Embedded
    User user;
    @Embedded
    Company company;
    @Embedded
    Address address;
    @Embedded
    Geo geo;

    public UserWithCompanyWithAddressWithGeo(){}

}

如前所述,查询中的 JOINS 将用于建立关系,因此在 a/the Dao 中添加以下内容:-

@Query("SELECT * FROM user JOIN company ON user.fk_companyId = companyId JOIN address ON user.fk_addressId = address.addressId JOIN geo ON address.fk_geoId = geo.geoId")
List<UserWithCompanyWithAddressWithGeo> getUserWithCompanyWithAddressWithGeo();

然后您可以使用 getUserWithCompanyWithAddressWithGeo() 获取所有用户的公司、地址和地理位置。

工作示例

使用上述内容,然后使用以下内容:-

public class MainActivity extends AppCompatActivity {

    final String TAG = "MYDBINFO";
    MyDatabase db;
    AllDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        db = Room.databaseBuilder(this,MyDatabase.class,"mydb")
                .allowMainThreadQueries()
                .build();
        dao = db.getAllDao();
        int geo1Id = (int) dao.insertGeo(new Geo(52.5,72.3));
        int geo2Id = (int) dao.insertGeo(new Geo(33.3,66.6));
        int address1Id = (int) dao.insertAddress(new Address("Street1","suite1","city1","1234",geo1Id));
        int address2Id = (int) dao.insertAddress(new Address("Street2","Suite2","City2","4321",geo2Id));
        int company1Id = (int) dao.insertCompany(new Company("Company1","Catchphrase1","bs1"));
        int company2Id = (int) dao.insertCompany(new Company("Company2","Catchphrase2","bs2"));
        int user1 = (int) dao.insertUser(new User("Name1","UserName1","Email1",address1Id,"Phone1","Website1",company1Id));
        int user2 = (int) dao.insertUser(new User("Name2","UserName2","Email1",address2Id,"Phone2","Website2",company2Id));
        List<UserWithCompanyWithAddressWithGeo> userWithCompanyWithAddressWithGeos = dao.getUserWithCompanyWithAddressWithGeo();
        for(UserWithCompanyWithAddressWithGeo userWithCompanyWithAddressWithGeo : userWithCompanyWithAddressWithGeos) {
            LogUserWithCompanyWithAddress(userWithCompanyWithAddressWithGeo);
        }
    }

    private void LogUserWithCompanyWithAddress(UserWithCompanyWithAddressWithGeo uwcwa) {
        Log.d(TAG,
                "User-Name = " + uwcwa.user.name +
                        " User-Email = " + uwcwa.user.email +
                        "\n\tCompany - Name = " + uwcwa.company.companyName + " Company - Catchphrase = " + uwcwa.company.catchPhrase +
                        "\n\tAddress - Street = " + uwcwa.address.street + " Address - City = " + uwcwa.address.city +
                        "\n\t\tGeo - Lat = " + uwcwa.geo.lat + " Geo - Long = " + uwcwa.geo.lng
        );
    }
}
  • 以上仅用于测试,不可重新运行。
  • 为了方便和简洁,它还在主线程上运行。

日志中的结果(第一次运行):-

2021-04-08 06:58:54.635 D/MYDBINFO: User-Name = Name1 User-Email = Email1
        Company - Name = Company1 Company - Catchphrase = Catchphrase1
        Address - Street = Street1 Address - City = city1
            Geo - Lat = 52.5 Geo - Long = 72.3
2021-04-08 06:58:54.636 D/MYDBINFO: User-Name = Name2 User-Email = Email1
        Company - Name = Company2 Company - Catchphrase = Catchphrase2
        Address - Street = Street2 Address - City = City2
            Geo - Lat = 33.3 Geo - Long = 66.6

附加

Re 1-Many 关系考虑以下已添加到用于测试的活动中(在其他插入之后但在提取之前)

int userx = (int) dao.insertUser(new User("NameX","UserX","EmailX",address1Id,"PhoneX","WebsiteX",company2Id));
  • 这是使用与用户 1 相同的地址,使用与用户 2 相同的公司。运行结果

:-

2021-04-08 07:34:59.851 12295-12295/a.a.so66992840javaroomonetoones D/MYDBINFO: User-Name = Name1 User-Email = Email1
        Company - Name = Company1 Company - Catchphrase = Catchphrase1
        Address - Street = Street1 Address - City = city1
            Geo - Lat = 52.5 Geo - Long = 72.3
2021-04-08 07:34:59.851 12295-12295/a.a.so66992840javaroomonetoones D/MYDBINFO: User-Name = Name2 User-Email = Email1
        Company - Name = Company2 Company - Catchphrase = Catchphrase2
        Address - Street = Street2 Address - City = City2
            Geo - Lat = 33.3 Geo - Long = 66.6
2021-04-08 07:34:59.851 12295-12295/a.a.so66992840javaroomonetoones D/MYDBINFO: User-Name = NameX User-Email = EmailX
        Company - Name = Company2 Company - Catchphrase = Catchphrase2
        Address - Street = Street1 Address - City = city1
            Geo - Lat = 52.5 Geo - Long = 72.3

替代方法 - 使用@Relation

当按照初始方法嵌入实体时,查询需要使用 JOIN。但是,您可以避免使用 JOIN 并通过使用 @Relation 注释让空间有效地构建 JOIN。但是,当使用@Relation 时,会检索嵌入实体的列表/数组。

对于 3 级层次结构(用户 -> 地址 - 地理),您似乎需要 2 个级别的 @Relation,因此需要 2 个 POJO 类。

第一个(层次结构中的最低层)(地址-地理)可以是 AddressWithGeo :-

public class AddressWithGeo {

    @Embedded
    Address address;
    @Relation(entity = Geo.class,entityColumn = "geoId",parentColumn = "fk_geoId")
    List<Geo> geos;

    public AddressWithGeo(){}
}

第二个(使用-> 地址和用户 -> 公司)可以是 UserWithCompanyWithAddressAndGeo :-

public class UserWithCompanyWithAddressAndGeo {
    @Embedded
    User user;
    @Relation(entity = Company.class,entityColumn = "companyId",parentColumn = "fk_companyId")
    List<Company> company;
    @Relation(entity = Address.class,entityColumn = "addressId",parentColumn = "fk_addressId")
    List<AddressWithGeo> addressWithGeos;

    public UserWithCompanyWithAddressAndGeo(){}
}
  • 即而不是@Embedded @Relation 用于较低级别的类,
  • 另请注意,由于 AddressWithGeo 不是实体而是 POJO,因此关系与地址表有关。

要使用上述内容,您至少需要一个 Dao @Query 用于更高级别。但是,以下是两者的@Queries(因此允许使用 Geo 提取地址):-

@Transaction
@Query("SELECT * FROM address")
List<AddressWithGeo> getAddressWithGeos();
@Transaction
@Query("SELECT * FROM user")
List<UserWithCompanyWithAddressAndGeo> getUserWithCompanyWithAddressAndGeo();

以下内容已添加到 MainActivity :-

    List<AddressWithGeo> addressWithGeos = dao.getAddressWithGeos(); // shows that AddressWithGeo can be used on it's own
    List<UserWithCompanyWithAddressAndGeo> userWithCompanyWithAddressAndGeos = dao.getUserWithCompanyWithAddressAndGeo();
    for (UserWithCompanyWithAddressAndGeo userWithCompanyWithAddressAndGeo: userWithCompanyWithAddressAndGeos) {
        logUser(userWithCompanyWithAddressAndGeo.user);
        for(Company cmpny: userWithCompanyWithAddressAndGeo.company) {
            logCompany(cmpny);
        }
        for(AddressWithGeo awg: userWithCompanyWithAddressAndGeo.addressWithGeos) {
            logAddress(awg.address);
            for(Geo geo: awg.geos) {
                logGeo(geo);
            }
        }
    }
  • 注意遍历列表(如果您 100% 确定至少存在一个且只有一个公司、地址和/或地理区域,那么您可以访问元素 0,而无需遍历列表)。

连同以下方法(使将输出写入日志更简单):-

private void logUser(User usr) {
    Log.d(TAG,"Name = " + usr.name + " Username = " + usr.username + " Email = " + usr.email + " Phone = " + usr.phone + " Website = " + usr.website);
}
private void logCompany(Company cmpny) {
    Log.d(TAG,"\tName = " + cmpny.companyName + " CatchPhrase = " + cmpny.catchPhrase + " BS = " + cmpny.bs);
}
private void logAddress(Address addr) {
    Log.d(TAG,"\tStreet = " + addr.street + " Suite = " + addr.suite + " City = " + addr.city + " Zipcode = " + addr.zipCode);
}
private void logGeo(Geo geo) {
    Log.d(TAG,"\t\t Lat = " + geo.lat + " Long = " + geo.lng);
}

运行时的结果(除了前面的)是:-

2021-04-08 18:26:04.595 D/MYDBINFO: Name = Name1 Username = UserName1 Email = Email1 Phone = Phone1 Website = Website1
2021-04-08 18:26:04.595 D/MYDBINFO:     Name = Company1 CatchPhrase = Catchphrase1 BS = bs1
2021-04-08 18:26:04.595 D/MYDBINFO:     Street = Street1 Suite = suite1 City = city1 Zipcode = 1234
2021-04-08 18:26:04.595 D/MYDBINFO:          Lat = 52.5 Long = 72.3
2021-04-08 18:26:04.595 D/MYDBINFO: Name = Name2 Username = UserName2 Email = Email1 Phone = Phone2 Website = Website2
2021-04-08 18:26:04.595 D/MYDBINFO:     Name = Company2 CatchPhrase = Catchphrase2 BS = bs2
2021-04-08 18:26:04.595 D/MYDBINFO:     Street = Street2 Suite = Suite2 City = City2 Zipcode = 4321
2021-04-08 18:26:04.596 D/MYDBINFO:          Lat = 33.3 Long = 66.6
2021-04-08 18:26:04.596 D/MYDBINFO: Name = NameX Username = UserX Email = EmailX Phone = PhoneX Website = WebsiteX
2021-04-08 18:26:04.596 D/MYDBINFO:     Name = Company2 CatchPhrase = Catchphrase2 BS = bs2
2021-04-08 18:26:04.596 D/MYDBINFO:     Street = Street1 Suite = suite1 City = city1 Zipcode = 1234
2021-04-08 18:26:04.596 D/MYDBINFO:          Lat = 52.5 Long = 72.3