最近呢做了一个半离线半在线的app,之前都是用原生的代码写的数据库,但是我想用新的东西,所以我选择了用GreenDao,然后用用起来还挺不错的。这一篇是讲数据库升级的,等有空再详细写一篇GreenDao的使用把。另外有一个号称全世界最高效的移动数据库:ObjectBox不知道大家有没有在用,我等这段时间忙完手上的项目就去学一下那个数据库。
数据库升级的时候,首先你得去修改你的版本号,这样才会触发数据库升级。原生的我们直接在OpenHelper里面可以写入版本号,然后GreenDao3.0之前可以在生成数据库代码的java类里面直接写明版本号,3.0之后主要用注解来写,那么就需要再module的gradle文件里面写啦。如下在android下面写:
android {........greendao{schemaVersion 2}}
直接修改他的版本号就可以了。然后你就会走到升级数据库的代码块啦,就是在OpenHelper的onUpgrade方法里面来处理。现在数据库升级的方法一般是这样的:
(1)取出老表里面的数据;
(2)创建临时表把数据转进来;
(3)删除老表和创建新表;
(4)把数据转到新表去,干掉临时表。
现在在github和csdn里面,你搜一下数据库升级或者是GreenDao的数据库升级,马上就出来了解决方案了,还直接把上面的四步操作直接帮你封装到一个类里面直接调用就可以了。我这边也是从别的地方借鉴(抄袭和学习)过来的。因为我这里讲的是GreenDao的数据库升级,我不是直接继承SQLiteOpenHelper而是继承DaoMaster.OpenHelper,这个类的代码如下:
public class CusOpenHelper extends DaoMaster.OpenHelper {private static final String TAG = "CusOpenHelper";public CusOpenHelper(Context context, String name) {super(context, name);}@Overridepublic void onUpgrade(Database db, int oldVersion, int newVersion) {super.onUpgrade(db, oldVersion, newVersion);Log.e(TAG, "onUpgrade: oldVersion:" + oldVersion + ",newVersion:" + newVersion);//第三个参数是那些表修改了就要传进去那些MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {@Overridepublic void onCreateAllTables(Database db, boolean ifNotExists) {DaoMaster.createAllTables(db, ifNotExists);}@Overridepublic void onDropAllTables(Database db, boolean ifExists) {DaoMaster.dropAllTables(db, ifExists);}}, PersonDao.class);}
}
然后我先把这个MigrationHelper的代码贴出来再接着讲:
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.StandardDatabase;
import org.greenrobot.greendao.internal.DaoConfig;import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** please call {@link #migrate(SQLiteDatabase, Class[])} or {@link #migrate(Database, Class[])}*/
public final class MigrationHelper {private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN’T MATCH WITH THE CURRENT PARAMETERS";public static boolean DEBUG = false;private static String TAG = "MigrationHelper";private static final String SQLITE_MASTER = "sqlite_master";private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master";private static WeakReference<ReCreateAllTableListener> weakListener;public interface ReCreateAllTableListener {void onCreateAllTables(Database db, boolean ifNotExists);void onDropAllTables(Database db, boolean ifExists);}public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {printLog("【The Old Database Version】" + db.getVersion());Database database = new StandardDatabase(db);migrate(database, daoClasses);}public static void migrate(SQLiteDatabase db, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {weakListener = new WeakReference<>(listener);migrate(db, daoClasses);}public static void migrate(Database database, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {weakListener = new WeakReference<>(listener);migrate(database, daoClasses);}public static void migrate(Database database, Class<? extends AbstractDao<?, ?>>... daoClasses) {printLog("【Generate temp table】start");generateTempTables(database, daoClasses);printLog("【Generate temp table】complete");ReCreateAllTableListener listener = null;if (weakListener != null) {listener = weakListener.get();}if (listener != null) {listener.onDropAllTables(database, true);printLog("【Drop all table by listener】");listener.onCreateAllTables(database, false);printLog("【Create all table by listener】");} else {dropAllTables(database, true, daoClasses);createAllTables(database, false, daoClasses);}printLog("【Restore data】start");restoreData(database, daoClasses);printLog("【Restore data】complete");}private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {for (int i = 0; i < daoClasses.length; i++) {String tempTableName = null;DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);String tableName = daoConfig.tablename;if (!isTableExists(db, false, tableName)) {printLog("【New Table】" + tableName);continue;}try {tempTableName = daoConfig.tablename.concat("_TEMP");StringBuilder dropTableStringBuilder = new StringBuilder();dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");db.execSQL(dropTableStringBuilder.toString());StringBuilder insertTableStringBuilder = new StringBuilder();insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");db.execSQL(insertTableStringBuilder.toString());printLog("【Table】" + tableName + "\n ---Columns-->" + getColumnsStr(daoConfig));printLog("【Generate temp table】" + tempTableName);} catch (SQLException e) {Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);}}}private static boolean isTableExists(Database db, boolean isTemp, String tableName) {if (db == null || TextUtils.isEmpty(tableName)) {return false;}String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER;String sql = "SELECT COUNT(*) FROM " + dbName + " WHERE type = ? AND name = ?";Cursor cursor = null;int count = 0;try {cursor = db.rawQuery(sql, new String[]{"table", tableName});if (cursor == null || !cursor.moveToFirst()) {return false;}count = cursor.getInt(0);} catch (Exception e) {e.printStackTrace();} finally {if (cursor != null) {cursor.close();}}return count > 0;}private static String getColumnsStr(DaoConfig daoConfig) {if (daoConfig == null) {return "no columns";}StringBuilder builder = new StringBuilder();for (int i = 0; i < daoConfig.allColumns.length; i++) {builder.append(daoConfig.allColumns[i]);builder.append(",");}if (builder.length() > 0) {builder.deleteCharAt(builder.length() - 1);}return builder.toString();}private static void dropAllTables(Database db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {reflectMethod(db, "dropTable", ifExists, daoClasses);printLog("【Drop all table by reflect】");}private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {reflectMethod(db, "createTable", ifNotExists, daoClasses);printLog("【Create all table by reflect】");}/*** dao class already define the sql exec method, so just invoke it*/private static void reflectMethod(Database 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, Database.class, boolean.class);method.invoke(null, db, isExists);}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}private static void restoreData(Database 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");ArrayList<String> properties = new ArrayList();ArrayList<String> propertiesQuery = new ArrayList();for (int j = 0; j < daoConfig.properties.length; j++) {String columnName = daoConfig.properties[j].columnName;if (getColumns(db, tempTableName).contains(columnName)) {properties.add(columnName);propertiesQuery.add(columnName);} else {try {if (getTypeByClass(daoConfig.properties[j].type).equals("INTEGER")) {propertiesQuery.add("0 as" + columnName);properties.add(columnName);}} catch (Exception e) {e.printStackTrace();}}}StringBuilder insertTableStringBuilder = new StringBuilder();insertTableStringBuilder.append("INSERT INTO ").append(tableName).append("(");insertTableStringBuilder.append(TextUtils.join(",", properties));insertTableStringBuilder.append(") SELECT ");insertTableStringBuilder.append(TextUtils.join(",", propertiesQuery));insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");StringBuilder dropTableStringBuilder = new StringBuilder();dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);db.execSQL(insertTableStringBuilder.toString());db.execSQL(dropTableStringBuilder.toString());}}private static String getTypeByClass(Class<?> type) throws Exception {if (type.equals(String.class)) {return "TEXT";}if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class) || type.equals(int.class)) {return "INTEGER";}if (type.equals(Boolean.class) || type.equals(boolean.class)) {return "BOOLEAN";}Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat("- Class:").concat(type.toString()));exception.printStackTrace();throw exception;}private static List<String> getColumns(Database 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;}private static void printLog(String info) {if (DEBUG) {Log.d(TAG, info);}}}
这个类是从github里面抠出来的,只不过我在数据库升级的过程中出现问题了,就是我创建的新表里面有Int字段,转移数据到新表的时候会说无法直接赋予int字段null类型数据,所以就转移操作失败,直接把我的数据库里面的数据全部干掉了。。。。丢失了。所以我去搜索了解决办法,就是去修改MigrationHelper类的restoreData方法,让他如果在转移数据的时候,新表里面存在int类型的就默认赋值为0而不是null,就解决啦。
另外我的继承DaoMaster.OpenHelper的类里面写了使用方法,哪里备注了,你的可变参数入参是你修改了什么表(包括新增),就把他的Dao写进去。