GreenDAO 스키마 업데이트 및 데이터 마이그레이션?
상업용 Android 앱에서 고려할 GreenDAO를 평가하고 있으며 스키마 업데이트를위한 마이그레이션 경로를 결정하고 싶었습니다.
onUpdate ()를 제공하고 변환을 추출하고 새 스키마에 따라 데이터를 저장하는 사용자 지정 OpenHelper를 작성해야한다고 주장하는 것이 맞습니까? 이 가정은 호출 순서 및 책임 분할에 대한 몇 가지 흥미로운 질문을 제기합니다.
GreenDAO에 대한 스키마 업데이트 및 데이터 마이그레이션에 대한 문서를 찾을 수 없습니다.
다음은이 주제에 대해 작성한 블로그 기사입니다.
당신은 올바르게 생각했습니다. 오늘날 서로 다른 스키마 버전 간에는 변경 내용 추적이 없습니다. 따라서 스키마를 업그레이드 할 때 SQL을 직접 작성해야합니다.
pleonasmik의 접근 방식에 대해 생각하면서 (참고로, 정말 도움이되었습니다) MigrationHelper 클래스를 하나 만들었습니다.
작동 원리 :
- 수업은 당신이 가진 모든 Daos를 잡아
- 이전 버전의 체계를 기반으로 임시 테이블을 생성합니다 (
generateTempTables
방법). - 이 새 테이블로 모든 데이터 가져 오기 (
generateTempTables
메서드) - 이전 버전의 모든 테이블 삭제 (
DaoMaster.dropAllTables
메소드) - 새 버전의 테이블을 만듭니다 (
DaoMaster.createAllTables
메서드). - 임시에서 새 버전의 테이블을 업데이트합니다 (
restoreData
메소드). - 모든 임시 테이블 삭제 (
restoreData
메소드)
마이그레이션 도우미 클래스 :
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import android.util.Log;
import com.crashlytics.android.Crashlytics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.internal.DaoConfig;
import greendao.DaoMaster;
/**
* Created by pokawa on 18/05/15.
*/
public class MigrationHelper {
private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS";
private static MigrationHelper instance;
public static MigrationHelper getInstance() {
if(instance == null) {
instance = new MigrationHelper();
}
return instance;
}
public void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
generateTempTables(db, daoClasses);
DaoMaster.dropAllTables(db, true);
DaoMaster.createAllTables(db, false);
restoreData(db, daoClasses);
}
private void generateTempTables(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
for(int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String divider = "";
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
ArrayList<String> properties = new ArrayList<>();
StringBuilder createTableStringBuilder = new StringBuilder();
createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" (");
for(int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if(getColumns(db, tableName).contains(columnName)) {
properties.add(columnName);
String type = null;
try {
type = getTypeByClass(daoConfig.properties[j].type);
} catch (Exception exception) {
Crashlytics.logException(exception);
}
createTableStringBuilder.append(divider).append(columnName).append(" ").append(type);
if(daoConfig.properties[j].primaryKey) {
createTableStringBuilder.append(" PRIMARY KEY");
}
divider = ",";
}
}
createTableStringBuilder.append(");");
db.execSQL(createTableStringBuilder.toString());
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(" FROM ").append(tableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
}
}
private 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");
ArrayList<String> properties = 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);
}
}
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", properties));
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 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)) {
return "INTEGER";
}
if(type.equals(Boolean.class)) {
return "BOOLEAN";
}
Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString()));
Crashlytics.logException(exception);
throw exception;
}
private static List<String> getColumns(SQLiteDatabase db, String tableName) {
List<String> columns = new ArrayList<>();
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null);
if (cursor != null) {
columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames()));
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return columns;
}
}
다음은 DaoMaster.java 클래스에서 어떻게 호출되어야 하는지를 보여주는 예입니다.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by migrating all tables data");
MigrationHelper.getInstance().migrate(db,
UserDao.class,
ItemDao.class);
}
이것은 수정 된 오류로 GreenDao 3. +에서 작동하는 @PedroOkawa의 동일한 코드입니다.
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import android.text.TextUtils;
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.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Createdby PedroOkawa and modified by MBH on 16/08/16.
*/
public final class MigrationHelper {
public static void migrate(SQLiteDatabase sqliteDatabase, Class<? extends AbstractDao<?, ?>>... daoClasses) {
StandardDatabase db = new StandardDatabase(sqliteDatabase);
generateNewTablesIfNotExists(db, daoClasses);
generateTempTables(db, daoClasses);
dropAllTables(db, true, daoClasses);
createAllTables(db, false, daoClasses);
restoreData(db, daoClasses);
}
public static void migrate(StandardDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
generateNewTablesIfNotExists(db, daoClasses);
generateTempTables(db, daoClasses);
dropAllTables(db, true, daoClasses);
createAllTables(db, false, daoClasses);
restoreData(db, daoClasses);
}
private static void generateNewTablesIfNotExists(StandardDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "createTable", true, daoClasses);
}
private static void generateTempTables(StandardDatabase 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");
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("CREATE TEMP TABLE ").append(tempTableName);
insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
}
}
private static void dropAllTables(StandardDatabase db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "dropTable", ifExists, daoClasses);
}
private static void createAllTables(StandardDatabase db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "createTable", ifNotExists, daoClasses);
}
/**
* dao class already define the sql exec method, so just invoke it
*/
private static void reflectMethod(StandardDatabase 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(StandardDatabase 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");
// 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(StandardDatabase 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;
}
}
사용법은 다음과 같습니다.
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
MigrationHelper.migrate(new StandardDatabase(sqLiteDatabase),
UserDao.class,
ItemDao.class);
// OR you can use it like this (Dont use both it is example of 2 different usages)
MigrationHelper.migrate(sqLiteDatabase,
UserDao.class,
ItemDao.class);
}
StandardDatabase
greendao에서 찾을 수 있으며 이것은 가져 오기입니다.
import org.greenrobot.greendao.database.StandardDatabase;
@PedroOkawa에게 다시 한 번 감사드립니다 :)
비슷한 질문에 대한 제 답변 이이 접근 방식에 도움이 될 수 있다고 생각합니다 . 정말로 데이터를 마이그레이션해야하는 경우, 예를 들어 SQLite에서 지원되지 않는 일부 제약 변경 사항이나 작업을 처리해야하는 경우 마이그레이션을 직접 작성하는 것이 좋습니다. 예를 들어 마이그레이션 도우미의 예 (링크 된 응답에서 수행 한 접근 방식에 따름)는 다음과 같습니다.
public class DBMigrationHelper6 extends AbstractMigratorHelper {
/* Upgrade from DB schema 6 to schema 7 , version numbers are just examples*/
public void onUpgrade(SQLiteDatabase db) {
/* Create a temporal table where you will copy all the data from the previous table that you need to modify with a non supported sqlite operation */
db.execSQL("CREATE TABLE " + "'post2' (" + //
"'_id' INTEGER PRIMARY KEY ," + // 0: id
"'POST_ID' INTEGER UNIQUE ," + // 1: postId
"'USER_ID' INTEGER," + // 2: userId
"'VERSION' INTEGER," + // 3: version
"'TYPE' TEXT," + // 4: type
"'MAGAZINE_ID' TEXT NOT NULL ," + // 5: magazineId
"'SERVER_TIMESTAMP' INTEGER," + // 6: serverTimestamp
"'CLIENT_TIMESTAMP' INTEGER," + // 7: clientTimestamp
"'MAGAZINE_REFERENCE' TEXT NOT NULL ," + // 8: magazineReference
"'POST_CONTENT' TEXT);"); // 9: postContent
/* Copy the data from one table to the new one */
db.execSQL("INSERT INTO post2 (_id, POST_ID, USER_ID, VERSION, TYPE, MAGAZINE_ID, SERVER_TIMESTAMP, CLIENT_TIMESTAMP, MAGAZINE_REFERENCE, POST_CONTENT)" +
" SELECT _id, POST_ID, USER_ID, VERSION, TYPE, MAGAZINE_ID, SERVER_TIMESTAMP, CLIENT_TIMESTAMP, MAGAZINE_REFERENCE, POST_CONTENT FROM post;");
/* Delete the previous table */
db.execSQL("DROP TABLE post");
/* Rename the just created table to the one that I have just deleted */
db.execSQL("ALTER TABLE post2 RENAME TO post");
/* Add Index/es if you want them */
db.execSQL("CREATE INDEX " + "IDX_post_USER_ID ON post" +
" (USER_ID);");
}
}
greenDAO 3에서 데이터베이스 스키마 버전을 업데이트하려는 경우 build.gradle
종속성 위 의 앱 파일에 다음을 추가하십시오 .
apply plugin: 'org.greenrobot.greendao'
greendao {
schemaVersion 1
}
Pedro Okawa' solution is correct, but you need to write your "UpgradeHelper" to extend "OpenHelper" because DaoMaster is overwritten each time you re-generate DAO code.
Example:
public class UpgradeHelper extends DaoMaster.OpenHelper {
public UpgradeHelper(Context context, String name, SQLiteDatabase.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 migrating all tables data");
MigrationHelper.getInstance().migrate(db,
UserDao.class,
ItemDao.class,
AnotherClassToGenerateDao.class);
}
}
If you are just looking for a way to add new Tables to your schema without deleting your user's data and don't need to transform any existing data, take a look at my answer to this question for a discrete example of how to do it with greenDao.
ReferenceURL : https://stackoverflow.com/questions/13373170/greendao-schema-update-and-data-migration
'program story' 카테고리의 다른 글
git-subtree 추가 후 rebase하는 방법은 무엇입니까? (0) | 2020.12.26 |
---|---|
스칼라 : 함수 인수에서 튜플 분해 (0) | 2020.12.25 |
Android Studio, 에뮬레이터 실행시 갑자기 GPU 드라이버 문제 발생 (0) | 2020.12.25 |
두 개의 Subversion 저장소를 동기화하는 방법은 무엇입니까? (0) | 2020.12.25 |
자바 스크립트에서 함수 선언 후 빈 괄호 ()는 무엇을합니까? (0) | 2020.12.25 |