使用UUID作为Android Room中的主键

13
我需要将我的Android移动应用程序与云中的多租户数据库同步。我认为最好的方法是在我的表的主键中使用UUID。我刚开始使用Room,想知道在哪里/如何实现这个,并寻找1)在Room中使用UUID作为PK的意见和2)我将在哪里实施这个(在模型初始化ID列,因为这是唯一使用@NonNull的方法)?
更新
我使用了INT,但不确定这是否是正确的方法。我看到了来自Android开发者网站的一个example,其中显示使用了uuid。当然会帮助我的云同步例程,但会牺牲速度和空间。我考虑采用混合方法,其中使用INT作为PK,但使用UUID作为附加字段。这样,我就不必为FK复制每个UUID。我进行了一些建模,看起来混合方法的大小将是完整UUID方法的50%(仅INT将是25%)。
有人遇到过同样的问题吗?您选择了方向(UUID,INT或混合),后悔了还是效果很好?

1
UUID通常可以作为主键使用。但是,为什么要使用UUID而不是更简单(且更小)的自增数字呢? - Tim Biegeleisen
2
当我将数据同步回多租户数据库中的云端时,它会确保没有冲突。因此,如果我们在手机上有一个Project表,在云端也有一个Project表,那么ID永远不会与另一个人的Project表PK相似。如果我在本地设备上使用int作为PK,那么在云端我需要一个不同的PK。我想如果本地使用UUID作为PK,那就太容易了。我不必担心多租户环境。 - lcj
2个回答

4
  1. 使用UUID作为主键而不是int的原因有几个,在分布式应用程序中,例如许多人使用多个设备编辑一个文档,并且您将记录每个人的编辑日志以进行版本控制功能。如果您的应用程序是集中式(大多数情况下),最好使用int,或者从服务器请求主键(可以是int、long或其他)。如果必须使用UUID,则使用它。否则,int或long是更好的选择。
  2. 最后,我将附上在Android Room中使用UUID作为主键的示例。
  3. UUID的存储空间和计算速度不应该是主要考虑因素,除非您遇到了这些问题(不需要过度优化)。例如:图片的存储空间可能约为1MB,UUID以utf-8格式以字符串形式保存在数据库中,最多占用144字节(int为4字节),约为图片大小的1/7000。在服务器端,不建议使用UUID作为主键,因为服务器同时为成千上万的用户提供服务,并且会有许多查询同时发生。尽管手机CPU远比服务器CPU弱,但它仅为一位用户提供服务。同样,在必须使用UUID的场景中,没有必要预先考虑优化问题。

Book.class

@Entity(tableName = "books")
public class Book{
@PrimaryKey
@NonNull
private UUID id;
private String title;
private Date date;

public Book() {
    this.id = UUID.randomUUID();
    date = new Date();
}

public UUID getId() {
    return id;
}

public void setId(UUID id) {
    this.id = id;
}

public String getTitle() {
    return title;
}

public void setTitle(String title) {
    this.title = title;
}

public Date getDate() {
    return date;
}

public void setDate(Date date) {
    this.date = date;
}

}

AppDatabase.class

@Database(entities = {Book.class}, version = 1, exportSchema = false)
@TypeConverters({UUIDConverter.class, DateConverter.class})
public abstract class AppDatabase extends RoomDatabase {
    private static AppDatabase INSTANCE;
    private static final String DATABASE_NAME = "BookDatabase";

    public abstract BookDao bookDao();

    public static void init(Context context) {
        if (INSTANCE == null) {
            INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME).build();
        }
    }

    public static AppDatabase getINSTANCE() {
        if (INSTANCE == null) throw new IllegalStateException("init in MyApplication.class");
        return INSTANCE;
    }
}

转换器: 转换器将sqlite不支持的uuid或其他格式转换为支持的类型。 编辑: 从版本2.4.0-alpha05开始,默认的UUID转换器使用字节数组而非字符串。以下更改链接可用于迁移。

public class UUIDConverter {

    @TypeConverter
    public static String fromUUID(UUID uuid) {
        return uuid.toString();
    }

    @TypeConverter
    public static UUID uuidFromString(String string) {
        return UUID.fromString(string);
    }
}

public class DateConverter {

    @TypeConverter
    public static long timestampFromDate(Date date) {
        return date.getTime();
    }

    @TypeConverter
    public static Date dateFromTimestamp(long timestamp) {
        return new Date(timestamp);
    }
}

这非常有帮助。谢谢你。使用INT在外键引用中确实变得具有挑战性。如果不使用UUID,最终会出现大量的查找同步。这段代码非常有帮助和清晰易懂。谢谢你。你在生产环境中使用过吗? - lcj
当你想要获取id时,在Android Studio中会发生什么?通常在Android中插入数据时,你会得到rowid。那么在插入时如何获取uuid呢? - lcj
1
我很高兴我的回答对您有帮助。当然,我们在生产环境中使用UUID。我们的应用程序在许多地方使用UUID,例如某些下载资源的标识符。在我看来,我们项目中的一些地方甚至有点滥用UUID。例如,在许多地方自动递增int更好,但我们仍然使用UUID。我主要负责Android客户端的工作,至少目前为止没有发现由UUID引起的性能问题。服务器端不是我的责任,但我没有听说过由UUID引起的明显性能问题。@lcj - zz-m
自动递增的整数是由SQLite创建并返回给您的代码。UUID是由您创建并保存到SQLite中的。在将其保存到数据库之前,您已经知道它。或者它是由服务器端创建的。然后,您将创建一个Web API来获取UUID并将其保存到SQLite中。例如,从服务器获取本周上传的最新10本书。服务器返回10个简单数据,包括10个UUID和书籍摘要。然后,您可以使用UUID从服务器获取每本书的详细数据。希望这能帮助您。@lcj - zz-m
我从整数开始,并一直使用它们,因为我担心外键和支持它们的UUID的增加。我的数据库中有很多FK。我真的不知道这是否是正确的选择,但从服务器端来看,似乎GUID不是一个好的方法。除了FK之外,人们似乎在性能和页面大小方面遇到了挑战。我确实在某些表上使用GUID来唯一标识记录,但不作为PK。这种方法的缺点是需要进行大量查找以确保我有正确的记录,我真的不喜欢这样,但是... - lcj

-3

UUID比使用行ID的别名更困难且效率更低。您只需使用 column_name INTEGER PRIMARY KEY 声明列,列就是行ID的别名。

当未提供值时,SQLite 会生成一个整数值。该值可以高达9223372036854775807(也可以使用负值,这样就有两倍的可能性)。

使用行ID可能快2倍。

您可以分配一个整数值。

云中的唯一性问题。如果应用程序没有指定值,则没有问题。

如果需要随机性,则如果未使用自动生成(在实体中仅使用Long而不是long的@PrimaryKey),则SQlite将生成唯一的随机值,并使用行ID 9223372036854775807(当达到此值时,SQLite会找到随机值)。

如果需要正负随机性,则除了添加带有9223372036854775807的行之外,还要添加负值。

以下是如何在Room中简单实现:

实体如下:

@Entity
public class RandomId {

    @PrimaryKey
    Long randomId;
    String name;

    public RandomId(){}

    public Long getRandomId() {
        return randomId;
    }

    public void setRandomId(Long randomId) {
        this.randomId = randomId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

道力克

@Dao
public interface RandomIdDao {

    @Insert
    long insert(RandomId randomId);

    @Query("SELECT * FROM randomid ORDER BY name")
    List<RandomId> getAll();
}

@Database(version = 1,entities = {RandomId.class})
public abstract class RandomIdDatabase extends RoomDatabase {

    abstract RandomIdDao randomIdDao();
}

类似活动

public class MainActivity extends AppCompatActivity {

    RandomIdDatabase randomIdDatabase;

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

        randomIdDatabase = Room.databaseBuilder(this,RandomIdDatabase.class,"randomiddb")
                .allowMainThreadQueries()
                .addCallback(new RoomDatabase.Callback() {
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        super.onCreate(db);
                        /* SETUP FOR positive and negative randomids */
                        ContentValues cv = new ContentValues();
                        cv.put("randomId",9223372036854775807L);
                        db.insert("RandomID", OnConflictStrategy.IGNORE,cv);
                        cv.clear();
                        cv.put("RandomId",-9223372036854775808L);
                        db.insert("RandomID", OnConflictStrategy.IGNORE,cv);
                    }
                })
                .build();
        randomIdDatabase.getOpenHelper().getWritableDatabase().beginTransaction();
        RandomId r = new RandomId();
        for (int i = 0; i < 1000; i++) {
            r.setRandomId(null);
            r.setName("name" + String.valueOf(i));
            randomIdDatabase.randomIdDao().insert(r);
        }
        randomIdDatabase.getOpenHelper().getWritableDatabase().setTransactionSuccessful();
        randomIdDatabase.getOpenHelper().getWritableDatabase().endTransaction();
        List<RandomId> randomIds = randomIdDatabase.randomIdDao().getAll();
        for (RandomId randomId : randomIds) {
            Log.d("IDDATA","name is " + randomId.getName() + " ID is " + randomId.getRandomId());
        }
    }
}

运行如此顺畅

D/IDDATA: name is null ID is -9223372036854775808
D/IDDATA: name is null ID is 9223372036854775807
D/IDDATA: name is name0 ID is 4107292993476813590
D/IDDATA: name is name1 ID is 22838362508669948
D/IDDATA: name is name10 ID is 3903077096050951194
D/IDDATA: name is name100 ID is 948211817704090341
D/IDDATA: name is name101 ID is 806075887185875254
D/IDDATA: name is name102 ID is 3204631877439831564
D/IDDATA: name is name103 ID is 1003770814770374644
D/IDDATA: name is name104 ID is 4116052162843098839
D/IDDATA: name is name105 ID is 3379736427023998848
D/IDDATA: name is name106 ID is 3597159533635558763
D/IDDATA: name is name107 ID is 3199952401843656135
D/IDDATA: name is name108 ID is 474496762820166420
D/IDDATA: name is name109 ID is 486041123921960369
D/IDDATA: name is name11 ID is 1162315037045732434
D/IDDATA: name is name110 ID is 2807178719405346105
D/IDDATA: name is name111 ID is 3643686187723794393
D/IDDATA: name is name112 ID is 4551641574908625814
D/IDDATA: name is name113 ID is 482870610003853013
D/IDDATA: name is name114 ID is 3865465837800377065
D/IDDATA: name is name115 ID is 2934767897308599664
D/IDDATA: name is name116 ID is 4473965121303816092
D/IDDATA: name is name117 ID is 2244323676217441072
D/IDDATA: name is name118 ID is 911318293674111436
D/IDDATA: name is name119 ID is 1434915280093930897
D/IDDATA: name is name12 ID is 2661856341619735016
........

1
这很有帮助。谢谢你。我没有想到这一点。我仍然担心碰撞问题,特别是在多租户环境中,人们可能会对自己的数据敏感。关于活动中的工作,我认为这将被封装在实体类本身中,因此工作只需要完成一次。当使用UUID时,我考虑了这一点,要么1)在实例化类时生成它(解决@NonNull问题),要么2)在插入之前设置它。 - lcj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接