android最佳实践9-2:greenDao的数据库升级

app开发期间的数据库升级

我们使用DevOpenHelper打开数据库

1
2
 DaoMaster.DevOpenHelper helper = 
new DevOpenHelper(application,"xue_old.db",null);

DevOpenHelper内容

1
2
3
4
5
6
7
8
9
10
11
12
public static class DevOpenHelper extends OpenHelper {
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
dropAllTables(db, true);
onCreate(db);
}
}

其中

1
2
3
4
//删除当前所有表
dropAllTables(db, true);
//新建当前所有表
onCreate(db);

这种方式意味着每次都重新创建,所以数据不会报错

app 上线后的数据库升级,Migration(合并)方式

我们自己新建一个HMROpenHelper,继承自OpenHelper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class HMROpenHelper extends DaoMaster.OpenHelper {

public HMROpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}

/**
* 数据库升级
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//操作数据库的更新
MigrationHelper.migrate(db,NoteDao.class,LessonDao.class,ZhangDao.class);
}

}

数据库的合并如下
MigrationHelper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
public class MigrationHelper {

/**
* 调用升级方法
* @param db
* @param daoClasses 一系列dao.class
*/
public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
//1 新建临时表
generateTempTables(db, daoClasses);
//2 创建新表
createAllTables(db, false, daoClasses);
//3 临时表数据写入新表,删除临时表
restoreData(db, daoClasses);
}


/**
* 生成临时表,存储旧的表数据
* @param db
* @param daoClasses
*/
private static void generateTempTables(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
//方法2
for (int i=0;i<daoClasses.length;i++){
DaoConfig daoConfig = new DaoConfig(db,daoClasses[i]);
String tableName = daoConfig.tablename;
if (!checkTable(db,tableName))
continue;
String tempTableName = daoConfig.tablename.concat("_TEMP");
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("alter table ")
.append(tableName)
.append(" rename to ")
.append(tempTableName)
.append(";");
db.execSQL(insertTableStringBuilder.toString());
}
}

/**
* 检测table是否存在
* @param db
* @param tableName
*/
private static Boolean checkTable(SQLiteDatabase db,String tableName){
StringBuilder query = new StringBuilder();
query.append("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='").append(tableName).append("'");
Cursor c = db.rawQuery(query.toString(), null);
if (c.moveToNext()){
int count = c.getInt(0);
if(count>0){
return true;
}
return false;
}
return false;
}

/**
* 删除所有旧表
* @param db
* @param ifExists
* @param daoClasses
*/
private static void dropAllTables(SQLiteDatabase db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "dropTable", ifExists, daoClasses);
}

/**
* 创建新的表结构
* @param db
* @param ifNotExists
* @param daoClasses
*/
private static void createAllTables(SQLiteDatabase db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "createTable", ifNotExists, daoClasses);
}

/**
* 创建根删除都在NoteDao声明了,可以直接拿过来用
* dao class already define the sql exec method, so just invoke it
*/
private static void reflectMethod(SQLiteDatabase db, String methodName, boolean isExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
if (daoClasses.length < 1) {
return;
}
try {
for (Class cls : daoClasses) {
//根据方法名,找到声明的方法
Method method = cls.getDeclaredMethod(methodName, SQLiteDatabase.class, boolean.class);
method.invoke(null, db, isExists);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

/**
* 临时表的数据写入新表
* @param db
* @param daoClasses
*/
private static void restoreData(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
if (!checkTable(db,tempTableName))
continue;
// get all columns from tempTable, take careful to use the columns list
List<String> columns = getColumns(db, tempTableName);
//新表,临时表都包含的字段
ArrayList<String> properties = new ArrayList<>(columns.size());
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if (columns.contains(columnName)) {
properties.add(columnName);
}
}
if (properties.size() > 0) {
final String columnSQL = TextUtils.join(",", properties);

StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(columnSQL);
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(columnSQL);
insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
}
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
db.execSQL(dropTableStringBuilder.toString());
}
}

private static List<String> getColumns(SQLiteDatabase db, String tableName) {
List<String> columns = null;
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 0", null);
if (null != cursor && cursor.getColumnCount() > 0) {
columns = Arrays.asList(cursor.getColumnNames());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
if (null == columns)
columns = new ArrayList<>();
}
return columns;
}
}

其核心思路是
1 把旧表改为临时表
2 建立新表
3 临时表数据写入新表,删除临时表

这样我们就可以直接打开数据库进行更新操作了

1
2
3
HMROpenHelper helper = new HMROpenHelper(application, "xue_old.db", null);
SQLiteDatabase sqlDB = helper.getWritableDatabase();
return sqlDB;

重点

别忘了更新时SCHEMA_VERSION自动加1,不然不会执行更新
位于DaoMaster.java