美文网首页
GreenDao数据库升级解决方案

GreenDao数据库升级解决方案

作者: 做好事不留名_ | 来源:发表于2019-12-02 21:31 被阅读0次
主要思路:
  1. 删除表不改动
  2. 新增表:创建新表
  3. 增加新字段:在原有表上直接新增字段
  4. 修改或删除原有字段:数据迁移
package com.xh.acldlib.db;

import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.xh.acldlib.BuildConfig;
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.internal.DaoConfig;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据库升级类
 * please call {@link #upgrade(Database, List)} (Database, List)}
 * <p>
 * Created by LYF on 2018/1/19.
 */
public class UpgradeHelper {
    private static boolean DEBUG = BuildConfig.DEBUG;
    private static final String SQLITE_MASTER = "sqlite_master";
    private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master";

    private static final String NULL = "NULL";//空类型
    private static final String TEXT = "TEXT";//文本类型
    private static final String INTEGER = "INTEGER";//整形
    private static final String BOOLEAN = "BOOLEAN";//boolean类型
    private static final String REAL = "REAL";//浮点型
    private static final String BLOB = "BLOB";//Byte[]类型

    /**
     * 升级
     *
     * @param database   Database
     * @param daoClasses 有变更表的表
     */
    public static void upgrade(@Nullable Database database, @Nullable List<Class<? extends AbstractDao<?, ?>>> daoClasses) {
        if (database == null || daoClasses == null) {
            return;
        }
        log("", "********************** 开始数据库升级 **********************");
        for (Class<? extends AbstractDao<?, ?>> daoClass : daoClasses) {
            DaoConfig daoConfig = new DaoConfig(database, daoClass);
            String tableName = daoConfig.tablename;
            if (!isTableExists(database, false, tableName)) {
                //发现新表
                createTable(database, daoClass);
                log(tableName, "新增表");
                continue;
            }

            // 获取本地数据库中表的所有列
            Map<String, String> oldColumns = getColumns2(database, tableName);
            Map<String, String> newColumns = covertMap(daoConfig.properties);
            if (isEquals(oldColumns, newColumns)) {
                //相同不做处理
                continue;
            }

            Map<String, String> diffMap = getDiff(oldColumns, newColumns);

            if (diffMap == null || diffMap.size() <= 0) {
                //旧表中的字段新表中都包含,直接走新增字段的操作
                //新增的字段
                diffMap = getDiff(newColumns, oldColumns);
                if (diffMap != null && diffMap.size() > 0) {
                    //表中新增字段操作
                    addColumn(database, tableName, diffMap);
                    log(tableName, "新增字段:" + getStringByMap(diffMap));
                }
                continue;
            }

            log("", "      ");
            log(tableName, "表中修改了字段或者删除了字段:" + getStringByMap(diffMap));
            log("", "《《《《《数据迁移开始》》》》》");

            //表中修改了字段或者删除了字段,要走导表的流程(SQLite)暂不支持直接删字段或这修改字段
            //如果已经存在临时表,则先删除
            String tempTableName = tableName.concat("_TEMP");
            database.execSQL("DROP TABLE IF EXISTS " + tempTableName + ";");
            log("", "删除临时表 --->>> " + tempTableName);

            //创建临时表,并将原有表的值复制过来
            database.execSQL("CREATE TEMPORARY TABLE " + tempTableName + " AS SELECT * FROM " + tableName + ";");
            log("", "创建临时表:" + tempTableName + " --->>> 并将原有表的值复制过来:" + tableName);

            //删除原表
            deleteTable(database, daoClass);
            log("", "删除表 --->>> " + tableName);
            //创建新表
            createTable(database, daoClass);
            log("", "创建表 --->>> " + tableName);

            if (newColumns != null) {
                List<String> newList = new ArrayList<>();
                List<String> oldList = new ArrayList<>();
                for (Map.Entry<String, String> entry : newColumns.entrySet()) {
                    String columnName = entry.getKey();
                    if (oldColumns.containsKey(columnName)) {
                        newList.add(columnName);
                        oldList.add(columnName);
                    } else {
                        switch (entry.getValue()) {
                            case INTEGER:
                            case BOOLEAN:
                            case REAL:
                                newList.add(columnName);
                                oldList.add("0");
                                break;
                            default:
                                newList.add(columnName);
                                oldList.add(null);
                                break;
                        }
                    }
                }
                if (newList.size() > 0) {
                    //将临时表中的数据插入新表中
                    String insertSql = "INSERT INTO " + tableName + " (" +
                            TextUtils.join(",", newList) +
                            ") SELECT " +
                            TextUtils.join(",", oldList) +
                            " FROM " + tempTableName + ";";
                    database.execSQL(insertSql);
                    log("", "导入数据 " + tempTableName + " --->>> " + tableName);
                }
            }
            //删除临时表
            database.execSQL("DROP TABLE " + tempTableName);
            log("", "删除临时表 --->>> " + tempTableName);
            log(tableName, "《《《《《数据迁移结束》》》》》\r\n");
            log("", "      ");
        }
        log("", "********************** 数据库升级结束 **********************");
    }

    /**
     * 转换Map
     *
     * @param properties Property数组
     * @return 转换后的 Map<String, String>
     */
    @Nullable
    private static Map<String, String> covertMap(@Nullable Property[] properties) {
        if (properties == null || properties.length <= 0) {
            return null;
        }
        Map<String, String> map = new HashMap<>();
        for (Property property : properties) {
            map.put(property.columnName, getTypeByClass(property.type));
        }
        return map;
    }

    /**
     * 对面两个Map是否相同
     *
     * @param map1 Map1 Map<String, String>
     * @param map2 Map2 Map<String, String>
     * @return 内容完全一样也算相同
     */
    private static boolean isEquals(@Nullable Map<String, String> map1, @Nullable Map<String, String> map2) {
        if (map1 == map2) {
            return true;
        }
        if (map1 != null && map2 != null && map1.size() == map2.size()) {
            for (Map.Entry<String, String> entry : map1.entrySet()) {
                String columnName = entry.getKey();
                if (!map2.containsKey(columnName)) {
                    return false;
                }
                if (!TextUtils.equals(map2.get(columnName), entry.getValue())) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 获取map1中有,map2中没有的数据
     *
     * @param map1 Map1 Map<String, String>
     * @param map2 Map2 Map<String, String>
     * @return 列名与类型 Map<columnName, Type>
     */
    @Nullable
    private static Map<String, String> getDiff(@Nullable Map<String, String> map1, @Nullable Map<String, String> map2) {
        if (map1 == null || map1.size() <= 0) {
            return null;
        }
        if (map2 == null || map2.size() <= 0) {
            return map1;
        }

        Map<String, String> diffMap = new HashMap<>();

        for (Map.Entry<String, String> entry : map1.entrySet()) {
            String columnName = entry.getKey();
            String type = entry.getValue();
            if (map2.containsKey(columnName) && TextUtils.equals(map2.get(columnName), type)) {
                continue;
            }
            diffMap.put(columnName, type);
        }
        return diffMap;
    }

    /**
     * 判断表是否存在
     *
     * @param db        Database
     * @param isTemp    是否为临时表,true临时表
     * @param tableName 表明
     * @return true存在,false不存在
     */
    private static boolean isTableExists(Database db, boolean isTemp, String tableName) {
        if (db == null || TextUtils.isEmpty(tableName)) {
            return false;
        }
        String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER;
        String sql = "SELECT COUNT(*) FROM " + dbName + " WHERE type = ? AND name = ?";
        Cursor cursor = null;
        int count = 0;
        try {
            cursor = db.rawQuery(sql, new String[]{"table", tableName});
            if (cursor == null || !cursor.moveToFirst()) {
                return false;
            }
            count = cursor.getInt(0);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return count > 0;
    }

    /**
     * 创建表
     *
     * @param database   Database
     * @param daoClasses AbstractDao
     */
    private static void createTable(Database database, @NonNull Class<? extends AbstractDao<?, ?>> daoClasses) {
        reflectMethod(database, "createTable", true, daoClasses);
    }

    /**
     * 删除表
     *
     * @param database   Database
     * @param daoClasses AbstractDao
     */
    private static void deleteTable(Database database, @NonNull Class<? extends AbstractDao<?, ?>> daoClasses) {
        reflectMethod(database, "dropTable", true, daoClasses);
    }

    /**
     * 通过反射,调用dao中对应的创建或删除表的方法
     *
     * @param database   Database
     * @param methodName 方法名 createTable创建表,dropTable删除表
     * @param isExists   是否存在表,true存在
     * @param daoClass   表对应的dao
     */
    private static void reflectMethod(Database database, String methodName, boolean isExists,
                                      @NonNull Class<? extends AbstractDao<?, ?>> daoClass) {
        if (database == null) {
            return;
        }
        try {
            Method method = daoClass.getDeclaredMethod(methodName, Database.class, boolean.class);
            method.invoke(null, database, isExists);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 在现有表中添加列
     *
     * @param database  Database
     * @param tableName 表名
     * @param map       包含columnName,columnType
     */
    private static void addColumn(@Nullable Database database, @Nullable String tableName, @Nullable Map<String, String> map) {
        if (database == null || TextUtils.isEmpty(tableName)) {
            return;
        }
        if (map == null || map.size() <= 0) {
            return;
        }

        for (Map.Entry<String, String> entry : map.entrySet()) {
            String defaultValue = null;
            switch (entry.getValue()) {
                case NULL:
                    break;
                case TEXT:
                    defaultValue = "\"\"";
                    break;
                case INTEGER:
                case BOOLEAN:
                case REAL:
                    defaultValue = "0";
                    break;
                default:
                    break;
            }
            if (defaultValue == null) {
                continue;
            }
            String addSql = "ALTER TABLE %s ADD %s %s DEFAULT %s";
            addSql = String.format(addSql, tableName, entry.getKey(), entry.getValue(), defaultValue);
            database.execSQL(addSql);
        }
    }

    /**
     * 根据class返回对应数据库中保存的数据类型
     *
     * @param type class
     * @return sqlite数据库中存储的数据类型
     */
    private static String getTypeByClass(Class<?> type) {
        if (type.equals(String.class)) {
            return TEXT;
        }
        if (type.equals(Long.class)
                || type.equals(Integer.class)
                || type.equals(Short.class)
                || type.equals(long.class)
                || type.equals(int.class)
                || type.equals(short.class)) {
            return INTEGER;
        }
        if (type.equals(Boolean.class) || type.equals(boolean.class)) {
            //boolean类型在数据库中保存的也是Integer类型
            return INTEGER;
        }
        if (type.equals(Float.class) || type.equals(Double.class) || type.equals(float.class) || type.equals(double.class)) {
            return REAL;
        }
        if (type.equals(Byte.class) || type.equals(byte.class)) {
            //byte类型在数据库中保存的也是Integer类型
            return INTEGER;
        }
        final String strTagB = "[B";
        if (strTagB.equals(type.getName())) {
            //byte[]类型在数据库中保存的也是Integer类型
            return BLOB;
        }
        log("", "未知类型 --> " + type.toString());
        return NULL;
    }

    /**
     * 获取表中的所有列
     *
     * @param db        Database
     * @param tableName 表名
     * @return 列名集合
     */
    @Nullable
    private static Map<String, String> getColumns2(Database db, String tableName) {
        Map<String, String> map = null;
        Cursor cursor = null;
        try {
            cursor = db.rawQuery("PRAGMA table_info(" + tableName + ")", null);
            if (cursor == null) {
                return null;
            }
            map = new HashMap<>();
            while (cursor.moveToNext()) {
                String columnName = cursor.getString(1);
                String columnType = cursor.getString(2);
                if (TextUtils.isEmpty(columnName)) {
                    continue;
                }
                map.put(columnName, columnType);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return map;
    }

    /**
     * 打印日志
     *
     * @param label 标签
     * @param info  日志内容
     */
    private static void log(@Nullable String label, @Nullable String info) {
        if (!DEBUG) {
            return;
        }
        if (TextUtils.isEmpty(label)) {
            label = "";
        } else {
            label += " ———>>> ";
        }
        if (info == null) {
            info = "";
        }
        Log.d("数据库升级", label + info);
    }

    /**
     * 从Map获取内容
     *
     * @param map 数据 Map<K, V>
     * @param <K> Key
     * @param <V> Value
     * @return String
     */
    private static <K, V> String getStringByMap(@Nullable Map<K, V> map) {
        if (map == null || map.size() <= 0) {
            return "空";
        }
        StringBuilder result = new StringBuilder();
        for (Map.Entry<K, V> entry : map.entrySet()) {
            result.append(entry.getKey().toString()).append(",").append(entry.getValue().toString()).append("; ");
        }
        return "【" + result.toString() + "】";
    }
}

相关文章

  • GreenDao简单使用和数据库升级

    一 GreenDao的配置 学习/参考地址:GreenDao3.0数据库的简单使用GreenDAO数据库升级 1、...

  • GreenDao3.0数据库升级数据迁移问题记录

      关于GreenDao数据库版本升级,网络上基本上是这样的一套以MigrationHelper的解决方案,我在实...

  • android GreenDao

    Android框架之路——GreenDao3.2.2的使用 - CSDN博客 GreenDAO 完美解决数据库升级...

  • GreenDao数据库升级问题

    前面博客中有介绍轻量级的数据库GreenDao的整合。本文介绍如何对GreenDao数据库进行升级。 为什么要进行...

  • GreenDao3的简单使用

    简介 GreenDAO是一个轻量、快速的ORM解决方案,它能高效地将对象映射到SQLite数据库,目前升级到了3....

  • 2019-06-05 数据库升级

    数据库升级 在实际开发当中,我们经常要对数据库进行升级,但GreenDAO默认的DaoMaster.DevOpen...

  • GreenDao学习笔记

    greenDAO greenDAO 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。它...

  • GreenDao数据库升级

    GreenDao数据库升级 第1步:升级数据库版本号 第2步:修改数据库实体类 第3步:迁移数据库 注:数据库升级...

  • GreenDao数据库升级解决方案

    主要思路: 删除表不改动 新增表:创建新表 增加新字段:在原有表上直接新增字段 修改或删除原有字段:数据迁移

  • greenDAO基本使用方法

    一. 什么是greenDAO greenDao是一个将对象映射到SQLite数据库中的轻量且快速的ORM解决方案,...

网友评论

      本文标题:GreenDao数据库升级解决方案

      本文链接:https://www.haomeiwen.com/subject/ytofgctx.html