Android 架構(gòu)之?dāng)?shù)據(jù)庫框架升級
前言:
上一篇講解了Android 架構(gòu)之?dāng)?shù)據(jù)框架搭建 ,里面含有數(shù)據(jù)庫最基礎(chǔ)的增刪改查功能,不過只考慮了單數(shù)據(jù)庫,開發(fā)者可以舉一反三按照對應(yīng)思路設(shè)計多數(shù)據(jù)庫架構(gòu)。 在本篇里,將會講解令開發(fā)者比較頭疼的數(shù)據(jù)庫升級。
話不多說,先來看代碼效果,看看是否是想要的

如上圖所示:
- 當(dāng)前APP版本號為V007;
- V001、V002升級到V007有對應(yīng)的處理邏輯;
- V003、V004、V005、V006升級到V007也有對應(yīng)的處理邏輯;
- 同理可實(shí)現(xiàn)任意版本可闊多個版本升級到最新數(shù)據(jù)庫;
開始之前我們先理一下數(shù)據(jù)庫升級的邏輯
- 任何數(shù)據(jù)庫在操作之前,我們最好有一個數(shù)據(jù)庫備份,所以這里得要備份對應(yīng)的數(shù)據(jù)庫File文件;
- 任何數(shù)據(jù)表在操作之前,也要有一個數(shù)據(jù)表備份,所以這里會在原表名加前后綴操作;
- 在數(shù)據(jù)表升級的時候,有些時候可能會對表名、表列做任意增刪改的操作,所以這里每次都要創(chuàng)建一個全新的表;
- 全新表創(chuàng)建好了,但是一張空表,這里就需要查詢對應(yīng)加了前后綴的原表數(shù)據(jù),將對應(yīng)數(shù)據(jù)添加至新表里;
- 數(shù)據(jù)全部拷貝完成時,為了讓用戶有良好的體驗(yàn),我們需要刪除對應(yīng)加了前后綴的原表;
- 對應(yīng)原表刪除完畢時,我們需要刪除對應(yīng)備份數(shù)據(jù)庫的
File文件。
總結(jié):
- 操作【1】和【6】 這倆操作 屬于 java代碼執(zhí)行
- 其他【2】、【3】、【4】、【5】 這些操作,都屬于SQL操作
- 但SQL操作,通過效果圖發(fā)現(xiàn),是寫在XML文件里面的,所以需要寫一個XML解析器
現(xiàn)在我們就按照對應(yīng)步驟一一講解
1、備份原數(shù)據(jù)庫File文件
/**
* 復(fù)制單個文件(可更名復(fù)制)
*
* @param oldPathFile 準(zhǔn)備復(fù)制的文件源
* @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; //字節(jié)數(shù) 文件大小
fs.write(buffer, 0, byteread);
}
inStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
總結(jié):這也沒啥可說的,就一個很簡單的文件復(fù)制。
2、數(shù)據(jù)庫升級XML編寫 updateXml.xml
<?xml version="1.0" encoding="utf-8"?>
<updateXml>
<createVersion version="V007">
<createDb name="hqk"> <!-- 要升級數(shù)據(jù)庫對應(yīng)名 ,如果應(yīng)用含多個數(shù)據(jù)庫,那么可以創(chuàng)建多個 createDb 標(biāo)簽-->
<sql_createTable> <!-- 創(chuàng)建最新的表結(jié)構(gòu) -->
<!--
@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對應(yīng)版本的app升級到 最新V007版本的升級邏輯-->
<updateStep versionFrom="V001,V002" versionTo="V007">
<!-- 對應(yīng)數(shù)據(jù)升級邏輯,對應(yīng)上面的 createDb 標(biāo)簽name ,如果有多對 createDb,這里也可執(zhí)行多對 updateDb-->
<updateDb name="hqk">
<sql_before> <!-- 將V001,V002對應(yīng)的舊表重命名備份-->
alter table tb_photo rename to bak_tb_photo;
</sql_before>
<sql_after> <!-- 查詢重命名后舊表數(shù)據(jù),將對應(yīng)數(shù)據(jù)添加至新表里-->
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>
總結(jié):
createVersion標(biāo)簽 ,表示 當(dāng)前 最新APP版本需要操作的內(nèi)容createDb標(biāo)簽,表示當(dāng)前最新對應(yīng)的數(shù)據(jù)庫要操作的內(nèi)容,可多組標(biāo)簽,實(shí)現(xiàn)多個數(shù)據(jù)庫升級sql_createTable標(biāo)簽,表示當(dāng)前最新對應(yīng)的數(shù)據(jù)表要操作的內(nèi)容,可多組標(biāo)簽,實(shí)現(xiàn)多表升級updateStep標(biāo)簽,表示不同版本要升級的對象,可多組標(biāo)簽,達(dá)到不同版本數(shù)據(jù)庫升級到最新數(shù)據(jù)庫的效果updateDb標(biāo)簽,表示舊版本要修改的對應(yīng)數(shù)據(jù)庫sql_before標(biāo)簽,表示數(shù)據(jù)庫升級時優(yōu)先級最高的SQL,(在新表創(chuàng)建前執(zhí)行)sql_after標(biāo)簽,表示數(shù)據(jù)庫升級時優(yōu)先級最低并按順序執(zhí)行的SQL(在新表創(chuàng)建后執(zhí)行)
3、創(chuàng)建XML解析器
3.1 對應(yīng)工具類 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;
}
/**
* 新表插入數(shù)據(jù)
*
* @param xml
* @param lastVersion 上個版本
* @param thisVersion 當(dāng)前版本
* @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節(jié)點(diǎn)即升級數(shù)據(jù)
if (lastVersion.equalsIgnoreCase(lastVersionArray[i]) && step.getVersionTo().equalsIgnoreCase(thisVersion)) {
thisStep = step;
break;
}
}
}
}
}
return thisStep;
}
/**
* 解析出對應(yīng)版本的建表腳本
*
* @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;
}
}
3.2 對應(yīng)XML的實(shí)體類
UpdateDbXml
/**
* @ClassName: UpdateDbXml
* @Description: 升級更新數(shù)據(jù)庫
*
*/
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: 數(shù)據(jù)庫升級腳本信息
*/
public class UpdateStep
{
/**
* 舊版本
*/
private String versionFrom;
/**
* 新版本
*/
private String versionTo;
/**
* 更新數(shù)據(jù)庫腳本
*/
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: 更新數(shù)據(jù)庫腳本
*
*/
public class UpdateDb
{
/**
* 數(shù)據(jù)庫名稱
*/
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;
/**
* 創(chuàng)建數(shù)據(jù)庫表腳本
*/
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: 創(chuàng)建數(shù)據(jù)庫腳本
*
*/
public class CreateDb
{
/**
* 數(shù)據(jù)庫表名
*/
private String name;
/**
* 創(chuàng)建表的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、萬事俱備只欠東風(fēng): 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內(nèi)容轉(zhuǎn)化為對應(yīng)對象
UpdateDbXml updateDbxml = DomUtils.readDbXml(context);
// 下載 上一個版本 --》下一個版本 【注:在下載時,需要將舊版本、新版本以逗號的形式寫入文件緩存】
String[] versions = FileUtil.getLocalVersionInfo(new File(parentFile,
"update.txt"));
String lastVersion = versions[0];//拿到上一個版本
String thisVersion = versions[1];//拿到當(dāng)前版本
//數(shù)據(jù)庫File原地址
String userFile = ContUtils.sqliteDatabasePath;
//數(shù)據(jù)庫File備份地址
String user_bak = ContUtils.copySqliteDatabasePath;
//升級前,數(shù)據(jù)庫File備份
FileUtil.CopySingleFile(userFile, user_bak);
//根據(jù)對應(yīng)新舊版本號查詢XML轉(zhuǎn)化對象里面的腳本,得到對應(yīng)升級腳本
UpdateStep updateStep = DomUtils.findStepByVersion(updateDbxml, lastVersion, thisVersion);
if (updateStep == null) {
return;
}
//拿到對應(yīng)升級腳本
List<UpdateDb> updateDbs = updateStep.getUpdateDbs();
try {
//將原始數(shù)據(jù)庫中所有的表名 更改成 bak_表名(數(shù)據(jù)還在)
executeBeforesSql(updateDbs);
//檢查新表,創(chuàng)建新表
CreateVersion createVersion = DomUtils.findCreateByVersion(updateDbxml, thisVersion);
executeCreateVersion(createVersion);
//將原來bak_表名 的數(shù)據(jù)遷移到 新表中
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();
//執(zhí)行數(shù)據(jù)庫語句
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;");
}
// 創(chuàng)建數(shù)據(jù)庫表sql
List<String> sqls = cd.getSqlCreates();
SQLiteDatabase sqlitedb = getDb();
executeSql(sqlitedb, sqls);
sqlitedb.close();
}
}
//所有的表名 更改成 bak_表名(數(shù)據(jù)還在)
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();
//執(zhí)行數(shù)據(jù)庫語句
executeSql(sqlitedb, sqls);
sqlitedb.close();
}
}
private SQLiteDatabase getDb() {
String dbfilepath = null;
SQLiteDatabase sqlitedb = null;
dbfilepath = ContUtils.sqliteDatabasePath;// logic對應(yīng)的數(shù)據(jù)庫路徑
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) {
// 檢查參數(shù)
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, "執(zhí)行sql:" + sql, false);
sqlitedb.execSQL(sql);
} catch (SQLException e) {
}
}
}
sqlitedb.setTransactionSuccessful();
sqlitedb.endTransaction();
}
}
到此這篇關(guān)于Android 架構(gòu)之?dāng)?shù)據(jù)庫框架升級的文章就介紹到這了,更多相關(guān)Android 架構(gòu)之?dāng)?shù)據(jù)庫框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android基于opencv實(shí)現(xiàn)多通道分離與合并
針對圖像多通道的分離與混合,OpenCV 4中提供了split()函數(shù)和merge()函數(shù)用于解決這些需求。本文講解一下Android如何調(diào)用這些函數(shù)實(shí)現(xiàn)多通道分離與合并2021-06-06
Android 退出應(yīng)用程序的實(shí)現(xiàn)方法
這篇文章主要介紹了Android 退出應(yīng)用程序的實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2017-04-04
解決Error:All flavors must now belong to a named flavor dimens
這篇文章主要介紹了解決Error:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com,需要的朋友可以參考下2017-11-11

