前言

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


如图所示

  • 以后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开发技能。