Android数据储存之SQLite存储数据【二】
原创
©著作权归作者所有:来自51CTO博客作者ch_kexin的原创作品,请联系作者获取转载授权,否则将追究法律责任
SQLiteOpenHelper类介绍:
SQLiteOpenHelper是SQLiteDatabase的一个帮助类,用来管理数据库的创建和版本的更新。一般是建立一个类继承它,并实现它的onCreate和onUpgrade方法。
方法名
| 方法描述
|
SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version) | 构造方法,其中 context 程序上下文环境 即:XXXActivity.this; name :数据库名字; factory:游标工厂,默认为null,即为使用默认工厂; version 数据库版本号 |
onCreate(SQLiteDatabase db) | 创建数据库时调用
|
onUpgrade(SQLiteDatabase db,int oldVersion , int newVersion) | 版本更新时调用
|
getReadableDatabase() | 创建或打开一个只读数据库
|
getWritableDatabase() | 创建或打开一个读写数据库
|
首先创建数据库类
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
public class SqliteDBHelper extends SQLiteOpenHelper {
// 步骤1:设置常数参量
private static final String DATABASE_NAME = "diary_db";
private static final int VERSION = 1;
private static final String TABLE_NAME = "diary";
// 步骤2:重载构造方法
public SqliteDBHelper(Context context) {
super(context, DATABASE_NAME, null, VERSION);
}
/*
* 参数介绍:context 程序上下文环境 即:XXXActivity.this
* name 数据库名字
* factory 接收数据,一般情况为null
* version 数据库版本号
*/
public SqliteDBHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
//数据库第一次被创建时,onCreate()会被调用
@Override
public void onCreate(SQLiteDatabase db) {
// 步骤3:数据库表的创建
String strSQL = "create table "
+ TABLE_NAME
+ "(tid integer primary key autoincrement,title varchar(20),weather varchar(10),context text,publish date)";
//步骤4:使用参数db,创建对象
db.execSQL(strSQL);
}
//数据库版本变化时,会调用onUpgrade()
@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
}
}
正如上面所述,数据库第一次创建时onCreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onUpgrade方法,我们可以执行修改表结构等语句。
我们需要一个Dao,来封装我们所有的业务方法,代码如下:
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.chinasoft.dbhelper.SqliteDBHelper;
public class DiaryDao {
private SqliteDBHelper sqliteDBHelper;
private SQLiteDatabase db;
// 重写构造方法
public DiaryDao(Context context) {
this.sqliteDBHelper = new SqliteDBHelper(context);
db = sqliteDBHelper.getWritableDatabase();
}
// 读操作
public String execQuery(final String strSQL) {
try {
System.out.println("strSQL>" + strSQL);
// Cursor相当于JDBC中的ResultSet
Cursor cursor = db.rawQuery(strSQL, null);
// 始终让cursor指向数据库表的第1行记录
cursor.moveToFirst();
// 定义一个StringBuffer的对象,用于动态拼接字符串
StringBuffer sb = new StringBuffer();
// 循环游标,如果不是最后一项记录
while (!cursor.isAfterLast()) {
sb.append(cursor.getInt(0) + "/" + cursor.getString(1) + "/"
+ cursor.getString(2) + "/" + cursor.getString(3) + "/"
+ cursor.getString(4)+"#");
//cursor游标移动
cursor.moveToNext();
}
db.close();
return sb.deleteCharAt(sb.length()-1).toString();
} catch (RuntimeException e) {
e.printStackTrace();
return null;
}
}
// 写操作
public boolean execOther(final String strSQL) {
db.beginTransaction(); //开始事务
try {
System.out.println("strSQL" + strSQL);
db.execSQL(strSQL);
db.setTransactionSuccessful(); //设置事务成功完成
db.close();
return true;
} catch (RuntimeException e) {
e.printStackTrace();
return false;
}finally {
db.endTransaction(); //结束事务
}
}
}
我们在Dao构造方法中实例化sqliteDBHelper并获取一个SQLiteDatabase对象,作为整个应用的数据库实例;在增删改信息时,我们采用了事务处理,确保数据完整性;最后要注意释放数据库资源db.close(),这一个步骤在我们整个应用关闭时执行,这个环节容易被忘记,所以朋友们要注意。
我们获取数据库实例时使用了getWritableDatabase()方法,也许朋友们会有疑问,在getWritableDatabase()和getReadableDatabase()中,你为什么选择前者作为整个应用的数据库实例呢?在这里我想和大家着重分析一下这一点。
我们来看一下SQLiteOpenHelper中的getReadableDatabase()方法:
public synchronized SQLiteDatabase getReadableDatabase() {
if (mDatabase != null && mDatabase.isOpen()) {
// 如果发现mDatabase不为空并且已经打开则直接返回
return mDatabase;
}
if (mIsInitializing) {
// 如果正在初始化则抛出异常
throw new IllegalStateException("getReadableDatabase called recursively");
}
// 开始实例化数据库mDatabase
try {
// 注意这里是调用了getWritableDatabase()方法
return getWritableDatabase();
} catch (SQLiteException e) {
if (mName == null)
throw e; // Can't open a temp database read-only!
Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
}
// 如果无法以可读写模式打开数据库 则以只读方式打开
SQLiteDatabase db = null;
try {
mIsInitializing = true;
String path = mContext.getDatabasePath(mName).getPath();// 获取数据库路径
// 以只读方式打开数据库
db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
if (db.getVersion() != mNewVersion) {
throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to "
+ mNewVersion + ": " + path);
}
onOpen(db);
Log.w(TAG, "Opened " + mName + " in read-only mode");
mDatabase = db;// 为mDatabase指定新打开的数据库
return mDatabase;// 返回打开的数据库
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase)
db.close();
}
}
在getReadableDatabase()方法中,首先判断是否已存在数据库实例并且是打开状态,如果是,则直接返回该实例,否则试图获取一个可读写模式的数据库实例,如果遇到磁盘空间已满等情况获取失败的话,再以只读模式打开数据库,获取数据库实例并返回,然后为mDatabase赋值为最新打开的数据库实例。既然有可能调用到getWritableDatabase()方法,我们就要看一下了:
public synchronized SQLiteDatabase getWritableDatabase() {
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
// 如果mDatabase不为空已打开并且不是只读模式 则返回该实例
return mDatabase;
}
if (mIsInitializing) {
throw new IllegalStateException("getWritableDatabase called recursively");
}
// If we have a read-only database open, someone could be using it
// (though they shouldn't), which would cause a lock to be held on
// the file, and our attempts to open the database read-write would
// fail waiting for the file lock. To prevent that, we acquire the
// lock on the read-only database, which shuts out other users.
boolean success = false;
SQLiteDatabase db = null;
// 如果mDatabase不为空则加锁 阻止其他的操作
if (mDatabase != null)
mDatabase.lock();
try {
mIsInitializing = true;
if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
// 打开或创建数据库
db = mContext.openOrCreateDatabase(mName, 0, mFactory);
}
// 获取数据库版本(如果刚创建的数据库,版本为0)
int version = db.getVersion();
// 比较版本(我们代码中的版本mNewVersion为1)
if (version != mNewVersion) {
db.beginTransaction();// 开始事务
try {
if (version == 0) {
// 执行我们的onCreate方法
onCreate(db);
} else {
// 如果我们应用升级了mNewVersion为2,而原版本为1则执行onUpgrade方法
onUpgrade(db, version, mNewVersion);
}
db.setVersion(mNewVersion);// 设置最新版本
db.setTransactionSuccessful();// 设置事务成功
} finally {
db.endTransaction();// 结束事务
}
}
onOpen(db);
success = true;
return db;// 返回可读写模式的数据库实例
} finally {
mIsInitializing = false;
if (success) {
// 打开成功
if (mDatabase != null) {
// 如果mDatabase有值则先关闭
try {
mDatabase.close();
} catch (Exception e) {
}
mDatabase.unlock();// 解锁
}
mDatabase = db;// 赋值给mDatabase
} else {
// 打开失败的情况:解锁、关闭
if (mDatabase != null)
mDatabase.unlock();
if (db != null)
db.close();
}
}
}
大家可以看到,几个关键步骤是,首先判断mDatabase如果不为空已打开并不是只读模式则直接返回,否则如果mDatabase不为空则加锁,然后开始打开或创建数据库,比较版本,根据版本号来调用相应的方法,为数据库设置新版本号,最后释放旧的不为空的mDatabase并解锁,把新打开的数据库实例赋予mDatabase,并返回最新实例。
看完上面的过程之后,大家或许就清楚了许多,如果不是在遇到磁盘空间已满等情况,getReadableDatabase()一般都会返回和getWritableDatabase()一样的数据库实例,所以我们在DBManager构造方法中使用getWritableDatabase()获取整个应用所使用的数据库实例是可行的。当然如果你真的担心这种情况会发生,那么你可以先用getWritableDatabase()获取数据实例,如果遇到异常,再试图用getReadableDatabase()获取实例,当然这个时候你获取的实例只能读不能写了
最后,让我们看一下如何使用这些数据操作方法来显示数据,界面核心逻辑代码:
public class SQLiteActivity extends Activity {
public DiaryDao diaryDao;
//因为getWritableDatabase内部调用了mContext.openOrCreateDatabase(mName, 0, mFactory);
//所以要确保context已初始化,我们可以把实例化Dao的步骤放在Activity的onCreate里
@Override
protected void onCreate(Bundle savedInstanceState) {
diaryDao = new DiaryDao(SQLiteActivity.this);
initDatabase();
}
class ViewOcl implements View.OnClickListener {
@Override
public void onClick(View v) {
String strSQL;
boolean flag;
String message;
switch (v.getId()) {
case R.id.btnAdd:
String title = txtTitle.getText().toString().trim();
String weather = txtWeather.getText().toString().trim();;
String context = txtContext.getText().toString().trim();;
String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date());
// 动态组件SQL语句
strSQL = "insert into diary values(null,'" + title + "','"
+ weather + "','" + context + "','" + publish + "')";
flag = diaryDao.execOther(strSQL);
//返回信息
message = flag?"添加成功":"添加失败";
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
break;
case R.id.btnDelete:
strSQL = "delete from diary where tid = 1";
flag = diaryDao.execOther(strSQL);
//返回信息
message = flag?"删除成功":"删除失败";
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
break;
case R.id.btnQuery:
strSQL = "select * from diary order by publish desc";
String data = diaryDao.execQuery(strSQL);
Toast.makeText(getApplicationContext(), data, Toast.LENGTH_LONG).show();
break;
case R.id.btnUpdate:
strSQL = "update diary set title = '测试标题1-1' where tid = 1";
flag = diaryDao.execOther(strSQL);
//返回信息
message = flag?"更新成功":"更新失败";
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
break;
}
}
}
private void initDatabase() {
// 创建数据库对象
SqliteDBHelper sqliteDBHelper = new SqliteDBHelper(SQLiteActivity.this);
sqliteDBHelper.getWritableDatabase();
System.out.println("数据库创建成功");
}
}