SQLiteOpenHelper版本升级

117 篇文章 0 订阅

SQLiteOpenHelper 基本用法

我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用软件时创建出应用使用到的数据库表结构及添加一些初始化记录,另外在软件升级的时候,也需要对数据表结构进行更新。那么,我们如何才能实现在用户初次使用或升级软件时自动在用户的手机上创建出应用需要的数据库表呢?总不能让我们在每个需要安装此软件的手机上通过手工方式创建数据库表吧?因为这种需求是每个数据库应用都要面临的,所以在Android系统,为我们提供了一个名为SQLiteOpenHelper的抽象类,必须继承它才能使用,它是通过对数据库版本进行管理来实现前面提出的需求。


下面我们来看一下Android SQLite 最常用的几个类和那些方法:


一、SQLiteOpenHelper :


onCreate(SQLiteDatabase db);

     用于初次使用软件时生成数据库表。

当调用SQLiteOpenHelper的getWritableDatabase()或者getReadableDatabase()方法获取用于操作数据库的SQLiteDatabase实例的时候,

如果数据库不存在,Android系统会自动生成一个数据库,接着调用onCreate()方法,onCreate()方法在初次生成数据库时才会被调用,

在onCreate()方法里可以生成数据库表结构及添加一些应用使用到的初始化数据


onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

在数据库的版本发生变化时会被调用,一般在软件升级时才需改变版本号


getWritableDatabase();

getReadableDatabase();

两方法都可以获取一个用于操作数据库的SQLiteDatabase实例。但getWritableDatabase() 方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,倘若使用getWritableDatabase()打开数据库就会出错。getReadableDatabase()方法先以读写方式打开数据库,如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。




二、SQLiteDatabase:


1、db.insert(table, nullColumnHack, values)

第一个参数是表名称,第二个参数是空列的默认值,第三个参数是ContentValues类型的一个封装了列名称和列值的Map;


2、db.delete(table, whereClause, whereArgs)

第一个参数是表名称,第二个参数是删除条件,第三个参数是删除条件值数组


3、db.update(table, values, whereClause, whereArgs)

第一个参数是表名称,第二个参数是更行列ContentValues类型的键值对(Map),第三个参数是更新条件(where字句),第四个参数是更新条件数组


4、db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy)(下面有对该方法详细讲解)


5、db.execSQL(sql) // 执行任何SQL语句


6、db.rawQuery(sql, selectionArgs)


对第四个方法详细讲解:


Cursor  query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit)

各个参数的意义说明:

①table:表名称

②columns:列名称数组

③selection:条件字句,相当于where

④selectionArgs:条件字句,参数数组

⑤groupBy:分组列

⑥having:分组条件

⑦orderBy:排序列

⑧limit:分页查询限制



三、Cursor:

Cursor是一个游标接口,提供了遍历查询结果的方法,如移动指针方法move(),获得列值方法getString()等.


Cursor游标常用方法:


getCount()   总记录条数

isFirst()     判断是否第一条记录

isLast()      判断是否最后一条记录

moveToFirst()    移动到第一条记录

moveToLast()    移动到最后一条记录

move(int offset)   移动到指定记录

moveToNext()    移动到下一条记录

moveToPrevious()    移动到上一条记录

getColumnIndexOrThrow(String columnName)  根据列名称获得列索引

getInt(int columnIndex)   获得指定列索引的int类型值

getString(int columnIndex)   获得指定列缩影的String类型值


利用onUpgrade 升级数据库版本

1.

代码片段,双击复制
01
private static final int VERSION = 2 ;

      
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    db.execSQL( "drop table if exists " + Tables.TERM);
    db.execSQL( "drop table if exists " + Tables.PAPER);
    db.execSQL( "drop table if exists " + Tables.CHECKPOINT);
    db.execSQL( "drop table if exists " + Tables.QUESTION);
    db.execSQL( "drop table if exists " + Tables.QUESTION_ATTRIBUTE);
    db.execSQL( "drop table if exists " + Tables.ALTERNATIVE);
    db.execSQL( "drop table if exists " + Tables.ALTERNATIVE_ATTRIBUTE);
    db.execSQL( "drop table if exists " + Tables.MATRIXROW);
    db.execSQL( "drop table if exists " + Tables.MATRIXROW_ATTRIBUTE);
    db.execSQL( "drop table if exists " + Tables.QUESTIONNAIRE);
    onCreate(db);
}
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//定义升级函数
private void upgradeDatabaseToVersion1(SQLiteDatabase db) {
         // Add 'new' column to mytable table.
         db.execSQL( "ALTER TABLE mytable ADD COLUMN new TEXT" );
    }
 
//重写onUpgrade
public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
         Log.w(TAG, "Upgrading database from version " + oldVersion
                + " to " + currentVersion + "." );
 
         switch (oldVersion) {
         case 0 :
             if (currentVersion <= 1 ) {
                return ;
             }
 
             db.beginTransaction();
             try {
                upgradeDatabaseToVersion1(db);
                db.setTransactionSuccessful();
             } catch (Throwable ex) {
                Log.e(TAG, ex.getMessage(), ex);
                break ;
             } finally {
                db.endTransaction();
             }
 
            return ;
    }
 
    Log.e(TAG, "Destroying all old data." );
    dropAll(db);
    onCreate(db);
}
2.

在android应用程序需要升级时,如果之前的数据库表结构发生了变化或者新添加了表,就需要对数据库进行升级,并保留原来的数据库数据。

 程序如何知道数据库需要升级?

SQLiteOpenHelper类的构造函数有一个参数是int version,它的意思就是指数据库版本号。比如在软件1.0版本中,我们使用SQLiteOpenHelper访问数据库时,该参数为1,那么数据库版本号1就会写在我们的数据库中。

到了1.1版本,我们的数据库需要发生变化,那么我们1.1版本的程序中就要使用一个大于1的整数来构造SQLiteOpenHelper类,用于访问新的数据库,比如2。

当我们的1.1新程序读取1.0版本的老数据库时,就发现老数据库里存储的数据库版本是1,而我们新程序访问它时填的版本号为2,系统就知道数据库需要升级。

何时触发数据库升级?如何升级?

当系统在构造SQLiteOpenHelper类的对象时,如果发现版本号不一样,就会自动调用onUpgrade函数,让你在这里对数据库进行升级。根据上述场景,在这个函数中把老版本数据库的相应表中增加字段,并给每条记录增加默认值即可。

新版本号和老版本号都会作为onUpgrade函数的参数传进来,便于开发者知道数据库应该从哪个版本升级到哪个版本。

升级完成后,数据库会自动存储最新的版本号为当前数据库版本号。

下面举例写出具体过程:

如果我上一个版本的数据库表结构没发生变化,但是新增了两张表,而且之前有一张表中默认有4条数据,现在新版本默认有11条数据,那么该怎么操作呢,假设表A发生了变化,并且新建了表B、C

1.首先我们需要把原来的数据库表重命名一下

public static final String TEMP_SQL_CREATE_TABLE_SUBSCRIBE = "alter table "
            + A + " rename to temp_A";

原来的表结构是:

private static final String SQL_CREATE_TABLE_SUBSCRIBE = "create table  if not exists "
            + A + "(id integer primary key autoincrement,code text not null,name text,username text)";

2.然后把备份表temp_A中的数据copy到新创建的数据库表A中,这个表A没发生结构上的变化

public static final String INSERT_SUBSCRIBE = "select 'insert into A (code,name,username,tablename) 
values ('''||code||''','''||name||''',''cnki'','''||tablename||'''')' as insertSQL from temp_A";

 

3.此时临时表中的数据已经全部复制到了表A中,但是我之前的表A中有四条默认的数据,用户可能删了,可能又增加了新的数据,那我不管用户什么操作,我都把这4条删除掉,然后重新添加一次,这样就保证了之前的四条数据还在新的数据表中。

这是我之前的四条数据,我先找出来:

public static final String[] arrWhereAct = {
            "where code ='K174' and tablename = 'phonepaper'",
            "where code ='GMRB' and tablename = 'newspaper'",
            "where code ='XJSJ' and tablename = 'magazine'",
            "where code ='JTKL' and tablename = 'magazine'" };

 

4.删除备份表

public static final String DELETE_TEMP_SUBSCRIBE = "delete from temp_A ";
    public static final String DROP_TEMP_SUBSCRIBE = "drop table if exists temp_A";

5.然后把数据库版本号改为比之前高的版本号,在OnUpgrade方法中执行上述语句就行,具体如下:

复制代码
@Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        
        for (int j = oldVersion; j <= newVersion; j++) {
            switch (j) {
            case 2:
          //创建临时表
db.execSQL(TEMP_SQL_CREATE_TABLE_SUBSCRIBE);           //执行OnCreate方法,这个方法中放的是表的初始化操作工作,比如创建新表之类的
onCreate(db);           //删除之前的表里面的那4条默认的数据
for (int i = 0; i < arrWhereAct.length; i++) { db.execSQL(DELETE_TEMP_SUBSCRIBE + arrWhereAct[i]); } //将临时表中的数据放入表A
         Cursor cursor
= db.rawQuery(INSERT_SUBSCRIBE, null); if (cursor.moveToFirst()) { do { db.execSQL(cursor.getString(cursor .getColumnIndex("insertSQL"))); } while (cursor.moveToNext()); } cursor.close();           //将临时表删除掉
db.execSQL(DROP_TEMP_SUBSCRIBE);
break; default: break; } } }
复制代码

为什么要在方法里写for循环,主要是考虑到夸版本升级,比如有的用户一直不升级版本,数据库版本号一直是1,而客户端最新版本其实对应的数据库版本已经是4了,那么我中途可能对数据库做了很多修改,通过这个for循环,可以迭代升级,不会发生错误。

具体实现就是这样,望高手吐槽!


全面控制数据库版本升级


一,前言

  没有采用Android自身提供的那一套数据库操作方式。而是想对SQLite数据库文件有更全面的控制,包括随时导出数据库文件修改表结构,增删数据等等。这样一来虽然在开放中得到不少便利,但是也带来了数据库升级的一些问题。

  后来不得已采用了一种方案,可以解决问题,现将方案的全部实现细节记录下来。最后也会提出一些我认为有问题的地方。

  二,数据库文件拷贝

  程序不负责数据库的创建,SQLite数据库文件是在外部创建好的。程序启动阶段拷贝进SD卡。以达到对数据库结构的全面控制。
  数据库文件存放位置见附件图片。

  Java代码
  

  1. public void copyDBFile() {
  2.   // 数据库路径
  3.   if (!FileOperator.checkFile(SysConst.DB_PATH)) {
  4.   boolean result = FileOperator.write2Sdcard(activity,
  5.   R.raw.scpip_collection, SysConst.DB_PATH);
  6.   Debug.log("无数据库文件,首次拷贝" + result);
  7.   if (!result) {
  8.   throw new IllegalAccessError(activity
  9.   .getString(R.string.copy_db_exception));
  10.   } else {
  11.   // 拷贝成功,更新数据库版本
  12.   try {
  13.   PackageInfo info = activity.getPackageManager()
  14.   .getPackageInfo(activity.getPackageName(), 0);
  15.   // 当前程序版本号,在AndroidManifest.xml中定义
  16.   int versionCode = info.versionCode;
  17.   Config.saveDbVer(versionCode);
  18.   Debug.log("拷贝成功" + result);
  19.   } catch (NameNotFoundException e) {
  20.   Debug.e(e);
  21.   }
  22.   }
  23.   } else {
  24.   // 数据库已存在的情况
  25.   if (dbUpdate.needUpdate() ) {
  26.   activity.showProgress("数据库升级中,请稍后", false);
  27.   new Thread() {
  28.   @Override
  29.   public void run() {
  30.   try {
  31.   Debug.log("update db");
  32.   dbUpdate.updateDb();
  33.   handler.sendEmptyMessage(0);
  34.   } catch (Exception e) {
  35.   Debug.e(e);
  36.   handler.sendEmptyMessage(-1);
  37.   }
  38.   };
  39.   }.start();
  40.   }
  41.   }
  42.   }
复制代码

  其中有几个要点:

  1、检测数据库文件是否已经存在。不存在则从raw文件夹复制数据库文件拷贝至SD卡中指定目录。

  2、数据库版本是根据应用的versionCode相同。拷贝数据库后,会把当前versionCode写入数据库的表中。

  Xml代码
  
  1. xmlns:android="http://schemas.android.com/apk/res/android"
  2.   package="com.xxx"
  3.   android:versionCode="2"
  4.   android:versionName="1.01.001">
  5.   versionCode在AndroidManifest.xml文件中。
复制代码

  在这种方案下,实际上是由versionCode控制数据库版本,versionName控制程序版本。

  3、SD卡指定目录已经存在数据库文件的情况,则读取其中保存的数据库版本号,与versionCode对比,从而确定是否需要升级数据库。代码如下:

  Java代码
  
  1. public boolean needUpdate() {
  2.   int currVer = Config.getDbVer();
  3.   return currVer < getAppVersion();
  4.   }
  5.   public int getAppVersion(){
  6.   try {
  7.   PackageInfo info = context.getPackageManager().getPackageInfo(
  8.   context.getPackageName(), 0);
  9.   // 当前程序版本号,在AndroidManifest.xml中定义
  10.   return info.versionCode;
  11.   } catch (NameNotFoundException e) {
  12.   Debug.e(e);
  13.   return 1;
  14.   }
  15.   }
复制代码

  三,升级数据库

  包括三个步骤:

  1、从程序中拷贝新数据库文件至SD卡指定目录,命名为temp.db。

  Java代码
 
  1.  String temp = SysConst.DB_FOLDER + "temp.db";
  2.   boolean s1 = FileOperator.write2Sdcard(context, R.raw.scpip_collection,temp);
复制代码

  2、分别获取两个数据源。

  Java代码
  
  1. //原数据库文件
  2.   BaseDao sd = new BaseDao();
  3.   //新数据库文件
  4.   BaseDao nd = new BaseDao(temp);
  5.   对于SQLite数据库来讲,数据源就是数据库文件。BaseDao是自己封装的,关键在于要可以配置不同的数据源
复制代码

  具体实现的相关代码如下:

  Java代码
  
  1. private String dbPath;
  2.   public BaseDao() {}
  3.   public BaseDao(String dbPath) {
  4.   this.dbPath = dbPath;
  5.   }
  6.   public SQLiteDatabase getDb() {
  7.   return SQLiteDatabase.openDatabase(SysConst.DB_PATH, null,
  8.   SQLiteDatabase.OPEN_READWRITE);
  9.   }
  10.   public SQLiteDatabase getDb(String dbPath) {
  11.   return SQLiteDatabase.openDatabase(dbPath, null,
  12.   SQLiteDatabase.OPEN_READWRITE);
  13.   }
复制代码

  这样就可以根据文件,获取不同的SQLiteDatabase 对象。

  3、传输数据

  把原数据库中的数据查询出来,插入到新数据库中。

  Java代码
  
  1. public void transfer(BaseDao sd,BaseDao nd,Class cls) throws Exception{
  2.   List list = sd.find(cls, null);
  3.   nd.batchInsert(list);
  4.   }
复制代码

  这里有两个要点,

  第一,使用了自行封装的ORM。

  第二,使用了SQLite批量插入,增加写入效率。代码如下:

  Java代码
  
  1. @Override
  2.   public void batchInsert(List datas) throws Exception {
  3.   SQLiteDatabase dba = null;
  4.   if(dbPath == null){
  5.   dba = getDb();
  6.   } else {
  7.   dba = getDb(dbPath);
  8.   }
  9.   int size = datas.size();
  10.   try {
  11.   dba.beginTransaction();
  12.   for (int i = 0; i < size; i++) {
  13.   DaoHelper helper = new DaoHelper(datas.get(i));
  14.   String tableName = helper.getTableName();
  15.   String sql = "select 1 from " + tableName + " where "
  16.   + helper.getPkCol() + "=";
  17.   Object id = helper.getPkValue();
  18.   if (id instanceof String) {
  19.   sql = sql + "'" + id + "'";
  20.   } else {
  21.   sql = sql + id;
  22.   }
  23.   c = dba.rawQuery(sql, null);
  24.   if (c != null ? (c.getCount() == 1) : false) {
  25.   c.close();
  26.   continue;
  27.   }
  28.   if(c != null){
  29.   c.close();
  30.   }
  31.   // SqlArgs sa = helper.prepareInsert();
  32.   // dba.execSQL(sa.getSql(), sa.getArgs());
  33.   dba.insert(helper.getTableName(), "", helper.getInsertContent());
  34.   }
  35.   dba.setTransactionSuccessful();
  36.   dba.endTransaction();
  37.   } finally {
  38.   dba.close();
  39.   }
  40.   }
复制代码

  4、写入当前数据库版本,即从程序获得的versionCode。

  5、删除原数据库文件,重命名temp.db。

  完整过程如下:

  Java代码
 
  1.  public void updateDb() throws Exception {
  2.   //1,写入新的数据库文件,命名为temp
  3.   String temp = SysConst.DB_FOLDER + "temp.db";
  4.   boolean s1 = FileOperator.write2Sdcard(context, R.raw.scpip_collection,temp);
  5.   Debug.log("s1:" + s1);
  6.   if(s1) {
  7.   //原数据库文件
  8.   BaseDao sd = new BaseDao();
  9.   //新数据库文件
  10.   BaseDao nd = new BaseDao(temp);
  11.   //转移数据
  12.   //此处代码略
  13.   //删除原数据库文件,重命名临时数据库文件
  14.   if(FileOperator.delSdcardFile(SysConst.DB_PATH)){
  15.   File file = new File(temp);
  16.   file.renameTo(new File(SysConst.DB_PATH));
  17.   }
  18.   //此时更新数据库版本
  19.   Config.saveDbVer(getAppVersion());
  20.   }
  21.   }
复制代码

  至此,整个数据库升级完成。在保留原数据的基础上,获取了新的数据库结构。

  四,问题

  1,需要保留的原数据与新表结构不符。这个可以在程序中控制,创建新的OR映射。

  2,效率问题,如果需要保留的数据量非常大的情况下,是否会出现问题。这个是亟待解决的,目前我还没有想到解决办法。

  五,总结

  这个方案针对的情况是外部创建数据库文件,程序启动时从apk包将数据库文件拷贝进SD卡,从而达到对数据库文件的完全控制。

  方案步骤:

  1,检测是否需要升级数据库文件。数据库文件版本是由versionCode控制。程序升级时,如果需要升级数据库,则要将versionCode+1。

  2,将新数据库文件(存在于apk中),写入SD卡。

  3,转移数据。

  4,删除原数据库文件,重命名新数据库文

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值