¿Actualización de esquema GreenDAO y migración de datos?

Estoy evaluando GreenDAO para su consideración en una aplicación comercial de Android que estaré trabajando y quería determinar la ruta de migración para las actualizaciones de esquema.

¿Estoy correcto al afirmar que tendría que escribir un OpenHelper personalizado que proporciona el onUpdate () y extrae las transformaciones y almacena los datos de acuerdo con el nuevo esquema? Esta suposición plantea algunas preguntas interesantes acerca del ordenamiento de las llamadas y el reparto de la responsabilidad.

No he podido encontrar ninguna documentación alrededor de la actualización del esquema y de la migración de datos para GreenDAO.

Aquí hay un montón de artículos de blog que he escrito sobre este tema:

  1. Revisión de greenDAO
  2. Parte 1 – Generación de esquemas
  3. Parte 2 – Migración de esquemas
  4. Parte 3 – Prueba de migración de esquema

Asumiste correctamente. No existe ningún seguimiento de cambios entre versiones de esquemas diferentes en la actualidad. Por lo tanto, necesitas escribir SQL cuando realices actualizaciones de esquemas.

Pensando en el enfoque del pleonasmik (por cierto, gracias, fue muy útil), creé una clase MigrationHelper.

Cómo funciona:

  1. La clase captura todos los Daos que tienes
  2. Crea las tablas temporales basadas en el esquema de la versión anterior (método generateTempTables )
  3. Importe todos los datos a estas nuevas tablas (método generateTempTables )
  4. Eliminar todas las tablas de la versión antigua (método DaoMaster.dropAllTables )
  5. Crea las tablas de la nueva versión (método DaoMaster.createAllTables )
  6. Actualiza las tablas de la nueva versión de los temporales (método restoreData )
  7. restoreData todas las tablas temporales (método restoreData )

Migración Clase de ayuda:

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

Y aquí está un ejemplo de cómo debería ser llamado en la clase 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); } 

Creo que mi respuesta a una pregunta similar puede ayudar con este enfoque. Si realmente necesitas migrar datos te sugiero que si por ejemplo necesitas lidiar con algunos cambios de restricciones o cosas que no son compatibles con SQLite, tú mismo escribes la migración. Por ejemplo un ejemplo de un ayudante migrator (siguiendo el enfoque que hice en la respuesta vinculada) podría ser:

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

Este es el mismo código de @PedroOkawa que funciona con el GreenDao 3. + con errores fijos:

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

Y el uso es:

 @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 se puede encontrar en greendao y esta es la importación:

 import org.greenrobot.greendao.database.StandardDatabase; 

Gracias de nuevo a @PedroOkawa 🙂

Pedro Okawa 'es correcta, pero necesitas escribir tu "UpgradeHelper" para extender "OpenHelper" porque DaoMaster se sobrescribe cada vez que vuelves a generar código DAO.

Ejemplo:

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

}

Para aquellos que busquen actualizar la versión del esquema de base de datos en greenDAO 3, agregue esto al archivo build.gradle su aplicación por encima de las dependencias:

 apply plugin: 'org.greenrobot.greendao' greendao { schemaVersion 1 } 

Si sólo está buscando una manera de agregar nuevas Tablas a su esquema sin eliminar los datos de su usuario y no necesita transformar ningún dato existente, eche un vistazo a mi respuesta a esta pregunta para un ejemplo discreto de cómo hacerlo con GreenDao.

  • ¿Cómo combinar múltiples operaciones greenDAO Tx en una sola transacción?
  • Muchos-a-Muchos compilan error usando greendao para android
  • Hilo de fondo que bloquea la interfaz de usuario durante varios segundos
  • GreenDAO: almacena la lista de entidades en otra entidad
  • Entidad Greendao cuando se serializa utilizando GSON no contiene entidad relacionada en Generated JSON cadena
  • GreenDAO no genera restricción FOREIGN KEY (...) en la tabla
  • Actualización del esquema greenDao
  • ¿Cuál es la mejor manera de crear la conexión DB greenDAO sólo una vez para una sola ejecución de la aplicación?
  • Android - Observer DB cambia con GreenDao ORM
  • Android: ¿Cómo usar greenDao para los datos de carga asíncrona?
  • Generador greenDAO da error de consola que no tiene sentido
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.