无法将 table 迁移到 Room 执行布尔值在 Sqlite 中保存方式的错误
Can't migrate a table to Room do to an error with the way booleans are saved in Sqlite
我一直在尝试将我的应用程序迁移到 Room
。由于创建方式的原因,我无法直接迁移的特定 table。
这些字段是使用数据类型 BOOL
和 BYTE
而不是 INTEGER
创建的。
我已经尝试失败了:
- 将我的实体字段更改为 Int/Boolean/Byte 并出现相同的错误
- 正在创建一个 TypeConverter 以将其另存为 Boolean/Byte
- 将
typeAffinity
作为 UNDEFINED
添加到我的实体 @ColumnInfo
中,亲和力 = 1
我的databaseSQL
创作语句:
CREATE TABLE IF NOT EXISTS myTable (_id INTEGER PRIMARY KEY AUTOINCREMENT,
my_first_field BOOL NOT NULL DEFAULT 0,
my_second_field BYTE NOT NULL DEFAULT 0)
我的实体:
@Entity(tableName = "myTable")
data class MyTable(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
var id: Int,
@ColumnInfo(name = "my_first_field")
var myFirstField: Boolean = false,
@ColumnInfo(name = "my_second_field")
var mySecondField: Byte = false
)
我经常遇到的错误是:
Expected:
TableInfo{name='my_table', columns={_id=Column{name='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1}, my_first_field=Column{name='my_first_field', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0}, my_second_field=Column{name='my_second_field', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='my_table', columns={_id=Column{name='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1}, my_first_field=Column{name='my_first_field', type='BOOL', affinity='1', notNull=true, primaryKeyPosition=0}, my_second_field=Column{name='my_second_field', type='BYTE', affinity='1', notNull=true, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}
有没有什么方法可以在不创建迁移策略的情况下直接进行?
我相信你可以,在建立房间数据库之前:-
检查是否需要做任何事情,例如通过使用 :-
SELECT count() FROM sqlite_master WHERE name = 'myTable' AND instr(sql,' BOOL ') AND instr(sql,' BYTE ');
然后检查结果。
如果它是 0 什么都不做(尽管为了安全你只能在它是 0 时使用 DROP TABLE IF EXISTS oldmyTable)。
ONLY 如果上面的returns 1 那么:-
删除重命名的原始 table(见下文和上面)以防万一它存在 :-
DROP TABLE IF EXISTS oldmyTable;
使用
定义另一个table
CREATE TABLE IF NOT EXISTS myOtherTable (_id INTEGER PRIMARY KEY AUTOINCREMENT,
my_first_field INTEGER NOT NULL DEFAULT 0,
my_second_field INTEGER NOT NULL DEFAULT 0)
即预期 模式
使用
填充新的table
INSERT INTO myOtherTable SELECT * FROM myTable;
使用 :-
重命名我的 table
ALTER TABLE mytable RENAME TO oldmyTable;
使用原始名称重命名 myOtherTable :-
ALTER TABLE myOtherTable RENAME TO mytable;
删除重命名的原始table(显然仅在测试时):-
DROP TABLE IF EXISTS oldmyTable;
- 在确定迁移成功之前,您可能希望忽略此步骤。
最终结果是 table 应该是 预期的。
关于评论:-
Problem is that I have like 16-20 tables to migrate.
你可以使用类似的东西:-
public static int preMigrateAdjustment(SQLiteDatabase mDB) {
String original_rename_prefix = "old";
String tempname_suffix = "temp";
String newsql_column = "newsql";
String[] columns = new String[]{
"name",
"replace(replace(sql,' BOOL ',' INTEGER '),' BYTE ',' INTEGER ') AS " + newsql_column
};
int count_done = 0;
String whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
Cursor csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
whereclause = "type = 'table' AND (instr(sql,' BOOL ') OR instr(sql,' BYTE '))";
csr = mDB.query(
"sqlite_master",
columns,
whereclause,
null,null,null,null
);
while (csr.moveToNext()) {
String base_table_name = csr.getString(csr.getColumnIndex("name"));
String newsql = csr.getString(csr.getColumnIndex(newsql_column));
String temp_table_name = base_table_name + tempname_suffix;
String renamed_table_name = original_rename_prefix+base_table_name;
mDB.execSQL(newsql.replace(base_table_name,temp_table_name));
mDB.execSQL("INSERT INTO " + temp_table_name + " SELECT * FROM " + base_table_name);
mDB.execSQL("ALTER TABLE " + base_table_name + " RENAME TO " + renamed_table_name);
mDB.execSQL("ALTER TABLE " + temp_table_name + " RENAME TO " + base_table_name);
count_done++;
}
whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
csr.close();
return count_done;
}
- 请注意,这不是万无一失的,例如如果你碰巧有 tables 已经以 old 开头,那么这些将被删除。
- 以上假设第二个运行实际丢弃重命名的原始tables.
额外
在解析 BOOL BYTE 类型后,使用相同的模式调查并实际测试(在本例中使用 5 tables),在该编码中发现了一个额外的问题
_id INTEGER PRIMARY KEY AUTOINCREMENT
结果为 notNull = false,同时编码
@PrimaryKey(autoGenerate = true)
private long _id;
结果为 notNull=true
例如假定 AUTOINCREMENT NOT NULL
未编码的快速修复 preMigrateAdjustment 中的行已从 :-
更改
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)));
到:-
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)).replace("AUTOINCREMENT","AUTOINCREMENT NOT NULL"));
工作演示
创建并填充旧的 (pre-room) tables.
创建和填充旧 tables 是在数据库助手中完成的 OrginalDBHelper.java :-
public class OriginalDBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "mydb";
public static final int DBVERSION = 1;
int tables_to_create = 5; //<<<<<<<<<< 5 sets of tables
SQLiteDatabase mDB;
public OriginalDBHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
mDB = this.getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
for (int i=0;i < tables_to_create;i++) {
db.execSQL("CREATE TABLE IF NOT EXISTS myTable" + String.valueOf(i) + "X (_id INTEGER PRIMARY KEY AUTOINCREMENT,\n" +
" my_first_field BOOL NOT NULL DEFAULT 0,\n" +
" my_second_field BYTE NOT NULL DEFAULT 0)"
);
db.execSQL("INSERT INTO myTable" + String.valueOf(i) + "X (my_first_field,my_second_field) VALUES(0,0),(1,0),(1,1),(0,1)");
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
pre-migration转换tables
即调整架构以适应房间)PreMigrationAdjustment.java
public class PreMigrationAdjustment {
public static int preMigrateAdjustment(SQLiteDatabase mDB) {
String original_rename_prefix = "old";
String tempname_suffix = "temp";
String newsql_column = "newsql";
String[] columns = new String[]{
"name",
"replace(replace(sql,' BOOL ',' INTEGER '),' BYTE ',' INTEGER ') AS " + newsql_column
};
int count_done = 0;
String whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
Cursor csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
whereclause = "type = 'table' AND (instr(sql,' BOOL ') OR instr(sql,' BYTE '))";
csr = mDB.query(
"sqlite_master",
columns,
whereclause,
null,null,null,null
);
while (csr.moveToNext()) {
String base_table_name = csr.getString(csr.getColumnIndex("name"));
String newsql = csr.getString(csr.getColumnIndex(newsql_column));
String temp_table_name = base_table_name + tempname_suffix;
String renamed_table_name = original_rename_prefix+base_table_name;
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)).replace("AUTOINCREMENT","AUTOINCREMENT NOT NULL"));
//mDB.execSQL((newsql.replace(base_table_name,temp_table_name)));
mDB.execSQL("INSERT INTO " + temp_table_name + " SELECT * FROM " + base_table_name);
mDB.execSQL("ALTER TABLE " + base_table_name + " RENAME TO " + renamed_table_name);
mDB.execSQL("ALTER TABLE " + temp_table_name + " RENAME TO " + base_table_name);
count_done++;
}
whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
csr.close();
return count_done;
}
}
- 警告这太简单了,不能在不考虑它的缺陷的情况下使用,并且仅用于演示。
房间的实体
为简洁起见,仅显示 5 个中的 1 个,即 myTable0X.java
显然这些必须仔细编写以匹配 pre-room tables.
@Entity()
public class myTable0X {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
private long id;
@ColumnInfo(name = "my_first_field")
private boolean my_first_field;
@ColumnInfo(name = "my_second_field")
private boolean my_second_field;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public boolean isMy_first_field() {
return my_first_field;
}
public void setMy_first_field(boolean my_first_field) {
this.my_first_field = my_first_field;
}
public boolean isMy_second_field() {
return my_second_field;
}
public void setMy_second_field(boolean my_second_field) {
this.my_second_field = my_second_field;
}
}
单个 DAO 接口 DAOmyTablex.java
@Dao
public interface DAOmyTablex {
@Query("SELECT * FROM myTable0X")
List<myTable0X> getAllFrommyTable0();
@Query("SELECT * FROM myTable1X")
List<myTable1X> getAllFrommyTable1();
@Query("SELECT * FROM myTable2X")
List<myTable2X> getAllFrommyTable2();
@Query("SELECT * FROM myTable3X")
List<myTable3X> getAllFrommyTable3();
@Query("SELECT * FROM myTable4X")
List<myTable4X> getAllFrommyTable4();
@Insert
long[] insertAll(myTable0X... myTable0XES);
@Insert
long[] insertAll(myTable1X... myTable1XES);
@Insert
long[] insertAll(myTable2X... myTable2XES);
@Insert
long[] insertAll(myTable3X... myTable3XES);
@Insert
long[] insertAll(myTable4X... myTable4XES);
@Delete
int delete(myTable0X mytable0X);
@Delete
int delete(myTable1X mytable1X);
@Delete
int delete(myTable2X mytable2X);
@Delete
int delete(myTable3X mytable3X);
@Delete
int delete(myTable4X mytable4X);
}
数据库mydb.java
@Database(entities = {myTable0X.class, myTable1X.class, myTable2X.class, myTable3X.class, myTable4X.class},version = 2)
public abstract class mydb extends RoomDatabase {
public abstract DAOmyTablex dbDAO();
}
- 请注意,所有 5 个实体都已使用。
- 请注意,由于当前数据库版本为 1,room 需要增加版本号,因此 version = 2
综合起来MainActivity.java
这包括 3 个核心阶段
- 正在构建 pre-room 数据库。
- 正在将 tables 转换为西装房间。
- 正在通过房间打开(移交)数据库。
当应用程序启动时,它会自动执行第 1 阶段和第 2 阶段,添加了一个按钮,单击该按钮后将进入第 3 阶段(仅一次)。
最后,从 tables 中提取数据(这实际上打开了 Room 数据库)
table 之一的数据输出到日志。
public class MainActivity extends AppCompatActivity {
OriginalDBHelper mDBHlpr;
Button mGo;
mydb mMyDB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGo = this.findViewById(R.id.go);
mGo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
goForIt();
}
});
mDBHlpr = new OriginalDBHelper(this);
Log.d("STAGE1","The original tables");
dumpAllTables();
Log.d("STAGE2", "Initiaing pre-mirgration run.");
Log.d("STAGE2 A RESULT",
String.valueOf(
PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()
)
) + " tables converted."
); //<<<<<<<<<< CONVERT THE TABLES
Log.d("STAGE2 B","Dumping adjusted tables");
dumpAllTables();
Log.d("STAGE2 C","Second run Cleanup");
Log.d("STAGE2 DRESULT",
String.valueOf(
PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()
)
) + " tables converted."
); //<<<<<<<<<< CONVERT THE TABLES
dumpAllTables();
Log.d("STAGE3","Handing over to ROOM (when button is clicked)");
}
private void goForIt() {
if (mMyDB != null) return;
mMyDB = Room.databaseBuilder(this,mydb.class,OriginalDBHelper.DBNAME).addMigrations(MIGRATION_1_2).allowMainThreadQueries().build();
List<myTable0X> mt0 = mMyDB.dbDAO().getAllFrommyTable0();
List<myTable1X> mt1 = mMyDB.dbDAO().getAllFrommyTable1();
List<myTable2X> mt2 = mMyDB.dbDAO().getAllFrommyTable2();
List<myTable3X> mt3 = mMyDB.dbDAO().getAllFrommyTable3();
List<myTable4X> mt4 = mMyDB.dbDAO().getAllFrommyTable4();
for (myTable0X mt: mt0) {
Log.d("THIS_MT","ID is " + String.valueOf(mt.getId()) + " FIELD1 is " + String.valueOf(mt.isMy_first_field()) + " FIELD2 is " + String.valueOf(mt.isMy_second_field()));
}
// etc.......
}
private void dumpAllTables() {
SQLiteDatabase db = mDBHlpr.getWritableDatabase();
Cursor c1 = db.query("sqlite_master",null,"type = 'table'",null,null,null,null);
while (c1.moveToNext()) {
Log.d("TABLEINFO","Dmuping Data for Table " + c1.getString(c1.getColumnIndex("name")));
Cursor c2 = db.query(c1.getString(c1.getColumnIndex("name")),null,null,null,null,null,null);
DatabaseUtils.dumpCursor(c2);
c2.close();
}
c1.close();
}
public final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
/**NOTES
//Tried the pre-migration here BUT SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
//Cannot use SupportSQLiteDatabase as that locks out access to sqlite_master
//PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()); //Initial run
//PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()); //Cleanup run
*/
}
};
}
- 由于 room 会考虑正在进行的迁移,因此迁移对象的迁移方法被不执行任何操作的方法覆盖。
- 根据尝试使用迁移的评论,问题是数据库被房间锁定并且 SupportSQliteDatabase 传递给 migration 方法不允许访问 sqlite_master.
结果
结果(只是 STAGE????? 输出)是:-
2019-05-19 13:18:12.227 D/STAGE1: The original tables
2019-05-19 13:18:12.244 D/STAGE2: Initiaing pre-mirgration run.
2019-05-19 13:18:12.281 D/STAGE2 A RESULT: 5 tables converted.
2019-05-19 13:18:12.281 D/STAGE2 B: Dumping adjusted tables
2019-05-19 13:18:12.303 D/STAGE2 C: Second run Cleanup
2019-05-19 13:18:12.304 D/STAGE2 DRESULT: 0 tables converted.
2019-05-19 13:18:12.331 D/STAGE3: Handing over to ROOM (when button is clicked)
决赛行数为:-
2019-05-19 13:20:03.090 D/THIS_MT: ID is 1 FIELD1 is false FIELD2 is false
2019-05-19 13:20:03.090 D/THIS_MT: ID is 2 FIELD1 is true FIELD2 is false
2019-05-19 13:20:03.090 D/THIS_MT: ID is 3 FIELD1 is true FIELD2 is true
2019-05-19 13:20:03.090 D/THIS_MT: ID is 4 FIELD1 is false FIELD2 is true
我一直在尝试将我的应用程序迁移到 Room
。由于创建方式的原因,我无法直接迁移的特定 table。
这些字段是使用数据类型 BOOL
和 BYTE
而不是 INTEGER
创建的。
我已经尝试失败了:
- 将我的实体字段更改为 Int/Boolean/Byte 并出现相同的错误
- 正在创建一个 TypeConverter 以将其另存为 Boolean/Byte
- 将
typeAffinity
作为UNDEFINED
添加到我的实体@ColumnInfo
中,亲和力 = 1
我的databaseSQL
创作语句:
CREATE TABLE IF NOT EXISTS myTable (_id INTEGER PRIMARY KEY AUTOINCREMENT,
my_first_field BOOL NOT NULL DEFAULT 0,
my_second_field BYTE NOT NULL DEFAULT 0)
我的实体:
@Entity(tableName = "myTable")
data class MyTable(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
var id: Int,
@ColumnInfo(name = "my_first_field")
var myFirstField: Boolean = false,
@ColumnInfo(name = "my_second_field")
var mySecondField: Byte = false
)
我经常遇到的错误是:
Expected:
TableInfo{name='my_table', columns={_id=Column{name='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1}, my_first_field=Column{name='my_first_field', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0}, my_second_field=Column{name='my_second_field', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='my_table', columns={_id=Column{name='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1}, my_first_field=Column{name='my_first_field', type='BOOL', affinity='1', notNull=true, primaryKeyPosition=0}, my_second_field=Column{name='my_second_field', type='BYTE', affinity='1', notNull=true, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}
有没有什么方法可以在不创建迁移策略的情况下直接进行?
我相信你可以,在建立房间数据库之前:-
检查是否需要做任何事情,例如通过使用 :-
SELECT count() FROM sqlite_master WHERE name = 'myTable' AND instr(sql,' BOOL ') AND instr(sql,' BYTE ');
然后检查结果。
如果它是 0 什么都不做(尽管为了安全你只能在它是 0 时使用 DROP TABLE IF EXISTS oldmyTable)。
ONLY 如果上面的returns 1 那么:-
删除重命名的原始 table(见下文和上面)以防万一它存在 :-
DROP TABLE IF EXISTS oldmyTable;
使用
定义另一个tableCREATE TABLE IF NOT EXISTS myOtherTable (_id INTEGER PRIMARY KEY AUTOINCREMENT, my_first_field INTEGER NOT NULL DEFAULT 0, my_second_field INTEGER NOT NULL DEFAULT 0)
即预期 模式
使用
填充新的tableINSERT INTO myOtherTable SELECT * FROM myTable;
使用 :-
重命名我的 tableALTER TABLE mytable RENAME TO oldmyTable;
使用原始名称重命名 myOtherTable :-
ALTER TABLE myOtherTable RENAME TO mytable;
删除重命名的原始table(显然仅在测试时):-
DROP TABLE IF EXISTS oldmyTable;
- 在确定迁移成功之前,您可能希望忽略此步骤。
最终结果是 table 应该是 预期的。
关于评论:-
Problem is that I have like 16-20 tables to migrate.
你可以使用类似的东西:-
public static int preMigrateAdjustment(SQLiteDatabase mDB) {
String original_rename_prefix = "old";
String tempname_suffix = "temp";
String newsql_column = "newsql";
String[] columns = new String[]{
"name",
"replace(replace(sql,' BOOL ',' INTEGER '),' BYTE ',' INTEGER ') AS " + newsql_column
};
int count_done = 0;
String whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
Cursor csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
whereclause = "type = 'table' AND (instr(sql,' BOOL ') OR instr(sql,' BYTE '))";
csr = mDB.query(
"sqlite_master",
columns,
whereclause,
null,null,null,null
);
while (csr.moveToNext()) {
String base_table_name = csr.getString(csr.getColumnIndex("name"));
String newsql = csr.getString(csr.getColumnIndex(newsql_column));
String temp_table_name = base_table_name + tempname_suffix;
String renamed_table_name = original_rename_prefix+base_table_name;
mDB.execSQL(newsql.replace(base_table_name,temp_table_name));
mDB.execSQL("INSERT INTO " + temp_table_name + " SELECT * FROM " + base_table_name);
mDB.execSQL("ALTER TABLE " + base_table_name + " RENAME TO " + renamed_table_name);
mDB.execSQL("ALTER TABLE " + temp_table_name + " RENAME TO " + base_table_name);
count_done++;
}
whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
csr.close();
return count_done;
}
- 请注意,这不是万无一失的,例如如果你碰巧有 tables 已经以 old 开头,那么这些将被删除。
- 以上假设第二个运行实际丢弃重命名的原始tables.
额外
在解析 BOOL BYTE 类型后,使用相同的模式调查并实际测试(在本例中使用 5 tables),在该编码中发现了一个额外的问题
_id INTEGER PRIMARY KEY AUTOINCREMENT
结果为 notNull = false,同时编码
@PrimaryKey(autoGenerate = true)
private long _id;
结果为 notNull=true
例如假定 AUTOINCREMENT NOT NULL
未编码的快速修复 preMigrateAdjustment 中的行已从 :-
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)));
到:-
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)).replace("AUTOINCREMENT","AUTOINCREMENT NOT NULL"));
工作演示
创建并填充旧的 (pre-room) tables.
创建和填充旧 tables 是在数据库助手中完成的 OrginalDBHelper.java :-
public class OriginalDBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "mydb";
public static final int DBVERSION = 1;
int tables_to_create = 5; //<<<<<<<<<< 5 sets of tables
SQLiteDatabase mDB;
public OriginalDBHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
mDB = this.getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
for (int i=0;i < tables_to_create;i++) {
db.execSQL("CREATE TABLE IF NOT EXISTS myTable" + String.valueOf(i) + "X (_id INTEGER PRIMARY KEY AUTOINCREMENT,\n" +
" my_first_field BOOL NOT NULL DEFAULT 0,\n" +
" my_second_field BYTE NOT NULL DEFAULT 0)"
);
db.execSQL("INSERT INTO myTable" + String.valueOf(i) + "X (my_first_field,my_second_field) VALUES(0,0),(1,0),(1,1),(0,1)");
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
pre-migration转换tables
即调整架构以适应房间)PreMigrationAdjustment.java
public class PreMigrationAdjustment {
public static int preMigrateAdjustment(SQLiteDatabase mDB) {
String original_rename_prefix = "old";
String tempname_suffix = "temp";
String newsql_column = "newsql";
String[] columns = new String[]{
"name",
"replace(replace(sql,' BOOL ',' INTEGER '),' BYTE ',' INTEGER ') AS " + newsql_column
};
int count_done = 0;
String whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
Cursor csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
whereclause = "type = 'table' AND (instr(sql,' BOOL ') OR instr(sql,' BYTE '))";
csr = mDB.query(
"sqlite_master",
columns,
whereclause,
null,null,null,null
);
while (csr.moveToNext()) {
String base_table_name = csr.getString(csr.getColumnIndex("name"));
String newsql = csr.getString(csr.getColumnIndex(newsql_column));
String temp_table_name = base_table_name + tempname_suffix;
String renamed_table_name = original_rename_prefix+base_table_name;
mDB.execSQL((newsql.replace(base_table_name,temp_table_name)).replace("AUTOINCREMENT","AUTOINCREMENT NOT NULL"));
//mDB.execSQL((newsql.replace(base_table_name,temp_table_name)));
mDB.execSQL("INSERT INTO " + temp_table_name + " SELECT * FROM " + base_table_name);
mDB.execSQL("ALTER TABLE " + base_table_name + " RENAME TO " + renamed_table_name);
mDB.execSQL("ALTER TABLE " + temp_table_name + " RENAME TO " + base_table_name);
count_done++;
}
whereclause = "name LIKE('" +
original_rename_prefix +
"%') AND type = 'table'";
csr = mDB.query("sqlite_master",null,whereclause,null,null,null,null);
while (csr.moveToNext()) {
mDB.execSQL("DROP TABLE IF EXISTS " + csr.getString(csr.getColumnIndex("name")));
}
csr.close();
return count_done;
}
}
- 警告这太简单了,不能在不考虑它的缺陷的情况下使用,并且仅用于演示。
房间的实体
为简洁起见,仅显示 5 个中的 1 个,即 myTable0X.java
显然这些必须仔细编写以匹配 pre-room tables.
@Entity()
public class myTable0X {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
private long id;
@ColumnInfo(name = "my_first_field")
private boolean my_first_field;
@ColumnInfo(name = "my_second_field")
private boolean my_second_field;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public boolean isMy_first_field() {
return my_first_field;
}
public void setMy_first_field(boolean my_first_field) {
this.my_first_field = my_first_field;
}
public boolean isMy_second_field() {
return my_second_field;
}
public void setMy_second_field(boolean my_second_field) {
this.my_second_field = my_second_field;
}
}
单个 DAO 接口 DAOmyTablex.java
@Dao
public interface DAOmyTablex {
@Query("SELECT * FROM myTable0X")
List<myTable0X> getAllFrommyTable0();
@Query("SELECT * FROM myTable1X")
List<myTable1X> getAllFrommyTable1();
@Query("SELECT * FROM myTable2X")
List<myTable2X> getAllFrommyTable2();
@Query("SELECT * FROM myTable3X")
List<myTable3X> getAllFrommyTable3();
@Query("SELECT * FROM myTable4X")
List<myTable4X> getAllFrommyTable4();
@Insert
long[] insertAll(myTable0X... myTable0XES);
@Insert
long[] insertAll(myTable1X... myTable1XES);
@Insert
long[] insertAll(myTable2X... myTable2XES);
@Insert
long[] insertAll(myTable3X... myTable3XES);
@Insert
long[] insertAll(myTable4X... myTable4XES);
@Delete
int delete(myTable0X mytable0X);
@Delete
int delete(myTable1X mytable1X);
@Delete
int delete(myTable2X mytable2X);
@Delete
int delete(myTable3X mytable3X);
@Delete
int delete(myTable4X mytable4X);
}
数据库mydb.java
@Database(entities = {myTable0X.class, myTable1X.class, myTable2X.class, myTable3X.class, myTable4X.class},version = 2)
public abstract class mydb extends RoomDatabase {
public abstract DAOmyTablex dbDAO();
}
- 请注意,所有 5 个实体都已使用。
- 请注意,由于当前数据库版本为 1,room 需要增加版本号,因此 version = 2
综合起来MainActivity.java
这包括 3 个核心阶段
- 正在构建 pre-room 数据库。
- 正在将 tables 转换为西装房间。
- 正在通过房间打开(移交)数据库。
当应用程序启动时,它会自动执行第 1 阶段和第 2 阶段,添加了一个按钮,单击该按钮后将进入第 3 阶段(仅一次)。
最后,从 tables 中提取数据(这实际上打开了 Room 数据库) table 之一的数据输出到日志。
public class MainActivity extends AppCompatActivity {
OriginalDBHelper mDBHlpr;
Button mGo;
mydb mMyDB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGo = this.findViewById(R.id.go);
mGo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
goForIt();
}
});
mDBHlpr = new OriginalDBHelper(this);
Log.d("STAGE1","The original tables");
dumpAllTables();
Log.d("STAGE2", "Initiaing pre-mirgration run.");
Log.d("STAGE2 A RESULT",
String.valueOf(
PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()
)
) + " tables converted."
); //<<<<<<<<<< CONVERT THE TABLES
Log.d("STAGE2 B","Dumping adjusted tables");
dumpAllTables();
Log.d("STAGE2 C","Second run Cleanup");
Log.d("STAGE2 DRESULT",
String.valueOf(
PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()
)
) + " tables converted."
); //<<<<<<<<<< CONVERT THE TABLES
dumpAllTables();
Log.d("STAGE3","Handing over to ROOM (when button is clicked)");
}
private void goForIt() {
if (mMyDB != null) return;
mMyDB = Room.databaseBuilder(this,mydb.class,OriginalDBHelper.DBNAME).addMigrations(MIGRATION_1_2).allowMainThreadQueries().build();
List<myTable0X> mt0 = mMyDB.dbDAO().getAllFrommyTable0();
List<myTable1X> mt1 = mMyDB.dbDAO().getAllFrommyTable1();
List<myTable2X> mt2 = mMyDB.dbDAO().getAllFrommyTable2();
List<myTable3X> mt3 = mMyDB.dbDAO().getAllFrommyTable3();
List<myTable4X> mt4 = mMyDB.dbDAO().getAllFrommyTable4();
for (myTable0X mt: mt0) {
Log.d("THIS_MT","ID is " + String.valueOf(mt.getId()) + " FIELD1 is " + String.valueOf(mt.isMy_first_field()) + " FIELD2 is " + String.valueOf(mt.isMy_second_field()));
}
// etc.......
}
private void dumpAllTables() {
SQLiteDatabase db = mDBHlpr.getWritableDatabase();
Cursor c1 = db.query("sqlite_master",null,"type = 'table'",null,null,null,null);
while (c1.moveToNext()) {
Log.d("TABLEINFO","Dmuping Data for Table " + c1.getString(c1.getColumnIndex("name")));
Cursor c2 = db.query(c1.getString(c1.getColumnIndex("name")),null,null,null,null,null,null);
DatabaseUtils.dumpCursor(c2);
c2.close();
}
c1.close();
}
public final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
/**NOTES
//Tried the pre-migration here BUT SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
//Cannot use SupportSQLiteDatabase as that locks out access to sqlite_master
//PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()); //Initial run
//PreMigrationAdjustment.preMigrateAdjustment(mDBHlpr.getWritableDatabase()); //Cleanup run
*/
}
};
}
- 由于 room 会考虑正在进行的迁移,因此迁移对象的迁移方法被不执行任何操作的方法覆盖。
- 根据尝试使用迁移的评论,问题是数据库被房间锁定并且 SupportSQliteDatabase 传递给 migration 方法不允许访问 sqlite_master.
结果
结果(只是 STAGE????? 输出)是:-
2019-05-19 13:18:12.227 D/STAGE1: The original tables
2019-05-19 13:18:12.244 D/STAGE2: Initiaing pre-mirgration run.
2019-05-19 13:18:12.281 D/STAGE2 A RESULT: 5 tables converted.
2019-05-19 13:18:12.281 D/STAGE2 B: Dumping adjusted tables
2019-05-19 13:18:12.303 D/STAGE2 C: Second run Cleanup
2019-05-19 13:18:12.304 D/STAGE2 DRESULT: 0 tables converted.
2019-05-19 13:18:12.331 D/STAGE3: Handing over to ROOM (when button is clicked)
决赛行数为:-
2019-05-19 13:20:03.090 D/THIS_MT: ID is 1 FIELD1 is false FIELD2 is false
2019-05-19 13:20:03.090 D/THIS_MT: ID is 2 FIELD1 is true FIELD2 is false
2019-05-19 13:20:03.090 D/THIS_MT: ID is 3 FIELD1 is true FIELD2 is true
2019-05-19 13:20:03.090 D/THIS_MT: ID is 4 FIELD1 is false FIELD2 is true