目前公司android项目普遍使用框架对数据库进行操作,数据库表与数据实体都具有严格的对应的关系,但是数据库的升依赖不同版本间的升级脚本,如果应用跨多版本进行升级时,当缺失部分升级脚本时就会导致应用异常。

  依赖脚本升级方案的缺点:

    1、如果缺失某段升级脚本,覆盖安装程序后,应用运行异常。

    2、项目跨版本升级管理复杂,多版本升级支持难度较大。

    3、数据库导致的问题不容易排查,很难定位问题所在。

  基于以上情况并以现有项目为背景(ormlite框架)构建android数据库自动升级维护方案,该方案需实现功能:

    1、根据对应实体升级数据库表结构,保证实体中每个数据库属性均正常创建。保证项目启动后数据库表结构与本版本设计一致。

    2、方案实施与项目所用数据库框架不存在冲突问题,可并行执行。

    3、方案执行效率不可对应用流程产生明显影响。

    4、本方案可作为数据库框架独立存在于项目中,完成项目数据库表的创建、升级操作。

  引用此方案需要执行的操作:在项目启动后,数据库创建或升级后首先进行数据库实体注册然后调用更新接口即可。数据库升级规则:遍历注册实体,如果实体对应的表不存在则创建表(与现有框架兼容难于处理,本例不提供具体实现);如果表存在,则依次校验数据库属性,如果该字段不存在则创建该字段。

注意:兼容不同框架时需对源码进行一定修改,但是处理逻辑一致!

代码执行流流程图如下:

代码实现如下:以下代码经过部分修改后可兼容ormlite修改,需修改内容:1、获取数据库链接 2、String工具类判断字符串是否为空及多字符串相等的方法替换

 /** @ClassName: DbUtils
*
* @Description: TODO
* 封装数据库常用操作工具方法
* @author walker
*
* @date 2017年7月26日 上午11:36:33
*
*/
public class DbUtils {
static DbUtils dbUtils;
SQLiteDatabase db;
Cursor cursor = null;
/**单例模式,私有化构造*/
private DbUtils() {}
public static DbUtils getInstance(){
if(dbUtils == null){
dbUtils = new DbUtils();
}
dbUtils.init();
return dbUtils;
}
/**
* @Description: TODO
* 初始化所需资源
*/
public void init(){
if(db == null){
db = AppContext.getAppContext().getDatabaseHelper().getWritableDatabase();
}
} ArrayList<Class> tableObjList = new ArrayList<Class>();
/**
* @Description: TODO
* 注册表实体:被注册实体与数据库表字段一一对应
* @param clazz 数据库表对应实体的class对象
*/
public <T> void registTable(Class<T> clazz){
if(tableObjList == null){
tableObjList = new ArrayList<Class>();
}
tableObjList.add(clazz);
} /**
* @Description: TODO
* 遍历已注册实体,更新表字段,数据库更新后调用
*/
public void update(){
HashMap<String,HashMap<String, String>> tableList = getSqliteTables();
HashMap<String, String> columnMap;
/**表名称*/
String tableName = "";
/**每个实体数据库注解*/
DatabaseTable tableAnnotation;
/**实体属性集合*/
Field[] files ;
/**实体属性数据库注解*/
DatabaseField columnAnnotation;
String columnName;
/**sql语句容器*/
StringBuilder sqlStrBuilder = new StringBuilder();
com.j256.ormlite.field.DataType dataType;
Object defaultValue = "";
/**字段类型*/
String columnType="";
//遍历已注册实体,更新或创建表
for (Class objClazz : tableObjList) {
tableAnnotation = (DatabaseTable) objClazz.getAnnotation(DatabaseTable.class);
if (objClazz != null && !StringUtils.isEmpty(tableAnnotation.tableName())) {
tableName = tableAnnotation.tableName();
} else {
tableName = objClazz.getSimpleName();
}
columnMap = tableList.get(tableName);
//表未创建,则执行建表逻辑,此处代码适配框架,表创建业务在框架内执行
if(columnMap == null){
//新建表应走第三方数据库框架。
// createTable(objClazz);
continue;
}
files = objClazz.getDeclaredFields();
for (Field field : files) {
// 获取字段注解:表名、类型
columnAnnotation = field.getAnnotation(DatabaseField.class);
// 字段被注解:数据库字段
if (columnAnnotation != null) {
// 字段名称
if (StringUtils.isEmpty(columnAnnotation.columnName())) {
columnName = field.getName();
} else {
columnName = columnAnnotation.columnName();
}
// 该字段已经创建,进行下一次循环
if (columnMap.containsKey(columnName)) {
continue;
}
//字段未创建,构建创建字段sql
if (sqlStrBuilder == null) {
sqlStrBuilder = new StringBuilder();
}
sqlStrBuilder.delete(0, sqlStrBuilder.length());
sqlStrBuilder.append("ALTER TABLE " + tableName);
sqlStrBuilder.append(" ADD COLUMN " + columnName);
dataType = columnAnnotation.dataType();
defaultValue = "";
if (StringUtils.strInStrs(dataType + "", dataType.STRING + "", dataType.UNKNOWN + "")) {
columnType = "varchar";
if(StringUtils.isEmptyUnNull(defaultValue+"")){
defaultValue = "''";
}
} else if (StringUtils.strInStrs(dataType + "", dataType.INTEGER + "")) {
if(StringUtils.isEmptyUnNull(defaultValue+"")){
defaultValue = 0;
}
columnType = "INTEGER";
} else if (StringUtils.strInStrs(dataType + "", dataType.DOUBLE + "")) {
if(StringUtils.isEmptyUnNull(defaultValue+"")){
defaultValue = 0;
}
columnType = "double";
} else if (StringUtils.strInStrs(dataType + "", dataType.FLOAT + "")) {
if(StringUtils.isEmptyUnNull(defaultValue+"")){
defaultValue = 0;
}
columnType = "FLOAT";
} else if (StringUtils.strInStrs(dataType + "", dataType.LONG + "")) {
if(StringUtils.isEmptyUnNull(defaultValue+"")){
defaultValue = 0;
}
columnType = "Long";
}
sqlStrBuilder.append(" " + columnType);
// 非空设置
if (!columnAnnotation.canBeNull()) {
sqlStrBuilder.append(" NOT NULL DEFAULT " + defaultValue);
}
executeSql(sqlStrBuilder+"");
}
}
}
} /**
* @Description: TODO
* 根据数据库实体类构建数据库表结构 :仅支持根据实体名称为表名,属性名称为字段,属性类型为字段类型方式建表。
* @param objClazz 数据库表实体类
*/
private void createTable(Class objClazz) {
StringBuilder sb = new StringBuilder();
sb.append(" CREATE TABLE " + objClazz.getSimpleName()+ " (");
String filedName = "";
//指定类的字段集合
Field[] files = objClazz.getDeclaredFields();
for (Field field : files) {
filedName = field.getName();
sb.append(filedName + " ");
sb.append(field.getType().getSimpleName());
sb.append(",");
}
if (sb.lastIndexOf(",") != -1) {
sb.replace(sb.lastIndexOf(","), sb.length(), ")");
}else{//拼接插入表语句失败
}
executeSql(sb+"");
}
/**
* @Description: TODO
* 获取数据库中已存在表信息
* @return 返回数据库集合,数据库名称为键,数据库字段集合(字段名)为值。
*/
public HashMap<String,HashMap<String, String> > getSqliteTables(){
HashMap<String,HashMap<String, String>> resMap = new HashMap<String,HashMap<String, String>>();
ArrayList<HashMap<String, String>> tableList= query("select name,sql from sqlite_master where type = 'table'");
ArrayList<HashMap<String, String>> columnMapList;
HashMap<String, String> columnMap;
for (HashMap<String, String> table : tableList) {
try {
columnMap = new HashMap<>();
columnMapList = query("PRAGMA table_info(" + table.get("name") + ")");
if (columnMapList != null) {
for (HashMap<String, String> column : columnMapList) {
columnMap.put(column.get("name"), column.get("type"));
}
}
resMap.put(table.get("name"), columnMap);
} catch (Exception e) {
e.printStackTrace();
}
}
return resMap;
} /**
* @Description: TODO
* 数据库查询方法
* @param sql 数据库查询sql语句
* @return 返回sql查询结果集合,如果产生异常或无内容则返回空集合
*/
public ArrayList<HashMap<String, String>> query(String sql) {
HashMap<String, String> res;
ArrayList<HashMap<String, String>> resList = new ArrayList<HashMap<String, String>>();
try {
cursor = db.rawQuery(sql, null);
while (cursor.moveToNext()) {
res = new HashMap<String, String>();
for (int i = 0; i < cursor.getColumnCount(); i++) {
res.put(cursor.getColumnName(i) + "", cursor.getString(i) + "");
}
resList.add(res);
}
} catch (Exception e) {
resList.clear();
} finally {
closeCursor();
}
return resList;
} /**
* @Description: TODO
* 将游标cursor中的数据转换成Map列表数据
* @param cursor 数据库查询结果集合
* @return 返回结果集对应的数据列表
*/
public ArrayList<HashMap<String, String>> cursorToMap(Cursor cursor){
HashMap<String, String> res;
ArrayList<HashMap<String, String>> resList = new ArrayList<HashMap<String,String>>();
try {
while (cursor.moveToNext()) {
res = new HashMap<String, String>();
for (int i = 0; i < cursor.getColumnCount(); i++) {
res.put(cursor.getColumnName(i)+"", cursor.getString(i)+"");
}
resList.add(res);
}
} catch (Exception e) {
resList.clear();
}finally {
closeCursor();
}
return resList;
} /**
* @Description: TODO
* 关闭数据库游标
*/
private void closeCursor(){
if(cursor != null){
cursor.close();
cursor = null;
}
} /**
* @Description: TODO
* 执行sql语句
* @param sql sql语句
* @return 执行成功返回true,否则返回false
*/
public boolean executeSql(String sql) {
try {
db.execSQL(sql);
return true;
} catch (Exception e) {
}
return false;
}
}

以上为android项目兼容现有数据库框架进行自动化升级的改造方案思路,后续会将本方案进行优化,实现数据库创建、升级、常用查询管理等共能。

android项目数据库升级跨版本管理解决方案的更多相关文章

  1. C/S应用升级更新完整解决方案

    年末福利,C/S应用升级更新完整解决方案放送 程序员,工作累寿命短,大家应该学会分享,别浪费有限的生命与健康做重复的事情. C/S方式实现的应用有个升级更新功能是必需的,以前整过一个但是没考虑多套C/ ...

  2. Android WebView存在跨域访问漏洞(CNVD-2017-36682)介绍及解决

    Android WebView存在跨域访问漏洞(CNVD-2017-36682).攻击者利用该漏洞,可远程获取用户隐私数据(包括手机应用数据.照片.文档等敏感信息),还可窃取用户登录凭证,在受害者毫无 ...

  3. C# Xamarin For Android自动升级项目实战

    一.课程介绍 “明人不说暗话,跟着阿笨一起玩Xamarin”,本次分享课程阿笨将带来大家一起学习Xamarin For Android系列<C# Xamarin For Android自动升级项 ...

  4. Android Studio 升级到3.0后出现编译错误\.gradle\caches\transforms-1\files-1.1\*****-release.aar

    Android Studio 升级到3.0后出现各种编译问题,其中有一个问题是关于资源找不到的问题,百度了半天,也没有相关的文章 C:\Users.gradle\caches\transforms-1 ...

  5. Android数据库升级

    随着Android应用版本的迭代,经常遇到数据库表结构发生改变,或者一些指定的表数据需要更新.这也就引出一个问题Android数据库的更新问题. Android数据库升级分类 Android数据库更新 ...

  6. android 在线升级借助开源中国App源码

    android 在线升级借助开源中国App源码 http://www.cnblogs.com/luomingui/p/3949429.html android 在线升级借助开源中国App源码分析如下: ...

  7. 跨域解决方案一:使用CORS实现跨域

    跨站HTTP请求(Cross-site HTTP request)是指发起请求的资源所在域不同于请求指向的资源所在域的HTTP请求. 比如说,我在Web网站A(www.a.com)中通过<img ...

  8. 优雅的处理Android数据库升级的问题

    原始完成于:2015-04-27 19:28:22 提供一种思路,优雅的处理Android数据库升级的问题,直接上代码: 1 package com.example.databaseissuetest ...

  9. Android大图片裁剪终极解决方案(上:原理分析)

    转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-)  http://my.oschina.net/ryanhoo/blog/86842 约几个月前,我正 ...

随机推荐

  1. 打造一个简单的Java字节码反编译器

    简介 本文示范了一种反编译Java字节码的方法,首先通过解析class文件,然后将解析的结果转成java代码.但是本文并没有覆盖所有的class文件的特性和指令,只针对部分规范进行解析. 所有的代码代 ...

  2. 【Android Developers Training】 0. 序言:构建你的第一个应用

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  3. [转]浅谈C++指针直接调用类成员函数

    找了一番之后发现这篇文章讲的很清楚. 传送门

  4. x01.ExcelHelper: NPOI 操作

    Excel 操作,具有十分明显的针对性,故很难通用,但这并不妨碍参考后以解决自己的实际问题. 有一汇总表如下: 当然,只是示范,产品的代码应该唯一!现在要根据此汇总表产生各个客户的产品清单.由于客户较 ...

  5. FileInputStreamTest

    package JBJADV003;import java.io.FileNotFoundException;import java.io.IOException;import java.io.Inp ...

  6. 浅入深出之Java集合框架(下)

    Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...

  7. mac重开电脑后显示重装提示解决办法

    情况描述: mac昨天电脑关闭后 第二天打开电脑就显示语言选择安装语言 解决办法: 1  出现语言安装提示界面  我们选择简体中文 2  出现苹果密码登陆    我们选择下面的按钮点击退出  这样就可 ...

  8. Swift语法初见

    Swift语法初见 http://c.biancheng.net/cpp/html/2424.html 类型的声明: let implicitInteger = 70 let implicitDoub ...

  9. 教你做一个单机版人事管理系统(Winform版)treeview与listview使用详情

    ------------------------------------------------------------------部门部分------------------------------ ...

  10. “玲珑杯”ACM比赛 Round #12 (D) 【矩阵快速幂的时间优化】

    //首先,感谢Q巨 题目链接 定义状态向量b[6] b[0]:三面临红色的蓝色三角形个数 b[1]:两面临红色且一面临空的蓝色三角形个数 b[2]:一面临红色且两面临空的蓝色三角形个数 b[3]:三面 ...