Восстановление и приложение на основе SQL - PullRequest
0 голосов
/ 24 марта 2019

Я хотел бы знать, как лучше всего реализовать undelete в приложении, которое использует SQL в качестве базовой структуры для хранения данных.Я спрашиваю, потому что в SQL после выполнения оператора delete данные больше не могут быть восстановлены.Тем не менее, представьте себе приложение для хранения данных о клиентах и ​​поставщиках со склада в списке.Ваше мобильное приложение может оказаться в вашем кармане и случайно удалить данные, или вы просто допустите ошибки при использовании пользовательского интерфейса.

Как лучше всего решить эту проблему?Вы могли бы покончить с SQL, но тогда приложение будет медленным, и количество строк кода и сложность кода наверняка увеличатся.

Спасибо.

1 Ответ

2 голосов
/ 24 марта 2019

Вы можете убедиться, что любое такое действие требует подтверждения.

Вы можете выполнять резервное копирование базы данных на регулярной основе или перед любыми такими действиями.

Вы можете ввести ведение журнала таких действий, что позволило откатить действия до определенного момента времени.

Не существует конкретного лучшего способа, так как то, что наиболее подходит, будет / может зависеть от сложности / простоты / эффективности приложения.

Во всех случаях будет дополнительный код и сложность.

Короче говоря, такие соображения должны быть частью дизайна.

Дополнительный комментарий: -

Не могли бы вы показать мне, как я могу скопировать значение из оригинала таблица инфо лог таблицы с триггером?

Обратите внимание на следующее: -

DROP TRIGGER IF EXISTS logdelete;
DROP TABLE IF EXISTS main;
DROP TABLE IF EXISTS logtable;
CREATE TABLE IF NOT EXISTS logtable (timestamp TEXT DEFAULT CURRENT_TIMESTAMP, logaction TEXT, val1 TEXT, val2 TEXT, val3 TEXT);
CREATE TABLE IF NOT EXISTS main (id INTEGER PRIMARY KEY, val1 TEXT, val2 TEXT, val3 TEXT);
CREATE TRIGGER IF NOT EXISTS logdelete AFTER DELETE ON main
    BEGIN
        INSERT INTO logtable (logaction, val1, val2, val3) VALUES('DLT',old.val1,old.val2,old.val3);
    END
;
INSERT INTO main (val1,val2,val3) VALUES
    ('A','B','C'),('D','E','F'),('G','H','I'),('J','K','L'),('M','N','O');
SELECT * FROM main;
SELECT * FROM logtable;
DELETE FROM main WHERE val1 IN ('D','J');
SELECT * FROM main;
SELECT * FROM logtable;
INSERT INTO main (val1,val2,val3) SELECT val1,val2,val3 FROM logtable WHERE logaction = 'DLT';
UPDATE logtable SET logaction = 'DLTDONE' WHERE logaction = 'DLT';
SELECT * FROM main;
SELECT * FROM logtable;

Это

  1. УДАЛЯЕТ триггеры всех таблиц (для удобства повторного запуска), затем
  2. СОЗДАЕТ таблицу регистрации протоколируется согласно основной таблице, НО с двумя дополнительными столбцами для даты и времени (отметка времени) удаления и предпринятого действия.
  3. СОЗДАЕТ основную таблицу main , со столбцом ID (псевдоним столбца rowid) и 3 столбцами для значений, а именно val1 , val2 и val3
  4. СОЗДАЕТ ТРИГГЕР для случая, когда строка удаляется из основной таблицы, в которую вставляется записываемая запись .
  5. ВСТАВЛЯЕТ несколько строк в основную таблицу.
  6. ВЫБИРАЕТ все строки из таблицы main и затем logtable (result1 и result2), чтобы показать данные перед любыми удалениями.
  7. УДАЛЯЕТ несколько строк (2-й и 4-й в соответствии со столбцом ID).
  8. ВЫБИРАЕТ все строки из таблицы main , а затем из logtable (result3 и result4) для отображения данных после удалений.
  9. ROLLS BACK УДАЛЕНИЯ, сделанные на main в соответствии с logtable , согласно ALL (предложение WHERE может использовать дату для отката ко времени или к периоду)
  10. ОБНОВЛЯЕТ журнальную таблицу 1072 * (может быть триггером, но выполнено в соответствии с теми же критериями, что и ROLL BACK в этом примере), чтобы отразить откаты.
  11. ВЫБИРАЕТ все строки из таблицы main , а затем из logtable (result5 и result6) для отображения данных после отката.

Результаты: -

1 основной

  • после вставки данных

enter image description here

2 журналируемых

  • (пусто, без удалений) (изображение не обязательно)

3 основных

  • после удаления (остаются 3 или 5 оригинальных строк)

enter image description here

4 журналируемых

  • теперь имеет 2 записи в журнале с действием удаления и удаленными данными

enter image description here

5 основных

  • после отката (вставка удаленных строк. ПРИМЕЧАНИЕ new id (хотя может установить id в соответствии с оригиналом))

enter image description here

6 журналируемых

  • после отката (записи в журнале теперь помечены как выполненные)

enter image description here

  • Обратите внимание, что журнальные строки, помеченные как DONE, эффективно соответствуют комментарию: -

Добавьте еще один столбец в соответствующие таблицы, чтобы отслеживать, является ли элемент «Удален». Тогда вы на самом деле не удалили бы никаких записей; ты бы просто мягкое удаление и восстановление, изменив значение этого столбца соответственно

но потенциально позволяет журналу быть удаленным.

Android-демо из вышеперечисленного: -

DatabasHelper DBHelper.java

public class DBHelper extends SQLiteOpenHelper {

    public static final String DBNAME = "mydb";
    public static final int DBVERSION = 1;

    public static final String TBL_MAIN = "main";
    public static final String TBL_LOGTABLE = "logtable";
    public static final String TRG_MAINDELETE = "logdelete";

    public static final String COL_MAIN_ID = BaseColumns._ID;
    public static final String COl_MAIN_VAL1 = "val1";
    public static final String COL_MAIN_VAL2 = "val2";
    public static final String COL_MAIN_VAL3 = "val3";

    public static final String COL_LOGTABLE_TIMESTAMP = "timestamp";
    public static final String COl_LOGTABLE_LOGACTION = "logaction";
    public static final String COL_LOGTABLE_VAL1 = COl_MAIN_VAL1;
    public static final String COL_LOGTABLE_VAL2 = COL_MAIN_VAL2;
    public static final String COL_LOGTABLE_VAL3 = COL_MAIN_VAL3;

    public static final String LOGACTION_DLTDONE = "DLTDONE";
    public static final String LOGACTION_DELETE = "DLT";

    private String main_crtsql = "CREATE TABLE IF NOT EXISTS " + TBL_MAIN + "(" +
            COL_MAIN_ID + " INTEGER PRIMARY KEY," +
            COl_MAIN_VAL1 + " TEXT," +
            COL_MAIN_VAL2 + " TEXT, " +
            COL_MAIN_VAL3 + " TEXT" +
            ")";

    private String logtable_crtsql = "CREATE TABLE IF NOT EXISTS " + TBL_LOGTABLE + "(" +
            COL_LOGTABLE_TIMESTAMP + " TEXT DEFAULT CURRENT_TIMESTAMP, " +
            COl_LOGTABLE_LOGACTION + " TEXT, " +
            COL_LOGTABLE_VAL1 + " TEXT, " +
            COL_LOGTABLE_VAL2 + " TEXT, " +
            COL_LOGTABLE_VAL3 + " TEXT " +
            ")";


    private String logdelete_crtsql = "CREATE TRIGGER IF NOT EXISTS " + TRG_MAINDELETE +
            " AFTER DELETE ON " + TBL_MAIN +
            " BEGIN " +
            "INSERT INTO " + TBL_LOGTABLE + "(" +
            COl_LOGTABLE_LOGACTION + "," +
            COL_LOGTABLE_VAL1 + "," +
            COL_LOGTABLE_VAL2 + "," +
            COL_LOGTABLE_VAL3 +
            ")" +
            " VALUES(" +
            "'DLT'," +
            "old." + COL_LOGTABLE_VAL1 + "," +
            "old." + COL_LOGTABLE_VAL2 + "," +
            "old." + COL_LOGTABLE_VAL3 +
            ")" +
            ";" +
            " END";


    SQLiteDatabase mDB;

    public DBHelper(Context context) {
        super(context, DBNAME, null, DBVERSION);
        mDB = this.getWritableDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(logtable_crtsql);
        db.execSQL(main_crtsql);
        db.execSQL(logdelete_crtsql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    public long addMain(String val1, String val2, String val3) {
        ContentValues cv = new ContentValues();
        cv.put(COl_MAIN_VAL1,val1);
        cv.put(COL_MAIN_VAL2,val2);
        cv.put(COL_MAIN_VAL3,val3);
        return mDB.insert(TBL_MAIN,null,cv);
    }

    public int deleteMain(String val) {
        String whereclause = COl_MAIN_VAL1 + "=?";
        String[] whereargs = new String[]{val};
        return mDB.delete(TBL_MAIN,whereclause,whereargs);
    }

    //INSERT INTO main (val1,val2,val3) SELECT val1,val2,val3 FROM logtable WHERE logaction = 'DLT';
    //UPDATE logtable SET logaction = 'DLTDONE' WHERE logaction = 'DLT';
    public void rollbackMain() {
        String columns = " (" + COl_MAIN_VAL1 + "," + COL_MAIN_VAL2 + "," +COL_LOGTABLE_VAL3 + ")";
        String values = " SELECT " + COL_LOGTABLE_VAL1 + "," + COL_LOGTABLE_VAL2 + "," + COL_LOGTABLE_VAL3 +
                " FROM " + TBL_LOGTABLE + " WHERE " + COl_LOGTABLE_LOGACTION + "='" + LOGACTION_DELETE + "'";
        String insertsql = "INSERT INTO " + TBL_MAIN + columns + values;
        mDB.beginTransaction();
        mDB.execSQL(insertsql);
        ContentValues cv = new ContentValues();
        cv.put(COl_LOGTABLE_LOGACTION,LOGACTION_DLTDONE);
        String wherecluase = COl_LOGTABLE_LOGACTION + "=?";
        String[] whereargs = new String[]{LOGACTION_DELETE};
        mDB.update(TBL_LOGTABLE,cv,wherecluase,whereargs);
        mDB.setTransactionSuccessful();
        mDB.endTransaction();
    }

    public void logtables() {
        Cursor csr = mDB.query(TBL_MAIN,null,null,null,null,null,null);
        DatabaseUtils.dumpCursor(csr);
        csr = mDB.query(TBL_LOGTABLE,null,null,null,null,null,null);
        DatabaseUtils.dumpCursor(csr);
        csr.close();
    }
}

Активность - MainActivity.java

public class MainActivity extends AppCompatActivity {

    DBHelper mDBHlpr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDBHlpr = new DBHelper(this);
        // Empty main and logtable
        mDBHlpr.getWritableDatabase().delete(DBHelper.TBL_MAIN,null,null);
        mDBHlpr.getWritableDatabase().delete(DBHelper.TBL_LOGTABLE,null,null);
        // Add some data
        mDBHlpr.addMain("Fred","Banana","Rock");
        mDBHlpr.addMain("Mary","Orange","Scissors");
        mDBHlpr.addMain("Sue","Apple","Paper");
        mDBHlpr.logtables();
        //Delete some data
        mDBHlpr.deleteMain("Mary");
        mDBHlpr.deleteMain("MrNobody");
        mDBHlpr.deleteMain("Sue");
        mDBHlpr.logtables();
        //Rollback
        mDBHlpr.rollbackMain();
        mDBHlpr.logtables();
    }
}

Результат

(эквивалентно предыдущим результатам, хотя и другим данным)

1

2019-03-27 12:44:37.136  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@7f0608d
2019-03-27 12:44:37.137  I/System.out: 0 {
2019-03-27 12:44:37.137  I/System.out:    _id=1
2019-03-27 12:44:37.137  I/System.out:    val1=Fred
2019-03-27 12:44:37.137  I/System.out:    val2=Banana
2019-03-27 12:44:37.137  I/System.out:    val3=Rock
2019-03-27 12:44:37.137  I/System.out: }
2019-03-27 12:44:37.137  I/System.out: 1 {
2019-03-27 12:44:37.137  I/System.out:    _id=2
2019-03-27 12:44:37.137  I/System.out:    val1=Mary
2019-03-27 12:44:37.137  I/System.out:    val2=Orange
2019-03-27 12:44:37.137  I/System.out:    val3=Scissors
2019-03-27 12:44:37.137  I/System.out: }
2019-03-27 12:44:37.137  I/System.out: 2 {
2019-03-27 12:44:37.137  I/System.out:    _id=3
2019-03-27 12:44:37.137  I/System.out:    val1=Sue
2019-03-27 12:44:37.138  I/System.out:    val2=Apple
2019-03-27 12:44:37.138  I/System.out:    val3=Paper
2019-03-27 12:44:37.138  I/System.out: }
2019-03-27 12:44:37.138  I/System.out: <<<<<

2

2019-03-27 12:44:37.138  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8d24242
2019-03-27 12:44:37.138  I/System.out: <<<<<

3.

2019-03-27 12:44:37.140  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@2c28253
2019-03-27 12:44:37.140  I/System.out: 0 {
2019-03-27 12:44:37.140  I/System.out:    _id=1
2019-03-27 12:44:37.141  I/System.out:    val1=Fred
2019-03-27 12:44:37.141  I/System.out:    val2=Banana
2019-03-27 12:44:37.141  I/System.out:    val3=Rock
2019-03-27 12:44:37.141  I/System.out: }
2019-03-27 12:44:37.141  I/System.out: <<<<<

4

2019-03-27 12:44:37.142  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@fb8f790
2019-03-27 12:44:37.142  I/System.out: 0 {
2019-03-27 12:44:37.142  I/System.out:    timestamp=2019-03-27 01:44:37
2019-03-27 12:44:37.142  I/System.out:    logaction=DLT
2019-03-27 12:44:37.142  I/System.out:    val1=Mary
2019-03-27 12:44:37.142  I/System.out:    val2=Orange
2019-03-27 12:44:37.142  I/System.out:    val3=Scissors
2019-03-27 12:44:37.143  I/System.out: }
2019-03-27 12:44:37.143  I/System.out: 1 {
2019-03-27 12:44:37.143  I/System.out:    timestamp=2019-03-27 01:44:37
2019-03-27 12:44:37.143  I/System.out:    logaction=DLT
2019-03-27 12:44:37.143  I/System.out:    val1=Sue
2019-03-27 12:44:37.143  I/System.out:    val2=Apple
2019-03-27 12:44:37.143  I/System.out:    val3=Paper
2019-03-27 12:44:37.143  I/System.out: }
2019-03-27 12:44:37.144  I/System.out: <<<<<

5.

2019-03-27 12:44:37.145  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@68f7889
2019-03-27 12:44:37.145  I/System.out: 0 {
2019-03-27 12:44:37.146  I/System.out:    _id=1
2019-03-27 12:44:37.146  I/System.out:    val1=Fred
2019-03-27 12:44:37.146  I/System.out:    val2=Banana
2019-03-27 12:44:37.146  I/System.out:    val3=Rock
2019-03-27 12:44:37.146  I/System.out: }
2019-03-27 12:44:37.146  I/System.out: 1 {
2019-03-27 12:44:37.146  I/System.out:    _id=2
2019-03-27 12:44:37.146  I/System.out:    val1=Mary
2019-03-27 12:44:37.146  I/System.out:    val2=Orange
2019-03-27 12:44:37.146  I/System.out:    val3=Scissors
2019-03-27 12:44:37.146  I/System.out: }
2019-03-27 12:44:37.146  I/System.out: 2 {
2019-03-27 12:44:37.146  I/System.out:    _id=3
2019-03-27 12:44:37.146  I/System.out:    val1=Sue
2019-03-27 12:44:37.146  I/System.out:    val2=Apple
2019-03-27 12:44:37.146  I/System.out:    val3=Paper
2019-03-27 12:44:37.146  I/System.out: }
2019-03-27 12:44:37.146  I/System.out: <<<<<

6.

2019-03-27 12:44:37.147  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@4c6408e
2019-03-27 12:44:37.147  I/System.out: 0 {
2019-03-27 12:44:37.147  I/System.out:    timestamp=2019-03-27 01:44:37
2019-03-27 12:44:37.147  I/System.out:    logaction=DLTDONE
2019-03-27 12:44:37.147  I/System.out:    val1=Mary
2019-03-27 12:44:37.147  I/System.out:    val2=Orange
2019-03-27 12:44:37.147  I/System.out:    val3=Scissors
2019-03-27 12:44:37.147  I/System.out: }
2019-03-27 12:44:37.147  I/System.out: 1 {
2019-03-27 12:44:37.147  I/System.out:    timestamp=2019-03-27 01:44:37
2019-03-27 12:44:37.148  I/System.out:    logaction=DLTDONE
2019-03-27 12:44:37.148  I/System.out:    val1=Sue
2019-03-27 12:44:37.148  I/System.out:    val2=Apple
2019-03-27 12:44:37.148  I/System.out:    val3=Paper
2019-03-27 12:44:37.148  I/System.out: }
2019-03-27 12:44:37.148  I/System.out: <<<<<
...