简体   繁体   English

在文本文件中单独记录 Room sqlite 数据库操作

[英]Seperately log Room sqlite database operations in text file

I would like to log all database insert , delete , update operations in a text file .我想在一个text file记录所有数据库insertdeleteupdate操作。

Scenario:设想:

I use room local database and api, when user is offline I would like to keep record of operations in a text file since after syncing those records wil be deleted.我使用房间本地数据库和api,当用户离线时,我想将操作记录保存在text file因为同步后这些记录将被删除。 And just to make sure if something goes wrong and syncronization fails, I will still have access to text file .并且只是为了确保出现问题并且同步失败,我仍然可以访问text file . Or is there any other recommended approach?或者有没有其他推荐的方法?

If you want some form of textual representation what could potentially drive a restore, rather than taking copies of the database then you could utilise TRIGGERS that record(log) the data to a table used for logging.如果您想要某种形式的文本表示,什么可能会驱动恢复,而不是复制数据库,那么您可以使用TRIGGERS将数据记录(记录)到用于记录的表中。

However, currently Room doesn't support building creating triggers from annotations.但是,目前 Room 不支持从注释构建创建触发器。 So they need to be built and added by running suitable code.所以它们需要通过运行合适的代码来构建和添加。 Running such code would probably be via a CallBack and probably the onCreate callback.运行这样的代码可能是通过 CallBack 和 onCreate 回调。

Here's a simple (as in just a single table) example that logs Inserts into the main table (Table1) recording them into the LogTable:-这是一个简单的(就像在单个表中一样)示例,该示例将插入记录到主表(表 1)中,并将它们记录到 LogTable 中:-

First the supportive LogTable , that includes some supportive Constants and methods :-首先是支持性的LogTable ,其中包括一些支持性的常量和方法:-

@Entity(tableName = LogTable.TABLE_NAME)
public class LogTable {
    public static final int ACTION_AFTER_INSERT = 11;
    public static final int ACTION_BEFORE_UPDATE = 1;
    public static final int ACTION_AFTER_UPDATE = 2;
    public static final int ACTION_BEFORE_DELETE = 3;
    public static final String SEPARATOR = "~";

    public static final String TABLE_NAME = "log";
    public static final String ID_COL = TABLE_NAME + BaseColumns._ID;
    public static final String TIMESTAMP_COL = "_timestamp";
    public static final String TABLENAME_COL = "_tablename";
    public static final String ACTION_COL = "_action";
    public static final String DATA_COL = "_data";

    @PrimaryKey
    @ColumnInfo(name = ID_COL)
    Long id;
    @ColumnInfo(name = TIMESTAMP_COL,defaultValue = "(strftime('%s','now'))")
    Long timestamp;
    @ColumnInfo(name = TABLENAME_COL)
    String tableName;
    @ColumnInfo(name = ACTION_COL)
    int action;
    @ColumnInfo(name = DATA_COL)
    String data;

    public static String actionAsString(int action) {
        switch (action) {
            case ACTION_AFTER_INSERT:
                return "INSERT";
            case ACTION_AFTER_UPDATE:
                return "AFTER UPDATE";
            case ACTION_BEFORE_UPDATE:
                return "BEFORE UPDATE";
            case ACTION_BEFORE_DELETE:
                return "DELETE";
            default:
                return "UNKNOWN";
        }
    }
}
  • the log table is designed to cater for any table and hence the tablename.日志表旨在迎合任何表,因此表名。 The data column will contain a potentially usable list of the values.数据列将包含可能可用的值列表。

The main table Table1 which includes it's INSERT trigger SQL (you need similar for DELETE and UPDATE (perhaps before and after)) :-主表Table1包括它的 INSERT 触发器 SQL(您需要类似的 DELETE 和 UPDATE(可能之前和之后)):-

public class Table1 {
    public static final String TABLE_NAME = "t1";
    public static final String ID_COLUMN = TABLE_NAME + BaseColumns._ID;
    public static final String NAME_COLUMN = TABLE_NAME + "_name";
    public static final String OTHER_COLUMN = TABLE_NAME + "_other";

    public static final String LOGCOLVALUES =
            "'" + TABLE_NAME + "',"
            + LogTable.ACTION_AFTER_INSERT + ","
            + "'" + ID_COLUMN+  "='|| new." + ID_COLUMN + "||'" + LogTable.SEPARATOR
            +  NAME_COLUMN + "='|| new." + NAME_COLUMN + "||'" + LogTable.SEPARATOR
            + OTHER_COLUMN + "='|| new." + OTHER_COLUMN
            ;


    @PrimaryKey
    @ColumnInfo(name = ID_COLUMN)
    Long id;
    @ColumnInfo(name = NAME_COLUMN)
    String name;
    @ColumnInfo(name = OTHER_COLUMN)
    String other;

    public static final String INSERT_TRIGGER_SQL =
            "CREATE TRIGGER IF NOT EXISTS " + TABLE_NAME + "_insert " +
                    "AFTER INSERT ON " + Table1.TABLE_NAME +
                    " BEGIN " +
                    " INSERT INTO " + LogTable.TABLE_NAME +
                    "(" +
                    LogTable.TABLENAME_COL + "," +
                    LogTable.ACTION_COL + "," +
                    LogTable.DATA_COL +
                    ")" +
                    " VALUES(" +
                    LOGCOLVALUES +
                    "); END;";

}
  • The LOGCOLVALUES constant generates the relatively complex VALUES clause. LOGCOLVALUES 常量生成相对复杂的 VALUES 子句。
  • the new.新的。 refers to the data being updated.指正在更新的数据。 Noting that for an UPDATE new.注意到对于一个 UPDATE 新的。 refers to the updated value whilst old.指旧时更新的值。 refers to the data before the update.指的是更新前的数据。 For an INSERT only new.对于仅插入新的。 is applicable, for DELETE only old.是适用的,对于 DELETE 仅旧的。

For the above the trigger SQL, when resolved, is :-对于上述触发 SQL,当解决时,是:-

CREATE TRIGGER IF NOT EXISTS t1_insert AFTER INSERT ON t1 BEGIN  INSERT INTO log(_tablename,_action,_data) VALUES('t1',11,'t1_id='|| new.t1_id||'~t1_name='|| new.t1_name||'~t1_other='|| new.t1_other); END;

The Dao's used for the example in AllDao are simply :- AllDao中用于示例的Dao很简单:-

@Dao
abstract class AllDao {

    @Insert
    abstract long insert(Table1 table1);
    @Query("SELECT * FROM log")
    abstract List<LogTable> getAllLogs();
}

The @Database class TheDatabase , where the TRIGGER is build in the onCreate method is :- @Database 类TheDatabase ,其中在onCreate方法中构建的 TRIGGER 是:-

@Database(entities = {Table1.class,LogTable.class},version = 1)
abstract class TheDatabase extends RoomDatabase {

    abstract AllDao getAllDao();

    private static volatile TheDatabase instance = null;

    public static TheDatabase getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(context,TheDatabase.class,"my.db")
                    .addCallback(cb)
                    .allowMainThreadQueries()
                    .build();
        }
        return instance;
    }

    private static Callback cb = new Callback() {
        @Override
        public void onCreate(SupportSQLiteDatabase db) {
            super.onCreate(db);
            db.execSQL(Table1.INSERT_TRIGGER_SQL);
        }

        @Override
        public void onOpen(SupportSQLiteDatabase db) {
            super.onOpen(db);
        }

        @Override
        public void onDestructiveMigration(SupportSQLiteDatabase db) {
            super.onDestructiveMigration(db);
        }
    };
}
  • obviously with a full implementation then each "main" table would have 3 triggers (update, insert and delete).显然,如果有一个完整的实现,那么每个“主”表将有 3 个触发器(更新、插入和删除)。

Putting all the above into a working example is MainActivity , which inserts 10 rows and then extracts data from the log :-将上述所有内容放入一个工作示例中的是MainActivity ,它插入 10 行,然后从日志中提取数据:-

public class MainActivity extends AppCompatActivity {

    TheDatabase db;
    AllDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        db = TheDatabase.getInstance(this);
        dao = db.getAllDao();

        Table1 t1 = new Table1();
        for(int i=0;i < 10; i++) {
            t1.name = "Test"+i;
            t1.other = "TestOtherData"+i;
            dao.insert(t1);
        }
        for(LogTable l: dao.getAllLogs()) {
            Log.d(
                    "LOGTABLEINFO",
                    "ID = " + l.id +
                            " TS = " + l.timestamp +
                            " Table =" + l.tableName +
                            " Action = " + LogTable.actionAsString(l.action) +
                            " Values = " + l.data
            );
        }
    }
}

RESULT结果

When run (twice) then the log contains :-运行(两次)时,日志包含:-

2021-07-24 10:26:58.830 D/LOGTABLEINFO: ID = 1 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=1~t1_name=Test0~t1_other=TestOtherData0
2021-07-24 10:26:58.830 D/LOGTABLEINFO: ID = 2 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=2~t1_name=Test1~t1_other=TestOtherData1
2021-07-24 10:26:58.830 D/LOGTABLEINFO: ID = 3 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=3~t1_name=Test2~t1_other=TestOtherData2
2021-07-24 10:26:58.831 D/LOGTABLEINFO: ID = 4 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=4~t1_name=Test3~t1_other=TestOtherData3
2021-07-24 10:26:58.831 D/LOGTABLEINFO: ID = 5 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=5~t1_name=Test4~t1_other=TestOtherData4
2021-07-24 10:26:58.831 D/LOGTABLEINFO: ID = 6 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=6~t1_name=Test5~t1_other=TestOtherData5
2021-07-24 10:26:58.831 D/LOGTABLEINFO: ID = 7 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=7~t1_name=Test6~t1_other=TestOtherData6
2021-07-24 10:26:58.831 D/LOGTABLEINFO: ID = 8 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=8~t1_name=Test7~t1_other=TestOtherData7
2021-07-24 10:26:58.831 D/LOGTABLEINFO: ID = 9 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=9~t1_name=Test8~t1_other=TestOtherData8
2021-07-24 10:26:58.831 D/LOGTABLEINFO: ID = 10 TS = 1627084992 Table =t1 Action = INSERT Values = t1_id=10~t1_name=Test9~t1_other=TestOtherData9
2021-07-24 10:26:58.831 D/LOGTABLEINFO: ID = 11 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=11~t1_name=Test0~t1_other=TestOtherData0
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 12 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=12~t1_name=Test1~t1_other=TestOtherData1
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 13 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=13~t1_name=Test2~t1_other=TestOtherData2
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 14 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=14~t1_name=Test3~t1_other=TestOtherData3
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 15 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=15~t1_name=Test4~t1_other=TestOtherData4
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 16 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=16~t1_name=Test5~t1_other=TestOtherData5
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 17 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=17~t1_name=Test6~t1_other=TestOtherData6
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 18 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=18~t1_name=Test7~t1_other=TestOtherData7
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 19 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=19~t1_name=Test8~t1_other=TestOtherData8
2021-07-24 10:26:58.832 D/LOGTABLEINFO: ID = 20 TS = 1627086418 Table =t1 Action = INSERT Values = t1_id=20~t1_name=Test9~t1_other=TestOtherData9

Supplementary补充

Taking the above a little further here's an example with all 4 Triggers, the Triggers being built by methods in the LogTable Entity/Class :-更进一步,这里有一个包含所有 4 个触发器的示例,触发器由LogTable Entity/Class 中的方法构建:-

Table1 has been simplified as LogTable builds the triggers based upon the trigger action, the table name and the table's columns.表 1已被简化,因为 LogTable 基于触发器操作、表名和表的列构建触发器。 It is now:-就是现在:-

@Entity(tableName = TABLE_NAME)
public class Table1 {
    public static final String TABLE_NAME = "t1";
    public static final String ID_COLUMN = TABLE_NAME + BaseColumns._ID;
    public static final String NAME_COLUMN = TABLE_NAME + "_name";
    public static final String OTHER_COLUMN = TABLE_NAME + "_other";

    @PrimaryKey
    @ColumnInfo(name = ID_COLUMN)
    Long id;
    @ColumnInfo(name = NAME_COLUMN)
    String name;
    @ColumnInfo(name = OTHER_COLUMN)
    String other;

}

LogTable has been changed to be :- LogTable已更改为:-

@Entity(tableName = LogTable.TABLE_NAME)
public class LogTable {
    public static final int ACTION_AFTER_INSERT = 11;
    public static final int ACTION_BEFORE_UPDATE = 1;
    public static final int ACTION_AFTER_UPDATE = 2;
    public static final int ACTION_BEFORE_DELETE = 3;
    public static final String SEPARATOR = "~";

    public static final String TABLE_NAME = "log";
    public static final String ID_COL = TABLE_NAME + BaseColumns._ID;
    public static final String TIMESTAMP_COL = "_timestamp";
    public static final String TABLENAME_COL = "_tablename";
    public static final String ACTION_COL = "_action";
    public static final String DATA_COL = "_data";

    @PrimaryKey
    @ColumnInfo(name = ID_COL)
    Long id;
    @ColumnInfo(name = TIMESTAMP_COL,defaultValue = "(strftime('%s','now'))")
    Long timestamp;
    @ColumnInfo(name = TABLENAME_COL)
    String tableName;
    @ColumnInfo(name = ACTION_COL)
    int action;
    @ColumnInfo(name = DATA_COL)
    String data;

    public static String actionAsString(int action) {
        switch (action) {
            case ACTION_AFTER_INSERT:
                return "AFTER INSERT";
            case ACTION_AFTER_UPDATE:
                return "AFTER UPDATE";
            case ACTION_BEFORE_UPDATE:
                return "BEFORE UPDATE";
            case ACTION_BEFORE_DELETE:
                return "AFTER DELETE";
            default:
                return "UNKNOWN";
        }
    }
    public static String buildTriggerSQL(int action, String tableName, String[] columns) {
        return buildTriggerSQL(action,tableName,columns,false);
    }

    public static String buildTriggerSQL(int action, String tableName, String[] columns, boolean logResult) {

        // Prepare the appropriate old or new for extracting data
        String retrieve;
        switch (action) {
            case ACTION_AFTER_INSERT: case ACTION_AFTER_UPDATE:
                retrieve = "new.";
                break;
            default:
                retrieve = "old.";
        }
        StringBuilder sb = new StringBuilder().append("CREATE TRIGGER IF NOT EXISTS ");
        // TriggerName
        sb
                // Trigger Name
                .append("_")
                .append(tableName)
                .append("_")
                .append(actionAsString(action).replace(' ','_'))
                // Trigger Action ON clause
                .append(" ").append(actionAsString(action)).append(" ON  ").append(tableName)
                .append(" BEGIN INSERT INTO ").append(TABLE_NAME).append("(")
                // The Log Table columns to be set (ID and timestamp generated by defaults)
                .append(TABLENAME_COL).append(",").append(ACTION_COL).append(",").append(DATA_COL).append(") ")
                // The fixed VALUES to be inserted
                .append(" VALUES(")
                .append("'").append(tableName).append("',")
                .append(action).append(",")
        ;
        // Build the values to be extracted from the changed table for the log table's data column
        boolean afterFirst = false;
        for (String s: columns) {
            // if not first line add separator between the previous and next
            if (afterFirst) {
                sb.append("||'").append(SEPARATOR).append("'||");
            }
            sb.append("'").append(s).append("='|| ")
                    .append(retrieve).append(s);
            afterFirst = true;
        }
        if (logResult) Log.d("LOGTBL_BLDTRGSQL","Trigger SQL = \n\t" + sb.append("); END;").toString());
        return sb.append("); END;").toString();
    }
}
  • TRIGGERS are named触发器被命名
  • _<the_tablename>_<the_action_with_spaces_replaced_by_underscores>

AllDao has been modified to include update and delete and also getById :- AllDao已被修改为包括更新和删除以及getById :-

@Dao
abstract class AllDao {

    @Insert
    abstract long insert(Table1 table1);
    @Query("SELECT * FROM log")
    abstract List<LogTable> getAllLogs();
    @Update
    abstract int update(Table1 table1);
    @Delete
    abstract int delete(Table1 table1);
    @Query("SELECT * FROM t1 WHERE t1_id =:id")
    abstract Table1 getTable1ById(long id);
}

TheDatabase now builds all 4 TRIGGERS and is :- TheDatabase现在构建了所有 4 个触发器,并且是:-

@Database(entities = {Table1.class,LogTable.class},version = 1)
abstract class TheDatabase extends RoomDatabase {

    abstract AllDao getAllDao();

    private static volatile TheDatabase instance = null;

    public static TheDatabase getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(context,TheDatabase.class,"my.db")
                    .addCallback(cb)
                    .allowMainThreadQueries()
                    .build();
        }
        return instance;
    }

    private static Callback cb = new Callback() {
        @Override
        public void onCreate(SupportSQLiteDatabase db) {
            super.onCreate(db);
            String[] table1Columns = new String[]{Table1.ID_COLUMN,Table1.NAME_COLUMN,Table1.OTHER_COLUMN};
            db.execSQL(LogTable.buildTriggerSQL(LogTable.ACTION_AFTER_INSERT,Table1.TABLE_NAME,table1Columns));
            db.execSQL(LogTable.buildTriggerSQL(LogTable.ACTION_BEFORE_UPDATE,Table1.TABLE_NAME,table1Columns));
            db.execSQL(LogTable.buildTriggerSQL(LogTable.ACTION_AFTER_UPDATE,Table1.TABLE_NAME,table1Columns));
            db.execSQL(LogTable.buildTriggerSQL(LogTable.ACTION_BEFORE_DELETE,Table1.TABLE_NAME,table1Columns));
        }

        @Override
        public void onOpen(SupportSQLiteDatabase db) {
            super.onOpen(db);
        }

        @Override
        public void onDestructiveMigration(SupportSQLiteDatabase db) {
            super.onDestructiveMigration(db);
        }
    };
}

Finally MainActivity includes update and delete :-最后MainActivity包括更新和删除:-

public class MainActivity extends AppCompatActivity {

    TheDatabase db;
    AllDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        db = TheDatabase.getInstance(this);
        dao = db.getAllDao();

        Table1 t1 = new Table1();
        for(int i=0;i < 10; i++) {
            t1.name = "Test"+i;
            t1.other = "TestOtherData"+i;
            dao.insert(t1);
        }
        t1.id = 1L;
        t1.other = "Updated Other Data";
        dao.update(t1);
        dao.delete(dao.getTable1ById(5L));
        for(LogTable l: dao.getAllLogs()) {
            Log.d(
                    "LOGTABLEINFO",
                    "ID = " + l.id +
                            " TS = " + l.timestamp +
                            " Table =" + l.tableName +
                            " Action = " + LogTable.actionAsString(l.action) +
                            " Values = " + l.data
            );
        }
    }
}

Results :-结果:-

2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 1 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=1~t1_name=Test0~t1_other=TestOtherData0
2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 2 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=2~t1_name=Test1~t1_other=TestOtherData1
2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 3 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=3~t1_name=Test2~t1_other=TestOtherData2
2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 4 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=4~t1_name=Test3~t1_other=TestOtherData3
2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 5 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=5~t1_name=Test4~t1_other=TestOtherData4
2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 6 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=6~t1_name=Test5~t1_other=TestOtherData5
2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 7 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=7~t1_name=Test6~t1_other=TestOtherData6
2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 8 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=8~t1_name=Test7~t1_other=TestOtherData7
2021-07-25 13:53:33.800 D/LOGTABLEINFO: ID = 9 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=9~t1_name=Test8~t1_other=TestOtherData8
2021-07-25 13:53:33.801 D/LOGTABLEINFO: ID = 10 TS = 1627185213 Table =t1 Action = AFTER INSERT Values = t1_id=10~t1_name=Test9~t1_other=TestOtherData9
2021-07-25 13:53:33.801 D/LOGTABLEINFO: ID = 11 TS = 1627185213 Table =t1 Action = BEFORE UPDATE Values = t1_id=1~t1_name=Test0~t1_other=TestOtherData0
2021-07-25 13:53:33.801 D/LOGTABLEINFO: ID = 12 TS = 1627185213 Table =t1 Action = AFTER UPDATE Values = t1_id=1~t1_name=Test9~t1_other=Updated Other Data
2021-07-25 13:53:33.801 D/LOGTABLEINFO: ID = 13 TS = 1627185213 Table =t1 Action = AFTER DELETE Values = t1_id=5~t1_name=Test4~t1_other=TestOtherData4

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM