GreenDAO-Schemaaktualisierung und Datenmigration?

Lesezeit: 12 Minuten

Benutzer-Avatar
Syntax

Ich evaluiere GreenDAO für die Berücksichtigung in einer kommerziellen Android-App, an der ich arbeiten werde, und wollte den Migrationspfad für Schemaaktualisierungen bestimmen.

Behaupte ich richtig, dass ich einen benutzerdefinierten OpenHelper schreiben müsste, der onUpdate() bereitstellt und Daten gemäß dem neuen Schema transformiert und speichert? Diese Annahme wirft einige interessante Fragen zur Reihenfolge der Anrufe und zur Aufteilung der Verantwortlichkeiten auf.

Ich konnte keine Dokumentation zur Schemaaktualisierung und Datenmigration für GreenDAO finden.

Hier sind eine Reihe von Blogartikeln, die ich zu diesem Thema geschrieben habe:

  1. Überprüfung von greenDAO
  2. Teil 1 – Schemagenerierung
  3. Teil 2 – Schemamigration
  4. Teil 3 – Testen der Schemamigration

  • Ich habe das gleiche Problem. hast du diesen Helfer geschrieben?

    – Benutzer987723

    6. Februar 2013 um 10:42 Uhr

  • Das habe ich getan, aber die Struktur dieser Helfer ist so, dass sie sehr eng an Ihre Lösung gekoppelt ist (arbeitet direkt an Ihren Tabellen und Spalten Ihrer Datenbank in Bezug auf die Versionsänderungen), sodass sie nicht wiederverwendbar sind. Verzeihung.

    – Syntax

    7. Februar 2013 um 3:13 Uhr

  • @Syntax Können Sie uns sagen, wo Sie den OpenHelper abgelegt haben? Im Moment erweitert das DaoMaster-Objekt den SQLiteOpenHelper, aber diese Datei wird generiert und ist nicht zum Bearbeiten gedacht. Oder haben Sie den DaoMaster einfach erweitert? Danke

    – Tim

    4. März 2013 um 12:51 Uhr

  • @Tim Mein benutzerdefinierter UpgradeHelper erweitert DaoMaster.OpenHelper, dann verwende ich ihn so. “OpenHelper helper = new UpgradeHelper(application, Constants.DB_NAME, null);”

    – Syntax

    5. März 2013 um 16:40 Uhr

  • Entschuldigung @MahdiAlkhatib, der Blog, der meine Lösung gehostet hat, wurde geschlossen und die gehosteten Anweisungen und Dateien wurden entfernt. Eine gute Nachricht ist, dass das web.archive immer noch Kopien hat; Ich habe die Links oben aktualisiert, sie sollten funktionieren 🙂 Viel Spaß

    – Syntax

    22. Januar 2015 um 12:41 Uhr

Benutzer-Avatar
Pedro Okawa

Als ich über den Ansatz von Pleonasmik nachdachte (übrigens, danke, es war wirklich hilfreich), habe ich eine MigrationHelper-Klasse erstellt.

Wie es funktioniert:

  1. Die Klasse fängt alle Daos, die du hast
  2. Erstellt die temporären Tabellen nach dem Schema der alten Version (generateTempTables Methode)
  3. Importieren Sie alle Daten in diese neuen Tabellen (generateTempTables Methode)
  4. Löschen Sie alle Tabellen der alten Version (DaoMaster.dropAllTables Methode)
  5. Erstellt die Tabellen der neuen Version (DaoMaster.createAllTables Methode)
  6. Aktualisiert die Tabellen der neuen Version aus den Temporären (restoreData Methode)
  7. Löschen Sie alle temporären Tabellen (restoreData Methode)

Migration Helper-Klasse:

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

Und hier ist ein Beispiel, wie es in der DaoMaster.java-Klasse aufgerufen werden sollte:

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

  • Das ist jetzt ein toller Code! Ich danke Ihnen für das Teilen 🙂

    – Hardik4560

    9. März 2016 um 7:49 Uhr

  • Ich mag die Lösung wirklich sehr, sie ist kurz und mächtig. Eine Frage, die ich hatte, ist, dass es nicht so aussieht, als wäre es in der Lage, den Fall zu behandeln, in dem Sie Felder hinzufügen, die obligatorisch (erforderlich) sind. Es scheint keinen Punkt zu geben, an dem Sie Standardwerte usw. zum Ausfüllen von Werten in Pflichtfeldern usw. angeben können (da der Migrationscode generisch ist). Wenn ich mich also nicht irre, kann diese Lösung die Arten von Änderungen einschränken, die Sie vornehmen können Ihre Datenbank.

    – Syntax

    25. März 2016 um 13:38 Uhr

  • Ich weiß, dass der unter Stack Overflow veröffentlichte Code unter Creative Commons steht … Aber dieser Typ hat den Code in sein eigenes GitHub-Repo kopiert, ohne diesen Thread überhaupt zu erwähnen: github.com/yuweiguocn/GreenDaoUpgradeHelper/blob/master/library/…

    – Enrico Susatyo

    6. April 2016 um 1:08 Uhr

  • Hallo Leute, @EnricoSusatyo wirklich danke, dass du mich davor gewarnt hast, aber das ist in Ordnung, ich werde es nicht zurückfordern, ich möchte wirklich, dass es kostenlos ist, und ich möchte auch nicht die Credits. Keine Sorge, und ich hoffe, ich habe euch geholfen, Leute, und nochmals vielen Dank, Kumpel. 😀

    – Pedro Okawa

    27. Juni 2016 um 12:37 Uhr

  • medium.com/@kidusmamuye/…

    – Kidus Tekeste

    24. Oktober 2019 um 18:13 Uhr

Du hast richtig angenommen. Derzeit gibt es keine Änderungsverfolgung zwischen verschiedenen Schemaversionen. Daher müssen Sie SQL selbst schreiben, wenn Sie Schema-Upgrades durchführen.

  • Die unten vorgeschlagenen Lösungen sind wirklich großartig und es wird mehr als perfekt sein, wenn Sie sich entscheiden, sie zu Greendao hinzuzufügen.

    – MoxGeek

    16. Juli 2020 um 12:28 Uhr

Benutzer-Avatar
MBH

Dies ist derselbe Code von @PedroOkawa, der mit GreenDao 3.+ mit behobenen Fehlern funktioniert:

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

}

und die Verwendung ist:

@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 kann in greendao gefunden werden und dies ist der Import:

import org.greenrobot.greendao.database.StandardDatabase;

Nochmals vielen Dank an @PedroOkawa 🙂

Benutzer-Avatar
Kolicious

Für diejenigen unter Ihnen, die die Version des Datenbankschemas auf greenDAO 3 aktualisieren möchten, fügen Sie dies zu Ihrer App hinzu build.gradle Datei oben Abhängigkeiten:

apply plugin: 'org.greenrobot.greendao'

greendao {
    schemaVersion 1
}

Benutzer-Avatar
pleonasmik

Ich denke, dass meine Antwort auf eine ähnliche Frage bei diesem Ansatz helfen kann. Wenn Sie wirklich Daten migrieren müssen, schlage ich Ihnen vor, dass Sie die Migration selbst schreiben, wenn Sie beispielsweise mit einigen Einschränkungen, Änderungen oder Dingen umgehen müssen, die in SQLite nicht unterstützt werden. Ein Beispiel für einen Migrationshelfer (nach dem Ansatz, den ich bei der verlinkten Antwort gemacht habe) könnte beispielsweise sein:

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

   }
}

Benutzer-Avatar
Oskarko

Die Lösung von Pedro Okawa ist richtig, aber Sie müssen Ihren “UpgradeHelper” schreiben, um “OpenHelper” zu erweitern, da DaoMaster jedes Mal überschrieben wird, wenn Sie DAO-Code neu generieren.

Beispiel:

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

}

Benutzer-Avatar
Gemeinschaft

Wenn Sie nur nach einer Möglichkeit suchen, Ihrem Schema neue Tabellen hinzuzufügen, ohne die Daten Ihres Benutzers zu löschen, und keine vorhandenen Daten transformieren müssen, sehen Sie sich meine Antwort auf diese Frage an, um ein diskretes Beispiel dafür zu erhalten, wie Sie damit umgehen können grünDao.

  • Hallo, danke für deinen Kommentar. Wenn Sie die Blogbeiträge lesen, die in meiner obigen Frage in Bearbeitungen hinzugefügt wurden, sollten Sie eine viel umfassendere Lösung für dieses Migrationsproblem usw. finden als die unter dem angegebenen Link. Prost

    – Syntax

    9. Mai 2013 um 0:04 Uhr

  • Ich habe alle 4 Ihrer Blogbeiträge gelesen, bevor ich überhaupt meine Frage gestellt habe. Da die Informationen, nach denen ich suchte, unter dem von Ihnen verwendeten Muster vergraben waren, dachte ich, ich würde ein diskretes Beispiel geben, da es anscheinend keine auf SO gibt, die sich auf greenDao beziehen. Ich verwende in meiner echten App tatsächlich ein Muster, das Ihrem sehr ähnlich ist, aber die Frage, die ich gestellt habe, war einfach, wie man eine neue Tabelle hinzufügt, nicht, welches Muster beim Aktualisieren Ihres Schemas verwendet werden soll. Ich schätze Ihre Beiträge, wollte nur eine prägnantere Antwort geben, wie im Haftungsausschluss in meiner Antwort angegeben.

    – DiscDev

    9. Mai 2013 um 14:02 Uhr


1186130cookie-checkGreenDAO-Schemaaktualisierung und Datenmigration?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy