android資料庫如何進行版本升級?架構之資料庫框架升級

anne117發表於2021-09-26

前言

在本篇裡,將會講解令開發者比較頭疼的資料庫升級。
話不多說,先來看程式碼效果,看看是否是想要的

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開發技能。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70007350/viewspace-2793802/,如需轉載,請註明出處,否則將追究法律責任。

相關文章