【问题标题】:Issue Prepopulate Room Database - Database Inspector empty问题 Prepopulate Room Database - Database Inspector 为空
【发布时间】:2021-08-15 11:50:32
【问题描述】:

我是 Android 开发的新手,我正在尝试开发一个非常简单的应用程序。 我想创建一个包含一个表的预填充数据库。该表是关于用户的,它只有三列 id、name 和 professional。 该数据库的唯一用途是通过每个用户的姓名进行搜索并找到他们的职业。 所以我只需要用一些数据预填充它。

我的问题是,当我运行数据库时什么都没有发生,甚至没有创建数据库。在数据库检查器中看不到任何内容

正如我从 Room 数据库 https://developer.android.com/training/data-storage/room/prepopulate 的文档中看到的那样 我只需要添加以下代码

Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
    .createFromAsset("database/myapp.db")
    .build();

所以我在 SqliteStudio 中创建了我的小型数据库,现在我尝试使用 Android 中的 Room 数据库复制它。下面你可以看到我的桌子的截图 users_table from sqliteStudio

依赖关系

// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
implementation 'androidx.wear:wear:1.1.0'
annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

// Lifecycle components
implementation "androidx.lifecycle:lifecycle-viewmodel:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$rootProject.lifecycleVersion"

implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
compileOnly 'com.google.android.wearable:wearable:2.8.1'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

分级

ext {
    appCompatVersion = '1.3.0'
    constraintLayoutVersion = '2.0.4'
    coreTestingVersion = '2.1.0'
    lifecycleVersion = '2.3.1'
    materialVersion = '1.3.0'
    roomVersion = '2.3.0'
    // testing
    junitVersion = '4.13.2'
    espressoVersion = '3.1.0'
    androidxJunitVersion = '1.1.2'
}

您还可以看到我的 Dao、DatabaseClass、Entity、Repository、ViewModel 中的代码

用户

@Entity(tableName = "users_table")
public class User {

    @PrimaryKey(autoGenerate = true)
    @NonNull
    private int id;

    @ColumnInfo(name = "name")
    @NonNull
    private String name;

    @ColumnInfo(name = "profession")
    @NonNull
    private String profession;

    public User(int id, @NonNull String name, @NonNull String profession) {
        this.id = id;
        this.name = name;
        this.profession = profession;
    }

所有的 getter 和 setter 也都存在

UserDao

@Dao
public interface UserDao {

    @Query("SELECT * FROM users_table")
    LiveData<List<User>> getAll();

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    void insert(User user);

    @Delete
    void delete(User user);

    @Query("DELETE FROM users_table")
    void deleteAll();
}

用户数据库

@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class UserDatabase extends RoomDatabase {
    public abstract UserDao userDao();
    private static volatile UserDatabase INSTANCE;

    private static final int NUM_OF_THREADS = 4;


    public static final ExecutorService databaseWriteExecutor
            = Executors.newFixedThreadPool(NUM_OF_THREADS);

    public static UserDatabase getDatabase(final Context context){
        if (INSTANCE == null){
            synchronized (UserDatabase.class){
                if (INSTANCE == null){
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            UserDatabase.class, "user.db")
                            .createFromAsset("users.db")
                            .build();
                }
            }
        }
        return INSTANCE;
    }
}

用户存储库

public class UserRepository {


    private UserDao userDao;
    private LiveData<List<User>> allUsers;

    public UserRepository(Application application) {
        UserDatabase db = UserDatabase.getDatabase(application);
        userDao = db.userDao();
        allUsers = userDao.getAll();

    }

    public LiveData<List<User>> getAllData() { return allUsers; }

    public void insert(User user){
        UserDatabase.databaseWriteExecutor.execute(() -> {
            userDao.insert(user);
        });
    }
    public void deleteAll(){
        UserDatabase.databaseWriteExecutor.execute(() -> {
            userDao.deleteAll();
        });
    }
}

用户视图模型

public class UserViewModel extends AndroidViewModel {

    public static UserRepository repository;
    public final LiveData<List<User>> allUsers;


    public UserViewModel(@NonNull Application application) {
        super(application);
        repository = new UserRepository(application);
        allUsers = repository.getAllData();
    }

    public LiveData<List<User>> getAllUsers() { return allUsers; }
    public static void insert(User user) { repository.insert(user); }
}

【问题讨论】:

  • 是否有任何代码可以访问(或尝试)数据库?只有在进行此类尝试时,Room 才会真正打开数据库。只有在第一次打开数据库时才会进行数据库文件的复制。
  • 你好@MikeT,谢谢你的回复。以上是我在项目中的确切代码。我还没有改变任何东西。当我完成代码后,我编译了项目,并且我正在将房间数据库显示给 Database Inspector ,但什么也没发生。然而类 UserDao_ImplUserDatabase_Impl 已经出现...我应该添加其他东西来初始化房间数据库吗?
  • 添加了一个应该澄清访问数据库的答案。 *_Impl 是根据注解生成的底层代码。这是预期的和正常的,实际上只是证明注释已被应用。注意答案是对一个常见问题的猜测。但是,它确实使用了您的代码,因此确认代码没有问题。

标签: java android android-studio android-sqlite android-room


【解决方案1】:

这只是您在实际访问数据库之前尝试检查数据库是否存在的猜测。但是,这是一个相对常见的问题。它还假设您使用的是兼容设备(例如,数据库检查器必须是 Android API 26+)。

当检索到数据库实例时,在您的情况下,在使用UserDatabase mydatabase = UserDatabase.getDatabase(); 的活动/片段中实际上并未打开数据库,而是仅在尝试实际提取/插入/更新/删除数据时打开数据库,在您的情况下,将 users.db 文件/资产从包复制到设备上的最终位置。因此,除非尝试访问数据库,否则 Database Inspector 和 Device File Explorer 都不会显示任何内容。

您可以通过在UserDatabase 类中的getDatabase 方法中添加一行来临时(或永久)强制打开。例如你可以使用:-

public abstract class UserDatabase extends RoomDatabase {
    public abstract UserDao userDao();
    private static volatile UserDatabase INSTANCE;

    private static final int NUM_OF_THREADS = 4;


    public static final ExecutorService databaseWriteExecutor
            = Executors.newFixedThreadPool(NUM_OF_THREADS);

    public static UserDatabase getDatabase(final Context context){
        if (INSTANCE == null){
            synchronized (UserDatabase.class){
                if (INSTANCE == null){
                    INSTANCE = Room.databaseBuilder(context,
                            UserDatabase.class, "user.db")
                            .allowMainThreadQueries() //NOTE ADDED for convenience of demo
                            .createFromAsset("users.db")
                            .build();
                    /*<<<<<<<<<< ADDED to FORCE an open of the database >>>>>>>>>>*/
                    SupportSQLiteDatabase sdb = INSTANCE.getOpenHelper().getWritableDatabase();
                }
            }
        }
        return INSTANCE;
    }
}

演示

以下是演示。 它使用您在问题中发布的代码。但是有以下变化:-

  • Getter 和 Setter 已添加到 User 类中。
  • Room.databaseBuilder 的上下文使用传递的context 而不是使用上下文来获取上下文(这不是问题,因为需要上下文的唯一原因是获取数据库的默认路径) em>。
  • 已添加.allowMainThreadQueries 以简化演示(作为整个过程的证明)。

首先使用 SQLite Studio 创建了一个数据库,其中填充了 3 行,如下所示:-

结构是:-

数据库文件被复制(在 SQLite Studio 中关闭连接后,再次打开连接,然后再次关闭以验证数据库是否正常)(并重命名为 users.db)到项目的资产文件夹(创建文件夹后),按照:-

一个活动 MainActivity 最初是用来证明这一点的:-

public class MainActivity extends AppCompatActivity {

    UserDatabase db;
    UserDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        db = UserDatabase.getDatabase(this); // Does not open the database
        dao = db.userDao(); // Does not open the database
    }
}

运行 1

上面的代码是从 Android Studio 运行的,结果如下(注意使用了 Device File Explorer 和 Database Inspector):-

  • 可以看出,在应用成功运行后,Database Inspector(应用检查)和设备文件资源管理器中均未显示任何内容。

  • NOTE可以看出getWritableDatabase这一行已经被注释掉了(为了演示没有)。

  • 结论仅仅获取UserDatabase的实例并获取UserDao的实例并不会导致数据库被创建。

运行 2

此运行演示了访问数据库会导致创建数据库。它使用 getAll 查询的非实时数据版本。在 UserDao 中添加了以下内容:-

@Query("SELECT * FROM users_table")
List<User> getAllUsers();

另外 MainActivity 已更改为包含(取消注释)先前注释掉的代码:-

    for (User user: dao.getAllUsers()) {
        Log.d("USERINFO","User is " + user.getName() + " profession is " + user.getProfession());
    }
  • 即实际访问数据。

现在运行时:-

并且日志包含:-

D/USERINFO: User is Fred profession is Doctor
D/USERINFO: User is Mary profession is Solicitor
D/USERINFO: User is Jane profession is Vet
  • 即数据是根据预填充的数据库。

    • 请注意,现在有 3 个文件,-wal 和 -shm 是 SQLite 处理的日志文件。
    • 请注意,您应该确保在创建资产时它们不存在。如果他们确实重复打开/关闭连接,直到他们没有。 createFromAsset 不会(我相信)处理附加/额外文件。如果它们确实存在,那么您可能会得到意想不到的结果(例如损坏、丢失数据)

运行 3

为了演示在 getDatabase 方法中强制打开,在 MainActivity 中访问数据库的代码再次按照 *Run 1@ 注释掉987654345@ 注释掉的 //SupportSQLiteDatabase sdb = INSTANCE.getOpenHelper().getWritableDatabase(); 已更改为包含在 SupportSQLiteDatabase sdb = INSTANCE.getOpenHelper().getWritableDatabase(); 重要应用程序已卸载 (一旦数据库文件存在,它将不会从再次访问资产文件夹,因此删除数据库(卸载应用程序即可))

  • 可能已经删除了数据库文件

没有要复制的资产文件

卸载应用程序并将资产文件重命名为not_the_users.db 后,结果是崩溃/异常,日志包括:-

2021-08-16 08:12:11.884 26625-26625/a.a.so68791243javaroomnodatabase E/AndroidRuntime: FATAL EXCEPTION: main
    Process: a.a.so68791243javaroomnodatabase, PID: 26625
    java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so68791243javaroomnodatabase/a.a.so68791243javaroomnodatabase.MainActivity}: java.lang.RuntimeException: Unable to copy database file.
  • 因此,这消除了无资产文件,如果您正在访问数据库(再次强制打开将确保及早检测到此类错误)。

运行 4 - 数据库不兼容

对于本次运行,users_table 表已通过将专业列的类型更改为 STRING 进行更改,并将其复制到 assets 文件夹中作为 users.db 并卸载应用程序。结果是预期的预期/发现不匹配崩溃/异常:-

2021-08-16 08:30:42.523 27239-27239/a.a.so68791243javaroomnodatabase E/AndroidRuntime: FATAL EXCEPTION: main
    Process: a.a.so68791243javaroomnodatabase, PID: 27239
    java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so68791243javaroomnodatabase/a.a.so68791243javaroomnodatabase.MainActivity}: java.lang.IllegalStateException: Pre-packaged database has an invalid schema: users_table(a.a.so68791243javaroomnodatabase.User).
     Expected:
    TableInfo{name='users_table', columns={name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, profession=Column{name='profession', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='users_table', columns={profession=Column{name='profession', type='STRING', affinity='1', notNull=true, primaryKeyPosition=0, defaultValue='null'}, name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
  • 同样,这消除了预填充数据库的典型问题如果您正在访问该数据库。

【讨论】:

  • 感谢您为这个详细的答案所做的努力。它帮助了我,我非常感谢它
猜你喜欢
  • 1970-01-01
  • 2019-02-01
  • 1970-01-01
  • 2021-07-31
  • 2020-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-10
相关资源
最近更新 更多