简体   繁体   English

发出预填充房间数据库 - 数据库检查器为空

[英]Issue Prepopulate Room Database - Database Inspector empty

I am new to Android development and I am trying to develop a very simple app.我是 Android 开发的新手,我正在尝试开发一个非常简单的应用程序。 I want to create a prepopulated database which has one table.我想创建一个有一个表的预填充数据库。 The table is about users and it has only three columns id, name, and profession.该表是关于用户的,它只有三列 id、name 和 profession。 The only usage of the database will be to search via the name of every user and find their profession.该数据库的唯一用途是通过每个用户的姓名进行搜索并找到他们的职业。 So I only need to prepopulate it with some data.所以我只需要用一些数据预先填充它。

My issue is that nothing is happening when i run the databse nothing is happening the database is not even been created.我的问题是,当我运行数据库时什么也没有发生,甚至没有创建数据库。 cant see anything in the dtabase inspector在数据库检查器中看不到任何东西

As I have read from the documentation of Room database https://developer.android.com/training/data-storage/room/prepopulate I just need to add the following code正如我从 Room 数据库https://developer.android.com/training/data-storage/room/prepopulate的文档中读到的,我只需要添加以下代码

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

So I created my small database in SqliteStudio and now I am trying to copy it with the Room database in Android. Bellow you can see a screenshot of my table users_table from sqliteStudio所以我在 SqliteStudio 中创建了我的小型数据库,现在我正在尝试将它复制到 Android 中的 Room 数据库。下面你可以看到我的表users_table 来自 sqliteStudio的屏幕截图

Dependencies依赖关系

// 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"

gradle gradle

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'
}

Bellow you can see also the code from my Dao, DatabaseClass, Entity, Repository, ViewModel在下面您还可以看到来自我的 Dao、DatabaseClass、Entity、Repository、ViewModel 的代码

User用户

@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;
    }

all getters and setters also exist所有的 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();
}

UserDatabase用户数据库

@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;
    }
}

UserRepository用户资料库

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();
        });
    }
}

UserViewModel用户视图模型

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); }
}

This is only a guess that you are trying to check to see if the database exists before actually accessing the database.这只是一个猜测,您正试图在实际访问数据库之前检查数据库是否存在。 However, it's a relatively common issue.但是,这是一个相对普遍的问题。 It also assumes that you are using a compatible device (eg must be Android API 26+ for database inspector).它还假定您使用的是兼容设备(例如,对于数据库检查器,必须是 Android API 26+)。

When a database instance is retrieved, in your case, when in an activity/fragment using UserDatabase mydatabase = UserDatabase.getDatabase();当检索数据库实例时,在您的情况下,当在活动/片段中使用UserDatabase mydatabase = UserDatabase.getDatabase(); does not actually open the database, rather it is only when an attempt is made to actually extract/insert/update/delete data that the database is opened, and in your case that the users.db file/asset is copied from the package to the final location on the device.实际上并没有打开数据库,而是只有在尝试实际提取/插入/更新/删除数据库打开的数据时,在您的情况下,users.db 文件/资产才从 package 复制到设备上的最终位置。 As such unless the attempt to access the database is made then neither Database Inspector nor Device File Explorer will show anything.因此,除非尝试访问数据库,否则数据库检查器和设备文件资源管理器都不会显示任何内容。

You can temporarily (or permanently) force an open by adding a line to the getDatabase method in the UserDatabase class. eg you could use:-您可以通过向UserDatabase class 中的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;
    }
}

Demonstration示范

The following is a demonstration.下面是一个演示。 It uses your code as posted in the question.它使用您在问题中发布的代码。 However with the following changes:-但是,进行了以下更改:-

  • Getters and Setters have been added to the User class.已将 Getters 和 Setters 添加到User class。
  • The Room.databaseBuilder's context uses the passed context rather than using the context to get the context (not an issue as only reason that a context is required is to get the default path to the database) . Room.databaseBuilder 的上下文使用传递的context而不是使用上下文来获取上下文(这不是问题,因为需要上下文的唯一原因是获取数据库的默认路径)
  • .allowMainThreadQueries has been added to simplify the demo (as proof of the entire process).添加了.allowMainThreadQueries以简化演示(作为整个过程的证明)。

First a database was created using SQLite Studio, it was populated with 3 rows, as per:-首先使用 SQLite Studio 创建了一个数据库,其中填充了 3 行,如下所示:-

  • 在此处输入图像描述

The Structure being:-结构是:-

  • 在此处输入图像描述

The database file was copied (after closing the connection in SQLite Studio, opening the connection again and then closing it again to verify that the database was as it should be) (and renamed to users.db ) to the assets folder of the project (after creating the folder), as per:-将数据库文件复制(在SQLite Studio中关闭连接后,再次打开连接然后再次关闭以验证数据库是否正确) (并重命名为users.db )到项目的资产文件夹(创建文件夹后),按照:-

在此处输入图像描述

An activity MainActivity was used to demonstrate this at first being:-活动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
    }
}

Run 1运行 1

The above was run and from Android Studio the following was the result (noting the use of both Device File Explorer and Database Inspector):-运行上面的代码,从 Android Studio 得到以下结果(注意同时使用 Device File Explorer 和 Database Inspector):-

  • 在此处输入图像描述

  • As can be seen nothing is shown in Database Inspector (App Inspection) nor in Device File Explorer after the App was successfully run.可以看出,在应用程序成功运行后,数据库检查器(应用程序检查)和设备文件资源管理器中都没有显示任何内容。

  • NOTE It can be seen that the getWritableDatabase line has been commented out (to demonstrate without).注意可以看到 getWritableDatabase 行已经被注释掉了(为了演示没有)。

  • Conclusion just getting an instance of the UserDatabase and getting an instance of the UserDao doe not result in the database being created.结论只是获取 UserDatabase 的实例和获取 UserDao 的实例不会导致创建数据库。

Run 2运行 2

This run demonstrates that accessing the database results in the database being created.此运行演示访问数据库会导致创建数据库。 It uses an non-live data version of the getAll query.它使用 getAll 查询的非实时数据版本。 In UserDao the following was added:-UserDao中添加了以下内容:-

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

Additionally MainActivity was changed to include (uncomment) the previously commented out code:-此外, MainActivity已更改为包括(取消注释)先前注释掉的代码:-

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

Now when run:-现在运行时:-

  • 在此处输入图像描述

and the log contains:-日志包含:-

D/USERINFO: User is Fred profession is Doctor
D/USERINFO: User is Mary profession is Solicitor
D/USERINFO: User is Jane profession is Vet
  • ie the data is as per the prepopulated database.即数据是按照预先填充的数据库。

    • Note that there are now 3 files, the -wal and -shm are logging files that SQLite handles.请注意,现在有 3 个文件,-wal 和 -shm 是 SQLite 处理的日志文件。
    • Note you should ensure that they DO NOT exist when creating the asset.请注意,您应该确保在创建资产时它们不存在。 If they do repeat the open/close connection until they do not.如果他们确实重复打开/关闭连接,直到他们不这样做。 The createFromAsset will not (I believe) cope with the additional/extra files. createFromAsset 不会(我相信)处理额外的/额外的文件。 If they do exist then you may get unexpected results (eg corruption, missing data)如果它们确实存在,那么您可能会得到意想不到的结果(例如损坏、丢失数据)

Run 3运行 3

To demonstrate forcing the open in the getDatabase method the code that access the database in MainActivity was again commented out as per * Run 1 and in the UserDatabase the commented out //SupportSQLiteDatabase sdb = INSTANCE.getOpenHelper().getWritableDatabase();为了演示在 getDatabase 方法中强制打开,在MainActivity中访问数据库的代码再次按照 * Run 1注释掉,在UserDatabase中注释掉//SupportSQLiteDatabase sdb = INSTANCE.getOpenHelper().getWritableDatabase(); was changed to be included an be SupportSQLiteDatabase sdb = INSTANCE.getOpenHelper().getWritableDatabase();已更改为包含一个 be SupportSQLiteDatabase sdb = INSTANCE.getOpenHelper().getWritableDatabase(); AND IMPORTANTLY the App was uninstalled (once the database file exists it will not be copied from the assets folder again, so deleting the database (which uninstalling the App will do)) .重要的是,应用程序已卸载(一旦数据库文件存在,它将不会再次从资产文件夹中复制,因此删除数据库(卸载应用程序即可))

  • could have deleted the database files可能已经删除了数据库文件

  • 在此处输入图像描述

No Asset File to be copied没有要复制的资产文件

After uninstalling the App and renaming the asset file to not_the_users.db then the result is a crash/exception and the log includes:-卸载应用程序并将资产文件重命名为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.
  • So this eliminates the no assets file, IF you are accessing the database (again the force open would ensure that such and error is detected early).因此,如果您正在访问数据库,这将消除无资产文件(再次强制打开将确保尽早检测到此类错误)。

Run 4 - Incompatible database运行 4 - 不兼容的数据库

For this run the users_table table was altered by changing the type of the profession column to STRING, copied into the assets folder as users.db and the App uninstalled.对于此运行,通过将专业列的类型更改为 STRING 来更改 users_table 表,将其作为 users.db 复制到资产文件夹中,并卸载应用程序。 The result, is the anticipated Expected/Found mismatch crash/exception as per:-结果是预期的 Expected/Found 不匹配崩溃/异常,如下所示:-

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=[]}
  • Again, this eliminates a typical issue with pre-populated database IF you are accessing the database.同样,如果您正在访问数据库,这将消除预填充数据库的典型问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM