android项目数据库升级跨版本管理解决方案

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

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

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

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

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

 

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

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

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

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

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

 

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

      

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

代码执行流流程图如下:

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

  1 /** @ClassName: DbUtils
  2  *
  3  * @Description: TODO
  4  *    封装数据库常用操作工具方法
  5  * @author walker
  6  *
  7  * @date 2017年7月26日 上午11:36:33
  8  * 
  9  */
 10 public class DbUtils {
 11     static DbUtils dbUtils;
 12     SQLiteDatabase db;
 13     Cursor cursor = null;
 14     /**单例模式,私有化构造*/
 15     private DbUtils() {}
 16     public static DbUtils getInstance(){
 17         if(dbUtils == null){
 18             dbUtils = new DbUtils();
 19         }
 20         dbUtils.init();
 21         return dbUtils;
 22     }
 23     /**
 24      * @Description: TODO
 25      * 初始化所需资源
 26      */
 27     public void init(){
 28         if(db == null){
 29             db = AppContext.getAppContext().getDatabaseHelper().getWritableDatabase();
 30         }
 31     }
 32     
 33     ArrayList<Class> tableObjList = new ArrayList<Class>();
 34     /**
 35      * @Description: TODO
 36      *  注册表实体:被注册实体与数据库表字段一一对应
 37      *  @param clazz 数据库表对应实体的class对象
 38      */
 39     public <T> void registTable(Class<T> clazz){
 40         if(tableObjList == null){
 41             tableObjList = new ArrayList<Class>();
 42         }
 43         tableObjList.add(clazz);
 44     }
 45     
 46     /**
 47      * @Description: TODO
 48      *    遍历已注册实体,更新表字段,数据库更新后调用
 49      */
 50     public void update(){
 51         HashMap<String,HashMap<String, String>> tableList = getSqliteTables();
 52         HashMap<String, String> columnMap;
 53         /**表名称*/
 54         String tableName = "";
 55         /**每个实体数据库注解*/
 56         DatabaseTable tableAnnotation;
 57         /**实体属性集合*/
 58         Field[] files ;
 59         /**实体属性数据库注解*/
 60         DatabaseField columnAnnotation;
 61         String columnName;
 62         /**sql语句容器*/
 63         StringBuilder sqlStrBuilder = new StringBuilder();
 64         com.j256.ormlite.field.DataType dataType;
 65         Object defaultValue = "";
 66         /**字段类型*/
 67         String columnType="";
 68         //遍历已注册实体,更新或创建表
 69         for (Class objClazz : tableObjList) {
 70             tableAnnotation = (DatabaseTable) objClazz.getAnnotation(DatabaseTable.class);
 71             if (objClazz != null && !StringUtils.isEmpty(tableAnnotation.tableName())) {
 72                 tableName = tableAnnotation.tableName();
 73             } else {
 74                 tableName = objClazz.getSimpleName();
 75             }
 76             columnMap = tableList.get(tableName);
 77             //表未创建,则执行建表逻辑,此处代码适配框架,表创建业务在框架内执行
 78             if(columnMap == null){
 79                 //新建表应走第三方数据库框架。
 80 //                createTable(objClazz);
 81                 continue;
 82             }
 83             files = objClazz.getDeclaredFields();
 84             for (Field field : files) {
 85                 // 获取字段注解:表名、类型
 86                 columnAnnotation = field.getAnnotation(DatabaseField.class);
 87                 // 字段被注解:数据库字段
 88                 if (columnAnnotation != null) {
 89                     // 字段名称
 90                     if (StringUtils.isEmpty(columnAnnotation.columnName())) {
 91                         columnName = field.getName();
 92                     } else {
 93                         columnName = columnAnnotation.columnName();
 94                     }
 95                     // 该字段已经创建,进行下一次循环
 96                     if (columnMap.containsKey(columnName)) {
 97                         continue;
 98                     }
 99                     //字段未创建,构建创建字段sql
100                     if (sqlStrBuilder == null) {
101                         sqlStrBuilder = new StringBuilder();
102                     }
103                     sqlStrBuilder.delete(0, sqlStrBuilder.length());
104                     sqlStrBuilder.append("ALTER  TABLE   " + tableName);
105                     sqlStrBuilder.append("  ADD COLUMN  " + columnName);
106                     dataType = columnAnnotation.dataType();
107                     defaultValue = "";
108                     if (StringUtils.strInStrs(dataType + "", dataType.STRING + "", dataType.UNKNOWN + "")) {
109                         columnType = "varchar";
110                         if(StringUtils.isEmptyUnNull(defaultValue+"")){
111                             defaultValue = "''";                            
112                         }
113                     } else if (StringUtils.strInStrs(dataType + "", dataType.INTEGER + "")) {
114                         if(StringUtils.isEmptyUnNull(defaultValue+"")){
115                             defaultValue = 0;                            
116                         }
117                         columnType = "INTEGER";
118                     } else if (StringUtils.strInStrs(dataType + "", dataType.DOUBLE + "")) {
119                         if(StringUtils.isEmptyUnNull(defaultValue+"")){
120                             defaultValue = 0;
121                         }
122                         columnType = "double";
123                     } else if (StringUtils.strInStrs(dataType + "", dataType.FLOAT + "")) {
124                         if(StringUtils.isEmptyUnNull(defaultValue+"")){
125                             defaultValue = 0;
126                         }
127                         columnType = "FLOAT";
128                     } else if (StringUtils.strInStrs(dataType + "", dataType.LONG + "")) {
129                         if(StringUtils.isEmptyUnNull(defaultValue+"")){
130                             defaultValue = 0;
131                         }
132                         columnType = "Long";
133                     }
134                     sqlStrBuilder.append(" " + columnType);
135                     // 非空设置
136                     if (!columnAnnotation.canBeNull()) {
137                         sqlStrBuilder.append(" NOT NULL  DEFAULT " + defaultValue);
138                     }
139                     executeSql(sqlStrBuilder+"");
140                 }
141             }
142         }
143     }
144     
145     /**
146      * @Description: TODO
147      *     根据数据库实体类构建数据库表结构 :仅支持根据实体名称为表名,属性名称为字段,属性类型为字段类型方式建表。
148      *  @param objClazz 数据库表实体类
149      */
150     private void createTable(Class objClazz) {
151         StringBuilder sb = new StringBuilder();
152         sb.append(" CREATE TABLE " + objClazz.getSimpleName()+ " (");
153         String filedName = "";
154         //指定类的字段集合
155         Field[] files = objClazz.getDeclaredFields();
156         for (Field field : files) {
157             filedName = field.getName();
158             sb.append(filedName + " ");
159             sb.append(field.getType().getSimpleName());
160             sb.append(",");
161         }
162         if (sb.lastIndexOf(",") != -1) {
163             sb.replace(sb.lastIndexOf(","), sb.length(), ")");
164         }else{//拼接插入表语句失败
165         }
166         executeSql(sb+"");
167     }
168     /**
169      * @Description: TODO
170      *     获取数据库中已存在表信息 
171      *  @return 返回数据库集合,数据库名称为键,数据库字段集合(字段名)为值。
172      */
173     public HashMap<String,HashMap<String, String> > getSqliteTables(){
174         HashMap<String,HashMap<String, String>> resMap = new HashMap<String,HashMap<String, String>>();
175         ArrayList<HashMap<String, String>> tableList= query("select name,sql from sqlite_master where type = 'table'");
176         ArrayList<HashMap<String, String>> columnMapList;
177         HashMap<String, String> columnMap;
178         for (HashMap<String, String> table : tableList) {
179             try {
180                 columnMap = new HashMap<>();
181                 columnMapList = query("PRAGMA table_info(" + table.get("name") + ")");
182                 if (columnMapList != null) {
183                     for (HashMap<String, String> column : columnMapList) {
184                         columnMap.put(column.get("name"), column.get("type"));
185                     }
186                 }
187                 resMap.put(table.get("name"), columnMap);
188             } catch (Exception e) {
189                 e.printStackTrace();
190             }
191         }
192         return resMap;
193     }
194     
195     
196     /**
197      * @Description: TODO
198      *  数据库查询方法
199      *  @param sql 数据库查询sql语句
200      *  @return 返回sql查询结果集合,如果产生异常或无内容则返回空集合
201      */
202     public ArrayList<HashMap<String, String>> query(String sql) {
203         HashMap<String, String> res;
204         ArrayList<HashMap<String, String>> resList = new ArrayList<HashMap<String, String>>();
205         try {
206             cursor = db.rawQuery(sql, null);
207             while (cursor.moveToNext()) {
208                 res = new HashMap<String, String>();
209                 for (int i = 0; i < cursor.getColumnCount(); i++) {
210                     res.put(cursor.getColumnName(i) + "", cursor.getString(i) + "");
211                 }
212                 resList.add(res);
213             }
214         } catch (Exception e) {
215             resList.clear();
216         } finally {
217             closeCursor();
218         }
219         return resList;
220     }
221         
222     /**
223      * @Description: TODO
224      * 将游标cursor中的数据转换成Map列表数据 
225      *  @param cursor 数据库查询结果集合
226      *  @return 返回结果集对应的数据列表
227      */
228     public ArrayList<HashMap<String, String>> cursorToMap(Cursor cursor){
229         HashMap<String, String> res;
230         ArrayList<HashMap<String, String>> resList = new ArrayList<HashMap<String,String>>();
231         try {
232             while (cursor.moveToNext()) {
233                 res = new HashMap<String, String>();
234                 for (int i = 0; i < cursor.getColumnCount(); i++) {
235                     res.put(cursor.getColumnName(i)+"", cursor.getString(i)+"");
236                 }
237                 resList.add(res);
238             }
239         } catch (Exception e) {
240             resList.clear();
241         }finally {
242             closeCursor();
243         }
244         return resList;
245     }
246         
247     /**
248      * @Description: TODO
249      *    关闭数据库游标
250      */
251     private void closeCursor(){
252         if(cursor != null){
253             cursor.close();
254             cursor = null;
255         }
256     }
257     
258     /**
259      * @Description: TODO
260      *  执行sql语句
261      *  @param sql sql语句
262      *  @return 执行成功返回true,否则返回false
263      */
264     public boolean executeSql(String sql) {
265         try {
266             db.execSQL(sql);
267             return true;
268         } catch (Exception e) {
269         }
270         return false;
271     }
272 }

 

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

 

posted @ 2017-08-11 15:52  聆风牧雨  阅读(1119)  评论(0编辑  收藏  举报