android数据库如何进行升级(架构之数据库框架升级)
前言
【android数据库如何进行升级(架构之数据库框架升级)】在本篇里,将会讲解令开发者比较头疼的数据库升级。
话不多说,先来看代码效果,看看是否是想要的
文章图片
如图所示
- 当前APP版本号为V007;
- V001、V002升级到V007有对应的处理逻辑;
- V003、V004、V005、V006升级到V007也有对应的处理逻辑;
- 同理可实现任意版本可阔多个版本升级到最新数据库;
- 任何数据库在操作之前,我们最好有一个数据库备份,所以这里得要备份对应的数据库File文件;
- 任何数据表在操作之前,也要有一个数据表备份,所以这里会在原表名加前后缀操作;
- 在数据表升级的时候,有些时候可能会对表名、表列做任意增删改的操作,所以这里每次都要创建一个全新的表;
- 全新表创建好了,但是一张空表,这里就需要查询对应加了前后缀的原表数据,将对应数据添加至新表里;
- 数据全部拷贝完成时,为了让用户有良好的体验,我们需要删除对应加了前后缀的原表;
- 对应原表删除完毕时,我们需要删除对应备份数据库的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
create table if not exists tb_photo ( id Long,tb_time TEXT ,tb_path TEXT,tb_name TEXT);
alter table tb_photo rename to bak_tb_photo;
insert into tb_photo(tb_time,id, tb_path) select tb_time,tb_id,tb_path from bak_tb_photo;
drop table if exists bak_tb_photo;
alter table tb_photo rename to bak_tb_photo;
insert into tb_photo(tb_time,id, tb_path) select tb_time,tb_id,tb_path from
bak_tb_photo;
drop table if exists bak_tb_photo;
复制代码
总结:
- createVersion 标签 ,表示 当前 最新APP版本需要操作的内容
- createDb 标签,表示当前最新对应的数据库要操作的内容,可多组标签,实现多个数据库升级
- sql_createTable 标签,表示当前最新对应的数据表要操作的内容,可多组标签,实现多表升级
- updateStep 标签,表示不同版本要升级的对象,可多组标签,达到不同版本数据库升级到最新数据库的效果
- updateDb 标签,表示旧版本要修改的对应数据库
- sql_before 标签,表示数据库升级时优先级最高的SQL,(在新表创建前执行)
- sql_after 标签,表示数据库升级时优先级最低并按顺序执行的SQL(在新表创建后执行)
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 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 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 updateSteps;
/**
* 升级版本
*/
private List createVersions;
public UpdateDbXml(Document document) {
{
// 获取升级脚本
NodeList updateSteps = document.getElementsByTagName("updateStep");
this.updateSteps = new ArrayList();
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();
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 getUpdateSteps() {
return updateSteps;
}public void setUpdateSteps(List updateSteps) {
this.updateSteps = updateSteps;
}public List getCreateVersions() {
return createVersions;
}public void setCreateVersions(List createVersions) {
this.createVersions = createVersions;
}}复制代码
UpdateStep.class
/**
* @ClassName: UpdateStep
* @Description: 数据库升级脚本信息
*/
public class UpdateStep
{
/**
* 旧版本
*/
private String versionFrom;
/**
* 新版本
*/
private String versionTo;
/**
* 更新数据库脚本
*/
private List updateDbs;
// ==================================================public UpdateStep(Element ele)
{
versionFrom = ele.getAttribute("versionFrom");
versionTo = ele.getAttribute("versionTo");
updateDbs = new ArrayList();
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 getUpdateDbs()
{
return updateDbs;
}public void setUpdateDbs(List 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 sqlBefores;
/**
*
*/
private List sqlAfters;
public UpdateDb(Element ele)
{
dbName = ele.getAttribute("name");
sqlBefores = new ArrayList();
sqlAfters = new ArrayList();
{
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 getSqlBefores()
{
return sqlBefores;
}public void setSqlBefores(List sqlBefores)
{
this.sqlBefores = sqlBefores;
}public List getSqlAfters()
{
return sqlAfters;
}public void setSqlAfters(List sqlAfters)
{
this.sqlAfters = sqlAfters;
}
}复制代码
CreateVersion.class
public class CreateVersion
{
/**
* 版本信息
*/
private String version;
/**
* 创建数据库表脚本
*/
private List createDbs;
public CreateVersion(Element ele)
{
version = ele.getAttribute("version");
Log.i("jett","CreateVersion="+version);
{
createDbs = new ArrayList();
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 getCreateDbs()
{
return createDbs;
}public void setCreateDbs(List createDbs)
{
this.createDbs = createDbs;
}}复制代码
CreateDb.class
/**
* @ClassName: CreateDb
* @Description: 创建数据库脚本
*
*/
public class CreateDb
{
/**
* 数据库表名
*/
private String name;
/**
* 创建表的sql语句集合
*/
private List sqlCreates;
public CreateDb(Element ele)
{
name = ele.getAttribute("name");
{
sqlCreates = new ArrayList();
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 getSqlCreates()
{
return sqlCreates;
}public void setSqlCreates(List sqlCreates)
{
this.sqlCreates = sqlCreates;
}}复制代码
4、万事俱备只欠东风: UpdateManager.class
public class UpdateManager {
private File parentFile = ContUtils.parentFile;
private File bakFile = ContUtils.bakFile;
private List 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 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 updateDbs) throws Exception {
for (UpdateDb db : updateDbs) {
if (db == null || db.getName() == null) {
throw new Exception("db or dbName is null;
");
}
List 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 sqls = cd.getSqlCreates();
SQLiteDatabase sqlitedb = getDb();
executeSql(sqlitedb, sqls);
sqlitedb.close();
}
}//所有的表名 更改成 bak_表名(数据还在)
private void executeBeforesSql(List updateDbs) throws Exception {
for (UpdateDb db : updateDbs) {
if (db == null || db.getName() == null) {
throw new Exception("db or dbName is null;
");
}
List 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 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开发技能。
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- android第三方框架(五)ButterKnife
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- 如何寻找情感问答App的分析切入点
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus使用queryWrapper如何实现复杂查询
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- 如何在Mac中的文件选择框中打开系统隐藏文件夹
- 漫画初学者如何学习漫画背景的透视画法(这篇教程请收藏好了!)