乐趣区

关于android:android数据库如何进行升级架构之数据库框架升级

前言

在本篇里,将会解说令开发者比拟头疼的数据库降级。
话不多说,先来看代码成果,看看是否是想要的


如图所示

  • 以后 APP 版本号为 V007;
  • V001、V002 降级到 V007 有对应的解决逻辑;
  • V003、V004、V005、V006 降级到 V007 也有对应的解决逻辑;
  • 同理可实现任意版本可阔多个版本升级到最新数据库;

开始之前咱们先理一下数据库降级的逻辑

  1. 任何数据库在操作之前,咱们最好有一个数据库备份,所以这里得要备份对应的数据库 File 文件;
  2. 任何数据表在操作之前,也要有一个数据表备份,所以这里会在原表名加前后缀操作;
  3. 在数据表降级的时候,有些时候可能会对表名、表列做任意增删改的操作,所以这里每次都要创立一个全新的表;
  4. 全新表创立好了,然而一张空表,这里就须要查问对应加了前后缀的原表数据,将对应数据增加至新表里;
  5. 数据全副拷贝实现时,为了让用户有良好的体验,咱们须要删除对应加了前后缀的原表;
  6. 对应原表删除结束时,咱们须要删除对应备份数据库的 File 文件。

总结:

  • 操作【1】和【6】这俩操作 属于 java 代码执行
  • 其余【2】、【3】、【4】、【5】这些操作,都属于 SQL 操作
  • 但 SQL 操作,通过效果图发现,是写在 XML 文件外面的,所以须要写一个 XML 解析器

当初咱们就依照对应步骤一一解说

1、备份原数据库 File 文件

    /**
     * 复制单个文件 (可更名复制)
     *
     * @param oldPathFile 筹备复制的文件源
     * @param newPathFile 拷贝到新绝对路径带文件名 (注:目录门路需带文件名)
     * @return
     */
    public static void CopySingleFile(String oldPathFile, String newPathFile) {
        try {
//            int bytesum = 0;
            int byteread = 0;
            File oldfile = new File(oldPathFile);
            File newFile = new File(newPathFile);
            File parentFile = newFile.getParentFile();
            if (!parentFile.exists()) {parentFile.mkdirs();
            }
            if (oldfile.exists()) { // 文件存在时
                InputStream inStream = new FileInputStream(oldPathFile); // 读入原文件
                FileOutputStream fs = new FileOutputStream(newPathFile);
                byte[] buffer = new byte[1024];
                while ((byteread = inStream.read(buffer)) != -1) {
//                    bytesum += byteread; // 字节数 文件大小
                    fs.write(buffer, 0, byteread);
                }
                inStream.close();}
        } catch (Exception e) {e.printStackTrace();
        }
    }
复制代码 

总结:这也没啥可说的,就一个很简略的文件复制。

2、数据库降级 XML 编写 updateXml.xml

<?xml version="1.0" encoding="utf-8"?>
<updateXml>
    <createVersion version="V007">
        <createDb name="hqk">  <!-- 要降级数据库对应名,如果利用含多个数据库,那么能够创立多个 createDb 标签 -->
            <sql_createTable>  <!-- 创立最新的表构造 -->
                <!--
                     @DbFiled("time")
                    private  String time;
                    @DbFiled("id")
                    private  Long id;
                    @DbFiled("path")
                    private  String path;
                 -->
                create table if not exists tb_photo (id Long,tb_time TEXT ,tb_path TEXT,tb_name TEXT);
            </sql_createTable>
        </createDb>
    </createVersion>

    <!-- V001,V002 对应版本的 app 降级到 最新 V007 版本的降级逻辑 -->
    <updateStep versionFrom="V001,V002" versionTo="V007">
        <!-- 对应数据降级逻辑,对应下面的 createDb 标签 name,如果有多对 createDb,这里也可执行多对 updateDb-->
        <updateDb name="hqk">
            <sql_before> <!-- 将 V001,V002 对应的旧表重命名备份 -->
                alter table tb_photo rename to bak_tb_photo;
            </sql_before>
            <sql_after>  <!-- 查问重命名后旧表数据,将对应数据增加至新表里 -->
                insert into tb_photo(tb_time,id, tb_path) select tb_time,tb_id,tb_path from bak_tb_photo;
            </sql_after>
            <sql_after><!-- 删除旧表备份 -->
                drop table if exists bak_tb_photo;
            </sql_after>
        </updateDb>
    </updateStep>

    <updateStep versionFrom="V003,V004,V005,V006" versionTo="V007">
        <updateDb name="hqk">
            <sql_before>
                alter table tb_photo rename to bak_tb_photo;
            </sql_before>
            <sql_after>
                insert into tb_photo(tb_time,id, tb_path) select tb_time,tb_id,tb_path from
                bak_tb_photo;
            </sql_after>

            <sql_after>
                drop table if exists bak_tb_photo;
            </sql_after>
        </updateDb>

    </updateStep>

</updateXml>
复制代码 

总结:

  • createVersion 标签,示意 以后 最新 APP 版本须要操作的内容
  • createDb 标签,示意以后最新对应的数据库要操作的内容,可多组标签,实现多个数据库降级
  • sql_createTable 标签,示意以后最新对应的数据表要操作的内容,可多组标签,实现多表降级
  • updateStep 标签,示意不同版本要降级的对象,可多组标签,达到不同版本数据库降级到最新数据库的成果
  • updateDb 标签,示意旧版本要批改的对应数据库
  • sql_before 标签,示意数据库降级时优先级最高的 SQL,(在新表创立前执行)
  • sql_after 标签,示意数据库降级时优先级最低并按程序执行的 SQL(在新表创立后执行)

更多 Android 技术分享能够关注 @我, 也能够退出 QQ 群号:1078469822,学习交换 Android 开发技能。

3、创立 XML 解析器

对应工具类 DomUtils.class

public class DomUtils {
    /**
     * 读取降级 xml
     *
     * @param context
     * @return
     */
    public static UpdateDbXml readDbXml(Context context) {
        InputStream is = null;
        Document document = null;
        try {is = context.getAssets().open("updateXml.xml");
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            document = builder.parse(is);
        } catch (Exception e) {e.printStackTrace();
        } finally {if (is != null) {
                try {is.close();
                } catch (IOException e) {e.printStackTrace();
                }
            }
        }
        if (document == null) {return null;}

        UpdateDbXml xml = new UpdateDbXml(document);

        return xml;
    }
    /**
     * 新表插入数据
     *
     * @param xml
     * @param lastVersion 上个版本
     * @param thisVersion 以后版本
     * @return
     */
    public static UpdateStep findStepByVersion(UpdateDbXml xml, String lastVersion, String thisVersion) {if (lastVersion == null || thisVersion == null) {return null;}
        // 更新脚本
        UpdateStep thisStep = null;
        if (xml == null) {return null;}
        List<UpdateStep> steps = xml.getUpdateSteps();
        if (steps == null || steps.size() == 0) {return null;}

        for (UpdateStep step : steps) {if (step.getVersionFrom() == null || step.getVersionTo() == null) { } else {
                // 降级起源以逗号分隔
                String[] lastVersionArray = step.getVersionFrom().split(",");
                if (lastVersionArray != null && lastVersionArray.length > 0) {for (int i = 0; i < lastVersionArray.length; i++) {
                        // 有一个配到 update 节点即降级数据
                        if (lastVersion.equalsIgnoreCase(lastVersionArray[i]) && step.getVersionTo().equalsIgnoreCase(thisVersion)) {
                            thisStep = step;
                            break;
                        }
                    }
                }
            }
        }
        return thisStep;
    }

    /**
     * 解析出对应版本的建表脚本
     *
     * @return
     */
    public static CreateVersion findCreateByVersion(UpdateDbXml xml, String version) {
        CreateVersion cv = null;
        if (xml == null || version == null) {return cv;}
        List<CreateVersion> createVersions = xml.getCreateVersions();

        if (createVersions != null) {for (CreateVersion item : createVersions) {Log.i("david", "item=" + item.toString());
                // 如果表雷同则要反对 xml 中逗号分隔
                String[] createVersion = item.getVersion().trim().split(",");
                for (int i = 0; i < createVersion.length; i++) {if (createVersion[i].trim().equalsIgnoreCase(version)) {
                        cv = item;
                        break;
                    }
                }
            }
        }
        return cv;
    }

}

复制代码 

对应 XML 的实体类

UpdateDbXml


/**
 * @ClassName: UpdateDbXml
 * @Description: 降级更新数据库
 *
 */
public class UpdateDbXml {
    /**
     * 降级脚本列表
     */
    private List<UpdateStep> updateSteps;
    /**
     * 降级版本
     */
    private List<CreateVersion> createVersions;

    public UpdateDbXml(Document document) {
        {
            // 获取降级脚本
            NodeList updateSteps = document.getElementsByTagName("updateStep");
            this.updateSteps = new ArrayList<UpdateStep>();
            for (int i = 0; i < updateSteps.getLength(); i++) {Element ele = (Element) (updateSteps.item(i));
                Log.i("jett","updateSteps 各个降级的版本:"+ele.toString());
                UpdateStep step = new UpdateStep(ele);
                this.updateSteps.add(step);
            }
        }
        {
            /**
             * 获取各降级版本
             */
            NodeList createVersions = document.getElementsByTagName("createVersion");
            this.createVersions = new ArrayList<CreateVersion>();
            for (int i = 0; i < createVersions.getLength(); i++) {Element ele = (Element) (createVersions.item(i));
                Log.i("jett","各个降级的版本:"+ele.toString());
                CreateVersion cv = new CreateVersion(ele);
                this.createVersions.add(cv);
            }
        }
    }

    public List<UpdateStep> getUpdateSteps() {return updateSteps;}

    public void setUpdateSteps(List<UpdateStep> updateSteps) {this.updateSteps = updateSteps;}

    public List<CreateVersion> getCreateVersions() {return createVersions;}

    public void setCreateVersions(List<CreateVersion> createVersions) {this.createVersions = createVersions;}

}

复制代码 

UpdateStep.class

/**
 * @ClassName: UpdateStep
 * @Description: 数据库降级脚本信息
 */
public class UpdateStep
{
    /**
     * 旧版本
     */
    private String versionFrom;

    /**
     * 新版本
     */
    private String versionTo;

    /**
     * 更新数据库脚本
     */
    private List<UpdateDb> updateDbs;

    // ==================================================

    public UpdateStep(Element ele)
    {versionFrom = ele.getAttribute("versionFrom");
        versionTo = ele.getAttribute("versionTo");
        updateDbs = new ArrayList<UpdateDb>();

        NodeList dbs = ele.getElementsByTagName("updateDb");
        for (int i = 0; i < dbs.getLength(); i++)
        {Element db = (Element) (dbs.item(i));
            UpdateDb updateDb = new UpdateDb(db);
            this.updateDbs.add(updateDb);
        }
    }

    public List<UpdateDb> getUpdateDbs()
    {return updateDbs;}

    public void setUpdateDbs(List<UpdateDb> updateDbs)
    {this.updateDbs = updateDbs;}

    public String getVersionFrom()
    {return versionFrom;}

    public void setVersionFrom(String versionFrom)
    {this.versionFrom = versionFrom;}

    public String getVersionTo()
    {return versionTo;}

    public void setVersionTo(String versionTo)
    {this.versionTo = versionTo;}

}

复制代码 

UpdateDb.class

**
 * @ClassName: UpdateDb
 * @Description: 更新数据库脚本
 *
 */
public class UpdateDb
{
    /**
     * 数据库名称
     */
    private String dbName;
    /**
     * 
     */
    private List<String> sqlBefores;
    /**
     * 
     */
    private List<String> sqlAfters;

    public UpdateDb(Element ele)
    {dbName = ele.getAttribute("name");
        sqlBefores = new ArrayList<String>();
        sqlAfters = new ArrayList<String>();

        {NodeList sqls = ele.getElementsByTagName("sql_before");
            for (int i = 0; i < sqls.getLength(); i++)
            {String sql_before = sqls.item(i).getTextContent();
                this.sqlBefores.add(sql_before);
            }
        }

        {NodeList sqls = ele.getElementsByTagName("sql_after");
            for (int i = 0; i < sqls.getLength(); i++)
            {String sql_after = sqls.item(i).getTextContent();
                this.sqlAfters.add(sql_after);
            }
        }

    }

    public String getName()
    {return dbName;}

    public void setDbName(String dbName)
    {this.dbName = dbName;}

    public List<String> getSqlBefores()
    {return sqlBefores;}

    public void setSqlBefores(List<String> sqlBefores)
    {this.sqlBefores = sqlBefores;}

    public List<String> getSqlAfters()
    {return sqlAfters;}

    public void setSqlAfters(List<String> sqlAfters)
    {this.sqlAfters = sqlAfters;}
}

复制代码 

CreateVersion.class

public class CreateVersion
{
    /**
     * 版本信息
     */
    private String version;

    /**
     * 创立数据库表脚本
     */
    private List<CreateDb> createDbs;

    public CreateVersion(Element ele)
    {version = ele.getAttribute("version");
        Log.i("jett","CreateVersion="+version);
        {createDbs = new ArrayList<CreateDb>();
            NodeList cs = ele.getElementsByTagName("createDb");
            for (int i = 0; i < cs.getLength(); i++)
            {Element ci = (Element) (cs.item(i));
                CreateDb cd = new CreateDb(ci);
                this.createDbs.add(cd);
            }
        }
    }

    public String getVersion()
    {return version;}

    public void setVersion(String version)
    {this.version = version;}

    public List<CreateDb> getCreateDbs()
    {return createDbs;}

    public void setCreateDbs(List<CreateDb> createDbs)
    {this.createDbs = createDbs;}

}

复制代码 

CreateDb.class


/**
 * @ClassName: CreateDb
 * @Description: 创立数据库脚本
 *
 */
public class CreateDb
{
    /**
     * 数据库表名
     */
    private String name;

    /**
     * 创立表的 sql 语句汇合
     */
    private List<String> sqlCreates;

    public CreateDb(Element ele)
    {name = ele.getAttribute("name");

        {sqlCreates = new ArrayList<String>();
            NodeList sqls = ele.getElementsByTagName("sql_createTable");
            for (int i = 0; i < sqls.getLength(); i++)
            {String sqlCreate = sqls.item(i).getTextContent();
                this.sqlCreates.add(sqlCreate);
            }
        }
    }

    public String getName()
    {return name;}

    public void setName(String name)
    {this.name = name;}

    public List<String> getSqlCreates()
    {return sqlCreates;}

    public void setSqlCreates(List<String> sqlCreates)
    {this.sqlCreates = sqlCreates;}

}

复制代码 

4、万事俱备只欠东风:UpdateManager.class

public class UpdateManager {
    private File parentFile = ContUtils.parentFile;
    private File bakFile = ContUtils.bakFile;
    private List<User> userList;

    public void startUpdateDb(Context context) {
        // 读取 XML 文件,将 XML 内容转化为对应对象
        UpdateDbXml updateDbxml = DomUtils.readDbXml(context);
        //    下载 上一个版本  --》下一个版本【注:在下载时,须要将旧版本、新版本以逗号的模式写入文件缓存】String[] versions = FileUtil.getLocalVersionInfo(new File(parentFile,
                "update.txt"));
        String lastVersion = versions[0];// 拿到上一个版本
        String thisVersion = versions[1];// 拿到以后版本

        // 数据库 File 原地址
        String userFile = ContUtils.sqliteDatabasePath;
        // 数据库 File 备份地址
        String user_bak = ContUtils.copySqliteDatabasePath;
        // 降级前,数据库 File 备份
        FileUtil.CopySingleFile(userFile, user_bak);
        // 依据对应新旧版本号查问 XML 转化对象外面的脚本,失去对应降级脚本
        UpdateStep updateStep = DomUtils.findStepByVersion(updateDbxml, lastVersion, thisVersion);
        if (updateStep == null) {return;}
        // 拿到对应降级脚本
        List<UpdateDb> updateDbs = updateStep.getUpdateDbs();
        try {// 将原始数据库中所有的表名 更改成 bak_表名 ( 数据还在)
            executeBeforesSql(updateDbs);
            // 查看新表,创立新表
            CreateVersion createVersion = DomUtils.findCreateByVersion(updateDbxml, thisVersion);
            executeCreateVersion(createVersion);
            // 将原来 bak_表名  的数据迁徙到 新表中
            executeAftersSql(updateDbs);
        } catch (Exception e) {e.printStackTrace();
        }

    }

    private void executeAftersSql(List<UpdateDb> updateDbs) throws Exception {for (UpdateDb db : updateDbs) {if (db == null || db.getName() == null) {throw new Exception("db or dbName is null;");
            }
            List<String> sqls = db.getSqlAfters();
            SQLiteDatabase sqlitedb = getDb();
            // 执行数据库语句
            executeSql(sqlitedb, sqls);
            sqlitedb.close();}
    }

    private void executeCreateVersion(CreateVersion createVersion) throws Exception {if (createVersion == null || createVersion.getCreateDbs() == null) {throw new Exception("createVersion or createDbs is null;");
        }
        for (CreateDb cd : createVersion.getCreateDbs()) {if (cd == null || cd.getName() == null) {throw new Exception("db or dbName is null when createVersion;");
            }
            // 创立数据库表 sql
            List<String> sqls = cd.getSqlCreates();
            SQLiteDatabase sqlitedb = getDb();
            executeSql(sqlitedb, sqls);
            sqlitedb.close();}
    }

    // 所有的表名 更改成 bak_表名 (数据还在)
    private void executeBeforesSql(List<UpdateDb> updateDbs) throws Exception {for (UpdateDb db : updateDbs) {if (db == null || db.getName() == null) {throw new Exception("db or dbName is null;");
            }
            List<String> sqls = db.getSqlBefores();
            SQLiteDatabase sqlitedb = getDb();
            // 执行数据库语句
            executeSql(sqlitedb, sqls);
            sqlitedb.close();}
    }

    private SQLiteDatabase getDb() {
        String dbfilepath = null;
        SQLiteDatabase sqlitedb = null;

        dbfilepath = ContUtils.sqliteDatabasePath;// logic 对应的数据库门路
        if (dbfilepath != null) {File f = new File(dbfilepath);
            f.mkdirs();
            if (f.isDirectory()) {f.delete();
            }
            sqlitedb = SQLiteDatabase.openOrCreateDatabase(dbfilepath, null);
        }
        return sqlitedb;
    }

    private void executeSql(SQLiteDatabase sqlitedb, List<String> sqls) {
        // 查看参数
        if (sqls == null || sqls.size() == 0) {return;}
        sqlitedb.beginTransaction();
        for (String sql : sqls) {sql = sql.replaceAll("\r\n", " ");
            sql = sql.replaceAll("\n", " ");
            if (!"".equals(sql.trim())) {
                try {// Logger.i(TAG, "执行 sql:" + sql, false);
                    sqlitedb.execSQL(sql);
                } catch (SQLException e) {}}
        }

        sqlitedb.setTransactionSuccessful();
        sqlitedb.endTransaction();}

}

复制代码 

总结:这里没啥好说的,具体都写在正文外面了,都是依照下面的思路写的

更多 Android 技术分享能够关注 @我, 也能够退出 QQ 群号:1078469822,学习交换 Android 开发技能。

退出移动版