18.移动架构数据库升级解决方案

简介: 今天的数据库升级将通过这个脚本文件完成,我们假设目前版本迭代中,已经上线了V001 V002版本,近期将要上线V003。首先先理清三个版本的数据库情况,三个版本中都涉及一个个数据库文件(暂不考虑分库的情况),user.

今天的数据库升级将通过这个脚本文件完成,我们假设目前版本迭代中,已经上线了V001 V002版本,近期将要上线V003。首先先理清三个版本的数据库情况,三个版本中都涉及一个个数据库文件(暂不考虑分库的情况),user.db,user.db中有一个表tb_user,和tb_photo表

在V001版本中
tb_user表有3个属性,user_id,name,password
tb_photo表有2个属性,path,time

在V002版本中
tb_user表有4个属性,user_id,name,password,lastLoginTime
tb_photo表有3个属性,path,time,sendTime

在V003版本中(即将上线的版本)
tb_user表有5个属性,user_id,name,password,lastLoginTime,lastExistTime
tb_photo表有4个属性,path,time,sendTime,size

此时上线,市场上涉及到的用户升级会有几种情况?

1.V001版本升级到V002版本
2.V002版本升级到V003版本
3.V001版本升级到V003版本

由于版本较少,所以涉及到的升级组合并不多,实际项目中可能会有很多,这里只举个简单的例子。我们每升级一个版本,都要考虑到目前市场上存在的所有版本升级的情况,所以这一点需要在我们的脚本文件有所体现

<!-- 保证 UTF-8编码 -->
<updateXml>
    <!--升级到V002需要创建的表结构-->
    <createVersion version="V002">
        <createDb name="user">
            <sql_createTable>
                create table if not exists tb_user(
                user_id Integer primary key,
                name TEXT,
                password TEXT,
                lastLoginTime TEXT
                );
            </sql_createTable>
            <sql_createTable>
                create table if not exists tb_photo(
                path TEXT,
                time TEXT,
                sendTime TEXT
                );
            </sql_createTable>
        </createDb>
    </createVersion>

    <!--升级到V003需要创建的表结构-->
    <createVersion version="V003">
        <createDb name="user">
            <sql_createTable>
                create table if not exists tb_user(
                user_id Integer primary key,
                name TEXT,
                password TEXT,
                lastLoginTime TEXT,
                lastExistTime TEXT
                );
            </sql_createTable>

            <sql_createTable>
                create table if not exists tb_photo(
                path TEXT,
                time TEXT,
                sendTime TEXT,
                size TEXT
                );
            </sql_createTable>
        </createDb>
    </createVersion>

    <!--从V001升级到V003-->
    <updateStep
        versionFrom="V001"
        versionTo="V003">
        <updateDb name="user">
            <sql_before>alter table tb_user rename to bak_tb_user;</sql_before>
            <sql_after>
                insert into tb_user(user_id,name, password)
                select user_id,name,password
                from bak_tb_user;
            </sql_after>
            <sql_after>
                drop table if exists bak_tb_user;
            </sql_after>
        </updateDb>

        <updateDb name="user">
            <sql_before>alter table tb_photo rename to bak_tb_photo;</sql_before>
            <sql_after>
                insert into tb_photo(path,time)
                select path,time
                from bak_tb_photo;
            </sql_after>
            <sql_after>
                drop table if exists bak_tb_photo;
            </sql_after>
        </updateDb>
    </updateStep>
    <!--从V001升级到V002-->
    <updateStep
        versionFrom="V001"
        versionTo="V002">
        <updateDb name="user">
            <sql_before>alter table tb_user rename to bak_tb_user;</sql_before>
            <sql_after>
                insert into tb_user(user_id,name, password)
                select user_id,name,password
                from bak_tb_user;
            </sql_after>
            <sql_after>
                drop table if exists bak_tb_user;
            </sql_after>
        </updateDb>

        <updateDb name="user">
            <sql_before>alter table tb_photo rename to bak_tb_photo;</sql_before>
            <sql_after>
                insert into tb_photo(path,time)
                select path,time
                from bak_tb_photo;
            </sql_after>
            <sql_after>
                drop table if exists bak_tb_photo;
            </sql_after>
        </updateDb>
    </updateStep>
    <!--从V002升级到V003-->
    <updateStep
        versionFrom="V002"
        versionTo="V003">
        <updateDb name="user">
            <sql_before>alter table tb_user rename to bak_tb_user;</sql_before>
            <sql_after>
                insert into tb_user(user_id,name, password,lastLoginTime)
                select user_id,name, password,lastLoginTime
                from bak_tb_user;
            </sql_after>
            <sql_after>
                drop table if exists bak_tb_user;
            </sql_after>
        </updateDb>

        <updateDb name="user">
            <sql_before>alter table tb_photo rename to bak_tb_photo;</sql_before>
            <sql_after>
                insert into tb_photo(path,time,sendTime)
                select path,time,sendTime
                from bak_tb_photo;
            </sql_after>
            <sql_after>
                drop table if exists bak_tb_photo;
            </sql_after>
        </updateDb>

    </updateStep>
</updateXml>

脚本中主要包括两个功能,数据库的创建和更新,考虑到每个用户升级的版本可能不同,所以每一种升级组合情况都要照顾到,比如说createVersion 节点中关于数据库创建的操作,版本V001的用户在选择升级的时候,有可能升级到V002也有可能升级到V003,所以需要针对这两个版本创建不同的数据库表结构。在更新数据库表的时候又需要根据不同的升级方向对表结构进行不同的处理,这些都会在脚本中做不同处理。

升级原理

升级原理很简单,在app完成升级启动时,将原有的数据库表重命名为bak_xxxx,从脚本文件中读取到创建新版本表结构的命令,创建一个新的表格,此时,这个表是空的,然后将重命名后的表bak_xxxx中的数据插入到新表中,插入成功,删除bak_xxxx,实现数据库升级


img_e3e3e54187acf7e18f70643e5bb15934.png
数据库升级原理.png
脚本解析

接下来看代码中如何实现,本次数据库升级以脚本为中心,所以一开始的任务就是读取脚本信息,封装成对象,因此,定义了以下的对象来存储脚本信息

脚本最外层的updateXml对应的对象:UpdateDbXml,可以对照脚本文件来阅读这个对象,对象中封装了两个集合,对应脚本中的updateStep和createVersion节点

package com.example.sqlite.rzm.update;

import android.database.sqlite.SQLiteDatabase;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;

public class UpdateDbXml {
    /**
     * 升级脚本列表
     */
    private List<UpdateStep> updateSteps;

    /**
     * 升级版本
     */
    private List<CreateVersion> createVersions;

    public UpdateDbXml(Document document) {
        {
            // 获取到xml中所有updateStep节点组成的集合
            NodeList updateSteps = document.getElementsByTagName(SqlConstant.KEY_UPDATE_STEP);
            this.updateSteps = new ArrayList<UpdateStep>();
            for (int i = 0; i < updateSteps.getLength(); i++) {
                //for循环遍历每一个updateStep节点,强转成Element对象
                Element ele = (Element) (updateSteps.item(i));
                //把每一个Element封装成Java对象UpdateStep
                UpdateStep step = new UpdateStep(ele);
                //加入集合
                this.updateSteps.add(step);
            }
        }
        {
            /**
             * 获取各升级版本
             */
            NodeList createVersions = document.getElementsByTagName(SqlConstant.KEY_CREATE_VERSION);
            this.createVersions = new ArrayList<CreateVersion>();
            for (int i = 0; i < createVersions.getLength(); i++) {
                Element ele = (Element) (createVersions.item(i));
                //封装成CreateVersion对象
                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和CreateVersion节点的封装

package com.example.sqlite.rzm.update;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;


public class UpdateStep {
    /**
     * 旧版本
     */
    private String versionFrom;

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

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

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

    public UpdateStep(Element ele) {
        //就像findViewById一样获取这个节点的属性attribute
        versionFrom = ele.getAttribute(SqlConstant.KEY_VERSION_FROM);
        versionTo = ele.getAttribute(SqlConstant.KEY_VERSION_TO);
        updateDbs = new ArrayList<UpdateDb>();

        //updateDb是一个跟updateStep一样的元素Element,这里会获取
        // 到所有同类型的元素组成的集合
        NodeList dbs = ele.getElementsByTagName(SqlConstant.KEY_UPDATE_DB);
        for (int i = 0; i < dbs.getLength(); i++) {
            //得到每一个updateDb元素封装成UpdateDb对象
            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;
    }

}

package com.example.sqlite.rzm.update;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;


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

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

    public CreateVersion(Element ele) {
        version = ele.getAttribute(SqlConstant.KEY_DB_VERSION);

        {
            createDbs = new ArrayList<CreateDb>();
            NodeList cs = ele.getElementsByTagName(SqlConstant.KEY_CREATE_DB);
            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和UpdateDb两个节点

package com.example.sqlite.rzm.update;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;


public class CreateDb {

    /**
     * 数据库表名
     */
    private String name;

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

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

        {
            sqlCreates = new ArrayList<String>();
            NodeList sqls = ele.getElementsByTagName(SqlConstant.KEY_SQL_CREATE_TABLE);
            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;
    }

}
package com.example.sqlite.rzm.update;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;

public class UpdateDb {
    /**
     * 数据库名称
     */
    private String dbName;

    /**
     *
     */
    private List<String> sqlBefores;

    /**
     *
     */
    private List<String> sqlAfters;

    public UpdateDb(Element ele) {
        //获取name属性
        dbName = ele.getAttribute(SqlConstant.KEY_DB_NAME);

        sqlBefores = new ArrayList<String>();
        sqlAfters = new ArrayList<String>();

        //这个元素中还包含有别的元素节点,使用同样的方式获取
        {
            NodeList sqls = ele.getElementsByTagName(SqlConstant.KEY_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(SqlConstant.KEY_SQL_AFTER);
            for (int i = 0; i < sqls.getLength(); i++) {
                String sql_after = sqls.item(i).getTextContent();
                this.sqlAfters.add(sql_after);
            }
        }

    }

    public String getDbName() {
        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;
    }
}

这里只需要知道以点,这些对象是为了解析脚本文件而创建的即可

升级过程

每当我们发布一个新版本,用户下载了新版本apk,进行覆盖安装,在安装之前需要有一步保存当前版本的操作,目的是什么?因为当app版本升级之后,原有数据库表结构还是保持上一版本的状态,而我们要实现将其结构升级到当前版本,需要知道上一个版本的数据库结构才能进行相应的升级。所以这一步必不可少

//保存上一版本版本号
updateManager.saveVersionInfo("V001");

等覆盖安装完成,app启动的时候,就需要去读取这个保存的版本号,从而根据上一版本和当前版本的版本信息从脚本文件中读取相应的命令进行升级

private boolean getLocalVersionInfo() {
        boolean ret = false;

        File file = new File(parentFile, UPDATE_INFO);

        if (file.exists()) {
            int byteRead = 0;
            byte[] tempBytes = new byte[100];
            StringBuilder stringBuilder = new StringBuilder();
            InputStream in = null;
            try {
                in = new FileInputStream(file);
                while ((byteRead = in.read(tempBytes)) != -1) {
                    stringBuilder.append(new String(tempBytes, 0, byteRead));
                }
                String infos = stringBuilder.toString();
                //lastVersion是当前版本升级之前的版本,当前版本是从这个版本升级过来的
                //这个信息是通过saveVersionInfo保存的
                lastVersion = infos;
                ret = true;
            } catch (Exception e) {

            } finally {
                if (null != in) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    in = null;
                }
            }
        }

        return ret;
    }

拿到版本信息之后就可以开始解析脚本文件了,脚本文件中设置了和每一个版本升级所需要的sql命令,所以在解析之前必须要获取到升级版本信息才行。

            //拿到当前版本
            String currentVersion = getVersionName(context);
            //拿到上一个版本
            String lastVersion = this.lastVersion;
            UpdateStep updateStep = analyseUpdateStep(updateDbxml, lastVersion, currentVersion);

            if (updateStep == null) {
                return;
            }
            List<UpdateDb> updateDbs = updateStep.getUpdateDbs();
            CreateVersion createVersion = analyseCreateVersion(updateDbxml, currentVersion);

如上,分别从脚本文件中解析出对应的updateStep和createVersion对象,这两个对象中封装了从脚本中解析出来的操作命令,接下来我们就要去执行这些命令,实现数据库的升级

                //****************   把数据库都拷贝一份到backDb文件夹中  *********************
                backUpDb(updateDbs);
                //****************   把数据库都拷贝一份到backDb文件夹中  *********************

                //****************   将原有数据库重命名  *********************
                executeDb(updateDbs, -1);
                //****************   将原有数据库重命名  *********************

                //****************   创建新的数据库和表  *********************
                executeCreateVersion(createVersion, false);
                //****************   创建新的数据库和表  *********************

                //****************   将重命名后的数据库中的数据插入新建的数据库表中,然后删除这个重命名后的数据库
                executeDb(updateDbs, 1);
                //****************   将重命名后的数据库中的数据插入新建的数据库表中,然后删除这个重命名后的数据库

具体的过程可以查看GitHub源码,这里不再一一介绍,总之就是一个解析xml脚本,执行sql命令生成或者更新表的过程。这里贴一下核心逻辑类UpdateManager的代码

package com.example.sqlite.rzm.update;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import android.text.TextUtils;

import com.example.sqlite.rzm.fileutil.FileUtil;

import org.w3c.dom.Document;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;


public class UpdateManager {
    /**
     * 数据库存放的根目录 目前是内存卡下的一个目录ROOT_DB_DIR
     */
    private final String ROOT_DB_DIR = "update";
    /**
     * 数据库备份的目录ROOT_BACK_UP_DIR,存放于ROOT_DB_DIR中
     */
    private final String ROOT_BACK_UP_DIR = "backDb";
    /**
     * 版本更新的相关信息存放文件
     */
    private final String UPDATE_INFO = "update.txt";
    /**
     * 升级用的脚本文件
     */
    private final String UPDATE_SCRIPT_XML = "updateXml.xml";
    private File parentFile = new File(Environment.getExternalStorageDirectory(), ROOT_DB_DIR);

    private File bakFile = new File(parentFile, ROOT_BACK_UP_DIR);

    public UpdateManager() {
        if (!parentFile.exists()) {
            parentFile.mkdirs();
        }
        if (!bakFile.exists()) {
            bakFile.mkdirs();
        }

    }

    public void checkThisVersionTable(Context context) {
        //解析mxl文件,将解析到的信息封装到UpdateDbXml对象中
        UpdateDbXml xml = readDbXml(context);
        //获取当前版本信息
        String thisVersion = this.getVersionName(context);
        //获取到和当前升级版本匹配的CreateVersion,xml中的每一个createVersion都对应这一个
        //版本的升级处理,这里第二个参数表示的是升级后的版本号,为什么是升级后的,因为执行到这一步
        //时,是已经下载了新版本apk,覆盖安装之后读取了安装后的apk的版本号,有了这个版本号我们
        //就知道相应的数据库应该升级到哪个版本,然后选取相应的CreateVersion对象进行操作
        CreateVersion thisCreateVersion = analyseCreateVersion(xml, thisVersion);
        try {
            //升级数据库
            executeCreateVersion(thisCreateVersion, true);
        } catch (Exception e) {
        }

    }

    /**
     * 开始升级
     *
     * @param context
     */
    public void startUpdateDb(Context context) {
        UpdateDbXml updateDbxml = readDbXml(context);
        if (getLocalVersionInfo()) {
            //拿到当前版本
            String currentVersion = getVersionName(context);
            //拿到上一个版本
            String lastVersion = this.lastVersion;
            UpdateStep updateStep = analyseUpdateStep(updateDbxml, lastVersion, currentVersion);

            if (updateStep == null) {
                return;
            }
            List<UpdateDb> updateDbs = updateStep.getUpdateDbs();
            CreateVersion createVersion = analyseCreateVersion(updateDbxml, currentVersion);

            try {
                //****************   把数据库都拷贝一份到backDb文件夹中  *********************
                backUpDb(updateDbs);
                //****************   把数据库都拷贝一份到backDb文件夹中  *********************

                //****************   将原有数据库重命名  *********************
                executeDb(updateDbs, -1);
                //****************   将原有数据库重命名  *********************

                //****************   创建新的数据库和表  *********************
                executeCreateVersion(createVersion, false);
                //****************   创建新的数据库和表  *********************

                //****************   将重命名后的数据库中的数据插入新建的数据库表中,然后删除这个重命名后的数据库
                executeDb(updateDbs, 1);
                //****************   将重命名后的数据库中的数据插入新建的数据库表中,然后删除这个重命名后的数据库
            } catch (Exception e) {
                e.printStackTrace();
                //发生异常,做一些处理
            }
            //****************   删除备份数据库(备份数据库是为了防止升级过程中出现问题而设)  *********************
            deleteBackupDb(updateDbs);
            //****************   删除备份数据库  *********************
        }
    }

    /**
     * 升级成功,删除备份
     *
     * @param updateDbs
     */
    private void deleteBackupDb(List<UpdateDb> updateDbs) {
        List<String> dbNames = getUpdateDbNames(updateDbs);
        if (dbNames.size() > 0) {
            for (String dbName : dbNames) {
                File userFileBak = new File(bakFile.getAbsolutePath() + File.separator + dbName + ".db");
                if (userFileBak.exists()) {
                    userFileBak.delete();
                }
            }
        }

    }

    /**
     * 备份原有数据库
     *
     * @param updateDbs 包含当前版本需要跟新的数据库信息,只需要将需要更新的数据库备份即可
     */
    private void backUpDb(List<UpdateDb> updateDbs) {

        List<String> dbNames = getUpdateDbNames(updateDbs);
        if (dbNames.size() > 0) {
            for (String dbName : dbNames) {
                String user = parentFile.getAbsolutePath() + File.separator + dbName + ".db";
                String user_bak = bakFile.getAbsolutePath() + File.separator + dbName + ".db";
                FileUtil.CopySingleFile(user, user_bak);
            }
        }
    }

    /**
     * 获取到所有需要备份的数据库名称,存入集合返回,一般在未分库的情况下,只有一个结果
     *
     * @param updateDbs
     * @return
     */
    private List<String> getUpdateDbNames(List<UpdateDb> updateDbs) {
        ArrayList<String> dbNames = new ArrayList<>();
        //获取所有需要备份的数据库名称并去除重复
        if (updateDbs != null) {
            for (UpdateDb updateDb : updateDbs) {
                String dbName = updateDb.getDbName();
                if (!TextUtils.isEmpty(dbName) && !dbNames.contains(dbName)) {
                    dbNames.add(dbName);
                }

            }
        }
        return dbNames;
    }

    /**
     * 根据建表脚本,核实一遍应该存在的表
     *
     * @param createVersion
     * @throws Exception
     */
    private void executeCreateVersion(CreateVersion createVersion, boolean isLogic) throws Exception {
        if (createVersion == null || createVersion.getCreateDbs() == null) {
            throw new Exception("check you updateXml.xml file to see if createVersion or createDbs node is null;");
        }

        for (CreateDb cd : createVersion.getCreateDbs()) {
            if (cd == null || cd.getName() == null) {
                throw new Exception("check you updateXml.xml file to see if createDb node or name is null");
            }
            // 创建数据库表sql
            List<String> sqls = cd.getSqlCreates();

            SQLiteDatabase sqlitedb = null;

            sqlitedb = getDb(cd.getName());
            executeSql(sqlitedb, sqls);
            sqlitedb.close();
        }
    }


    /**
     * 执行针对db升级的sql集合
     *
     * @param updateDbs 数据库操作脚本集合
     * @param type      小于0为建表前,大于0为建表后
     * @throws Exception
     * @throws throws    [违例类型] [违例说明]
     * @see
     */
    private void executeDb(List<UpdateDb> updateDbs, int type) throws Exception {
        if (updateDbs == null) {
            throw new Exception("updateDbs is null;");
        }
        for (UpdateDb db : updateDbs) {
            if (db == null || db.getDbName() == null) {
                throw new Exception("db or dbName is null;");
            }

            List<String> sqls = null;
            //更改表
            if (type < 0) {
                sqls = db.getSqlBefores();
            } else if (type > 0) {
                sqls = db.getSqlAfters();
            }

            SQLiteDatabase sqlitedb = null;

            sqlitedb = getDb(db.getDbName());

            executeSql(sqlitedb, sqls);

            sqlitedb.close();

        }
    }

    /**
     * 执行sql语句
     *
     * @param sqlitedb SQLiteDatabase
     * @param sqls     sql语句集合
     * @throws Exception 异常
     * @throws throws    [违例类型] [违例说明]
     * @see
     */
    private void executeSql(SQLiteDatabase sqlitedb, List<String> sqls) throws Exception {
        // 检查参数
        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 {
                    sqlitedb.execSQL(sql);
                } catch (SQLException e) {
                }
            }
        }

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


    /**
     * 新表插入数据
     *
     * @param xml
     * @param lastVersion 上个版本
     * @param thisVersion 当前版本
     * @return
     */
    private UpdateStep analyseUpdateStep(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;
    }

    /**
     * 创建数据库,获取数据库对应的SQLiteDatabase
     *
     * @param dbname
     * @return 设定文件
     * @throws throws [违例类型] [违例说明]sta
     * @see
     */
    private SQLiteDatabase getDb(String dbname) {
        String dbfilepath = null;
        SQLiteDatabase sqlitedb = null;
        if (!parentFile.exists()) {
            parentFile.mkdirs();
        }
        dbfilepath = parentFile.getAbsolutePath() + File.separator + dbname + ".db";// logic对应的数据库路径
        if (dbfilepath != null) {
            File f = new File(dbfilepath);
            f.mkdirs();
            if (f.isDirectory()) {
                f.delete();
            }
            sqlitedb = SQLiteDatabase.openOrCreateDatabase(dbfilepath, null);
        }

        return sqlitedb;
    }


    /**
     * 解析出对应版本的建表脚本
     *
     * @return
     */
    private CreateVersion analyseCreateVersion(UpdateDbXml xml, String version) {
        CreateVersion cv = null;
        if (xml == null || version == null) {
            return cv;
        }
        //获取到xml中createVersion节点对应的建表信息,这个信息是对应的最新版本的数据库信息
        //其他旧的版本数据库最终都要生成这个信息中指定的所有数据库和表的形式,这个节点可能会有
        //多个,以针对升级到不同版本的数据库升级处理,比如说,现在当前最新版本是10.0,目前市场
        //上6.0 7.0 8.0 9.0 都有相应的用户在使用,当前9.0的用户选择升级只能升级到最新的10.0
        //但是其他版本的用户就不同了,比如6.0用户,他有可能会选择升级到7.0 8.0 9.0 10.0中的
        //任意版本,由于每个版本的数据库结构可能都不相同,那么此时我们旧需要在xml中分别配置针对
        //7.0 8.0 9.0 10.0的四种升级方案,那么此时createVersion节点就存在四个,用户选择升
        //级到那个版本,就选择哪个createVersion节点进行处理
        //
        // 这就是createVersion[i].trim().equalsIgnoreCase(version)这一行判断存在的意义
        List<CreateVersion> createVersions = xml.getCreateVersions();
        if (createVersions != null) {
            for (CreateVersion item : createVersions) {
                //一般来说,一个createVersion设置一个version就可以了,但是有一些比较特殊的情况
                //比如8.0 和9.0 两个版本数据库并没有变化,所以升级到8.0 和9.0所进行的数据库升级
                //操作是一样的,所以可以把这两个版本号写在一块以,号隔开。或者你也可以分开写,复制
                //个一摸一样的节点,只把version值改一下,不过这样比较低级,不如写在一块节省代码
                String[] createVersion = item.getVersion().trim().split(",");

                for (int i = 0; i < createVersion.length; i++) {
                    //选择和当前升级后版本相匹配的数据库升级节点,version是升级后的版本
                    if (createVersion[i].trim().equalsIgnoreCase(version)) {
                        cv = item;

                        break;
                    }
                }
            }
        }

        return cv;
    }

    /**
     * 读取升级xml
     *
     * @param context
     * @return
     */
    private UpdateDbXml readDbXml(Context context) {
        InputStream is = null;
        Document document = null;
        try {
            is = context.getAssets().open(UPDATE_SCRIPT_XML);
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            document = builder.parse(is);
        } catch (Exception e) {
            e.printStackTrace();
            throw new NullPointerException("read update script xml failed,check your asset dir to see if you have a script xml");
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        if (document == null) {
            return null;
        }

        UpdateDbXml xml = new UpdateDbXml(document);

        return xml;
    }

    /**
     * 获取APK版本号
     *
     * @param context 上下文
     * @return 版本号
     * @throws throws [违例类型] [违例说明]
     * @see
     */
    public String getVersionName(Context context) {
        String versionName = null;
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            versionName = info.versionName;

        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        return versionName;
    }

    /**
     * 这个方法是模拟一个情景,当前有一个新版本发布,此时运行于市场上的app通过版本更新
     * 接口请求到了新版本信息,新版本版本号为V003,当前版本版本号为V002,通过这个方法将旧版本
     * 号写入到一个文件中做记录,这个信息可以传递出此次升级是从哪个版本升级到哪个版本
     *
     * @return 保存成功返回true,否则返回false
     * @throws throws [违例类型] [违例说明]
     * @see
     */
    public boolean saveVersionInfo(String lastVersion) {
        boolean ret = false;

        FileWriter writer = null;
        try {
            writer = new FileWriter(new File(parentFile, UPDATE_INFO), false);
            writer.write(lastVersion);
            writer.flush();
            ret = true;
        } catch (IOException e) {
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return ret;
    }

    /**
     * 获取本地版本相关信息,app升级之后需要保存升级之前的版本信息,从而
     * 根据这个信息可以知道,当前app是从哪个本版升级过来的
     *
     * @return 获取数据成功返回true,否则返回false
     * @throws throws [违例类型] [违例说明]
     * @see
     */
    private String lastVersion;

    private boolean getLocalVersionInfo() {
        boolean ret = false;

        File file = new File(parentFile, UPDATE_INFO);

        if (file.exists()) {
            int byteRead = 0;
            byte[] tempBytes = new byte[100];
            StringBuilder stringBuilder = new StringBuilder();
            InputStream in = null;
            try {
                in = new FileInputStream(file);
                while ((byteRead = in.read(tempBytes)) != -1) {
                    stringBuilder.append(new String(tempBytes, 0, byteRead));
                }
                String infos = stringBuilder.toString();
                //lastVersion是当前版本升级之前的版本,当前版本是从这个版本升级过来的
                //这个信息是通过saveVersionInfo保存的
                lastVersion = infos;
                ret = true;
            } catch (Exception e) {

            } finally {
                if (null != in) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    in = null;
                }
            }
        }

        return ret;
    }
}

源码地址:https://github.com/renzhenming/SqliteUpGrade
待续。。

相关文章
|
2月前
|
存储 SQL 关系型数据库
TiDB的优势:为何选择TiDB作为您的数据库解决方案
【2月更文挑战第25天】随着数据规模的不断增长和业务需求的日益复杂化,现代企业对数据库系统的扩展性、高可用以及分布式处理能力提出了更高的要求。TiDB作为一个新型的开源分布式数据库,以其独特的设计理念与卓越的技术特性,在众多数据库解决方案中脱颖而出。本文将深入剖析TiDB的核心优势,探讨其如何帮助企业从容应对海量数据挑战、实现无缝水平扩展、保障服务高可用性,并提供灵活一致的事务支持。
|
2月前
|
SQL NoSQL 前端开发
基于BS架构的饰品购物平台设计与实现(程序+文档+数据库)
基于BS架构的饰品购物平台设计与实现(程序+文档+数据库)
|
2月前
|
Oracle 关系型数据库 分布式数据库
分布式数据库集成解决方案
分布式数据库集成解决方案
209 0
|
4月前
|
存储 缓存 关系型数据库
鱼和熊掌如何兼得?一文解析RDS数据库存储架构升级
阿里云RDS率先推出新型存储类型通用云盘,提供低延迟、低成本、高持久性的用户体验。
鱼和熊掌如何兼得?一文解析RDS数据库存储架构升级
|
4月前
|
存储 DataWorks 监控
DataWorks,一个 polar db 有上万个数据库,解决方案
DataWorks,一个 polar db 有上万个数据库,解决方案
|
3月前
|
Kubernetes 物联网 数据中心
大规模 IoT 边缘容器集群管理的几种架构 -2-HashiCorp 解决方案 Nomad
大规模 IoT 边缘容器集群管理的几种架构 -2-HashiCorp 解决方案 Nomad
|
3天前
|
运维 负载均衡 关系型数据库
MySQL高可用解决方案演进:从主从复制到InnoDB Cluster架构
MySQL高可用解决方案演进:从主从复制到InnoDB Cluster架构
|
9天前
|
存储 运维 Kubernetes
多态关联在数据库设计中的应用和解决方案
多态关联在数据库设计中的应用和解决方案
17 0
|
12天前
|
API 持续交付 开发者
构建高性能微服务架构:挑战与解决方案
【4月更文挑战第29天】 随着现代软件开发的复杂性日益增加,微服务架构成为众多企业和开发者的首选。它通过将大型应用程序拆分为一系列小型、自治的服务来提供灵活性和可扩展性。然而,随之而来的是一系列的挑战,包括服务间通信、数据一致性、安全性和性能优化等。本文将深入探讨在构建高性能微服务架构过程中可能遇到的挑战,并提供针对性的解决方案,以帮助开发者克服这些难题,实现更加健壮和高效的系统。
|
13天前
|
运维 负载均衡 监控
软件体系结构 - 关系数据库(3)主从架构
【4月更文挑战第26天】软件体系结构 - 关系数据库(3)主从架构
24 0