- 三种方式简单实现数据持久化功能
- 文件存储
- 简介
- 将数据存储到文件中
- 从文件中读取数据
- 扩展StringStringBuilderStringBuffer
- SharePreferences存储
- 注意点
- 获取SharePreferences对象的三种方式
- Context类中的 getSharedPreferences方法
- Activity类中的 getPreferences方法
- PreferenceManager类中的 getDefaultSharedPreferences方法
- 向SharedPreferences文件中存储数据三步骤
- 从Sharedpreferences中读取数据
- 实践记住密码功能
- SQLite 数据库存储一款轻量级的关系型数据库
- 创建数据库SQLiteOpenHelper帮助类
- SQLiteOpenHelper 抽象类
- 用adb查看数据库
- 升级数据库onUpgrade
- 添加数据 insert
- 更新数据 update
- 删除数据 delete
- 查询数据query
- 使用SQL操作数据库dbexecSQL
- 数据库的最佳实践
- 使用事务
- 升级数据库的最佳写法
- 实际项目中可以考虑这样
- 创建数据库SQLiteOpenHelper帮助类
- 文件存储
三种方式简单实现数据持久化功能
- 文件存储
- SharedPreference存储
- 数据库存储
文件存储
简介
文件存储是 Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式
化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合用于存储一些简单的
文本数据或二进制数据。
将数据存储到文件中
Context类: openFileOutput (),
可以用于将数据存储到指定的文件中。
两个参数,第一个参数是文件名,指定的文件名不可以包含路径,因为所有的文件都是默认存到/data/data/package name/files/目录下的。
第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE 和 MODE_APPEND。其中 MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而 MODE_APPEND则表示如果该文件已存在就往文件里面追加内容,不存在就创建新文件。
其实文件的操作模式本来还有另外两种,MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE,这两种模
式表示允许其他的应用程序对我们程序中的文件进行读写操作,不过由于这两种模式过于危
险,很容易引起应用的安全性漏洞,现已在 Android 4.2版本中被废弃。
openFileOutput ()方法返回的是一个 FileOutputStream对象,得到了这个对象之后就可以
使用 Java 流的方式将数据写入到文件中了。
public void save() {
String data = "Data to save";
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
openFileOutput(name, mode)返回FileOutPutStream,
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
以及最后要进行的关闭处理writer.close();
从文件中读取数据
Context类中还提供了一个 openFileInput()方法,
参数:文件名,系统会自动到/data/data//files/目录下去加载这个文件,并返回一个
FileInputStream对象
public String load() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}
openFileInput(name)->FileInputStream
StringBuilder content = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = “”;
while ((line = reader.readLine()) != null) {
content.append(line);
}
reader.close();
content.toString();
扩展String、StringBuilder、StringBuffer
1.如果要操作少量的数据用 = String
2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
StringBuffer与StringBuilder的区别主要是前者是线程安全的,就是说它是同步的;后者不安全,不是同步的,其它的区别不大。当你的程序不需要线程同步,一般都用StringBuilder.
StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度就快了。
String s = new String(“zzz”);
当你要改变s的时候,比如改为s =s+”dsdsdsd”;
系统会重新创建一个字符串变量它的值为”zzzdsdsdsd”,然后把该字符串赋值给s.
当你这样的改变s的行为在一个循环里面,那么将会创建大量的中间变量,影响程序的运行效率:
如:for(int i=0;i<100000;i++){
s=”a”;
}
StringBuilder sb = new StringBuilder();
一次性给sb分配一个固定长度大小的内存空间,当你改变的时候会在此空间后面加上,不够的时候,内存空间自动增加.比如初始分配的内存大小为10字节,
那么.Sb.append(“as”);它占据内存空间10字节,此时sb.toString().equals(“as”)为true;
Sb.append.(“qqq”),它占据的内存空间还是10字节,sb.toString().equals(“asqqq”)为true.
当内存空间不够的时候,自动加长,加入一次增加10字节,那么:
Sb.append(“ppppppp”),它占据的内存空间为20字节.
StringBuilder允许设定它的初始长度和每次增加的长度。
SharePreferences存储
注意点
- 文件存储在:/data/data/包名/shared_prefs /目录下
- 以键值对的方式进行存储。
获取SharePreferences对象的三种方式
Context类中的 getSharedPreferences()方法
参数:文件名称、mode
1、文件名称:第一个参数用于指定 SharedPreferences 文件的名称,如果指
定的文件不存在则会创建一个,SharedPreferences 文件都是存放在/data/data/package name/shared_prefs/目录下的。
2、指定操作模式:MODE_PRIVATE (默认,和0等值)和 MODE_MULTI_PROCESS。
MODE_PRIVATE 表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。MODE_MULTI_PROCESS则一般是用于会有多个进程中对同一个 SharedPreferences文件进行读写的情况。
=》MODE_WORLD_READABLE和 MODE_WORLD_WRITEABLE 这两种模式已在 Android 4.2 版本中被废弃(安全问题)
Activity类中的 getPreferences()方法
和 Context 中的 getSharedPreferences()方法很相似,不过它只接收一个操作模式参数,因为使用这个方法时会自动将当前活动的类名作为 SharedPreferences的文件名。
PreferenceManager类中的 getDefaultSharedPreferences()方法
这是一个静态方法,它接收一个 Context 参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences文件。
向SharedPreferences文件中存储数据三步骤
- 调用 SharedPreferences对象的 edit()方法来获取一个 SharedPreferences.Editor 对象。
- 向 SharedPreferences.Editor 对象中添加数据,比如添加一个布尔型数据就使用
putBoolean 方法,添加一个字符串则使用 putString()方法,以此类推。
- 调用 commit()方法将添加的数据提交,从而完成数据存储操作。
从Sharedpreferences中读取数据
SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");
实践:记住密码功能
自己之前项目里面的登录代码,还包括了记住之前登录过的账户,模仿QQ那种,记住的方式正好是用文件,等于结合上面两点,直接从项目拿出来,参考。
protected SharedPreferences sp;
protected SharedPreferences.Editor editor;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
initDate();
}
private Handler myHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
warnDialog.dismissWarnDialog();
}
};
private void initDate(){
ObjectInputStream in = null;
try {
InputStream is = openFileInput("account.obj");
in = new ObjectInputStream(is);
mList = (ArrayList<String>) in.readObject();
if (mList.size() > 0) {
nameText.setText(mList.get(0));
if(sp.getBoolean("isRemember", false)){
pwdText.setText(sp.getString(nameText.getText().toString().trim(), ""));
remPwd.setChecked(true);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
private void initPopup(){
mAdapter = new NameListAdapter(LoginActivity.this, mList);
mListView = new ListView(this);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
nameText.setText(mList.get(position));
pwdText.setText("");
mPopup.dismiss();
}
});
int height = ViewGroup.LayoutParams.WRAP_CONTENT;
int width = pwdText.getWidth();
System.out.println(width);
mPopup = new PopupWindow(mListView, width, height, true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(getResources().getDrawable(
R.drawable.rect_cycle_white_bg));
mPopup.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss() {
// TODO Auto-generated method stub
mShowing = false;
}
});
}
private void rememberName(){
String input = nameText.getText().toString();
mList.remove(input);
mList.add(0,input);
if (mList.size() > 5) {
mList.remove(0);
}
ObjectOutputStream out = null;
try {
FileOutputStream os = openFileOutput("account.obj",
MODE_PRIVATE);
out = new ObjectOutputStream(os);
out.writeObject(mList);
} catch (Exception e) {
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
@Override
protected void showIndexDialog() {
// TODO Auto-generated method stub
dialog = new IndexDialog(LoginActivity.this);
dialog.showDialog(R.layout.layout_index_button, LoginActivity.this.findViewById(R.id.login_layout), LoginActivity.this, true);
}
@Override
protected void quickStartClicked() {
// TODO Auto-generated method stub
}
@Override
protected void initView() {
// TODO Auto-generated method stub
nameEditLayout = (LinearLayout)findViewById(R.id.sr_username_edit_layout);
pwdText = (EditText)findViewById(R.id.sr_password);
nameText = (EditText)findViewById(R.id.sr_username);
initDropBtn();
initLoginBtn();
nameText.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
// TODO Auto-generated method stub
if(hasFocus){
nameEditLayout.setBackgroundResource(R.drawable.set_button_cir_white_border);
}else{
nameEditLayout.setBackgroundResource(R.drawable.rect_cycle_edit_white_gray);
}
}
});
remPwd = (CheckBox)findViewById(R.id.sr_remember);
findPwd = (TextView)findViewById(R.id.sr_reget_psw);
findPwd.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
gotoActivity(LoginActivity.this, ForgotPwdActivity.class);
LoginActivity.this.finish();
}
});
rgBtn = (Button)findViewById(R.id.sr_register);
rgBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
gotoActivity(LoginActivity.this, RegisterActivity.class);
LoginActivity.this.finish();
}
});
}
private void initDropBtn(){
showDrowBtn = (ImageView)findViewById(R.id.show_drop);
showDrowBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(mList!=null && mList.size()>0 && !mInitPopup){
mInitPopup = true;
initPopup();
}
if (mPopup != null) {
if (!mShowing) {
mPopup.showAsDropDown(nameText,0,-5);
mShowing = true;
} else {
mPopup.dismiss();
}
}
}
});
}
private void initLoginBtn(){
loginBtn = (Button)findViewById(R.id.sr_login);
loginBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(checkLogin()){
rememberName();
gotoActivity(LoginActivity.this, QuickStartRunActivity.class);
LoginActivity.this.finish();
}else{
warnDialog = new WarnDialog(LoginActivity.this);
warnDialog.showWarnDialog(R.layout.layout_dialog_error, R.string.login_error, true);
myHandler.sendEmptyMessageDelayed(0, 3000);
}
}
});
}
private boolean checkLogin(){
db = MainActivity.db;
String name = nameText.getText().toString().trim();
String pwd = pwdText.getText().toString().trim();
Cursor cursor = db.rawQuery("select * from TBL_PERSONAL_INFO where name = ?", new String[] { name });
if(cursor.moveToNext()){
if(pwd.equals(cursor.getString(3))){
if(remPwd.isChecked()){
editor.putBoolean("isRemember", true);
editor.putString(name, pwd);
}
else{
editor.putBoolean("isRemember", false);
}
editor.putString("name", name);
editor.commit();
cursor.close();
return true;
}
}
editor.putBoolean("isLogin", false);
editor.commit();
cursor.close();
return false;
}
SQLite 数据库存储——一款轻量级的关系型数据库
文件存储和SharedPreferences存储毕竟只适用于去保存一些简单的数据和键值对,当需要存储大量复杂的关系型数据的时候,你就会发现以上两种存储方式很难应付得了。比如我们手机的短信程序中可能会有很多个会话,每个会话中又包含了很多条信息内容,并且大部分会话还可能各自对应了电话簿中的某个联系人。存储这些数据量大、结构性复杂的数据,使用数据库就可以做得到。
创建数据库(SQLiteOpenHelper帮助类)
SQLiteOpenHelper 抽象类
SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法,分别是onCreate()和 onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
1、 getReadableDatabase() 和getWritableDatabase()
Android使用getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。(getReadableDatabase()方法中会调用getWritableDatabase()方法)
SQLiteOpenHelper 中 还 有 两 个 非 常 重 要 的 实 例 方 法。两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而 getWritableDatabase()方法则将出现异常。
二者区别和对应的源码解析,可以参考:http://blog.csdn.net/alex_zhuang/article/details/7342840简单易懂。这两个方法中会调用抽象类实现之后的onCreate()和 onUpgrade()
2、SQLiteOpenHelper构造方法
有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数,第一个参数是 Context。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null。第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。
3、获取数据库
构建出SQLiteOpenHelper的实例之后,再调用它的 getReadableDatabase()或 getWritableDatabase()方
法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的 onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
数据库建表语句
create table Book (
id integer primary key autoincrement,
author text,
price real,
pages integer,
name text)
转换成android中使用
public static final String CREATE_BOOK = "create table book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
+
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
}
实际应用中,比较好的方式:
创建一个单独的类,把表名、表内参数以及参数类型分三个变量。
public class SQLInfo {
private static String TableNames[] = {
"TBL_PERSONAL_INFO",
"TBL_RUN_DATA"
};//表名
private static String FieldNames[][] = {
{"ID","NAME","REAL_NAME","PWD","SEX","AGE","HEIGHT","WEIGHT","QUESTION_ONE","ANSWER_ONE","QUESTION_TWO","ANSWER_TWO","QUESTION_THREE","ANSWER_THREE"},
{"ID","NAME","USED_TIME","FINISH_DISTANCE","CALORIES","AVG_SPEED","AVG_INCLINE"}
};//字段名
private static String FieldTypes[][] = {
{"INTEGER PRIMARY KEY AUTOINCREMENT","TEXT","TEXT","TEXT","INTEGER","INTEGER","INTEGER","REAL","INTEGER","TEXT","INTEGER","TEXT","INTEGER","TEXT"},
{"INTEGER PRIMARY KEY AUTOINCREMENT","TEXT","INTEGER","REAL","INTEGER","REAL","INTEGER"}
};//字段类型
public static String[] getTableNames() {
return TableNames;
}
public static String[][] getFieldNames() {
return FieldNames;
}
public static String[][] getFieldTypes() {
return FieldTypes;
}
}
在自己创建的帮助类(继承SQLiteOpenHelper)中
public SQLDbHelper(Context context) {
super(context, myDBName, null, version);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
if(TableNames == null){
message = NO_CREATE_TABLES;
return;
}
for(int i = 0; i < TableNames.length; i++){
String sql = "CREATE TABLE " + TableNames[i] + " (";
for (int j = 0; j < FieldNames[i].length; j++)
{
sql += FieldNames[i][j] + " " + FieldTypes[i][j] + ",";
}
sql = sql.substring(0, sql.length() - 1);
sql += ")";
db.execSQL(sql);
}
}
}
用adb查看数据库
adb是 Android SDK中自带的一个调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。它存放在 sdk的 platform-tools 目录下,想直接使用记得配置路径到环境变量。
使用步骤:
(1)打开命令行界面,输入 adb shell,
(2)cd 进行到/data/data/com.example.databasetest/databases/目录下,ls
(3)这个目录下出现了两个数据库文件,一个正是我们创建的 BookStore.db,而另一个BookStore.db-journal 则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是 0 字节。
(4)借助 sqlite 命令来打开数据库了,只需要键入 sqlite3,后面加上数据库
名。
(5)此时数据库中会多一个android_metadata 表,每个数据库中都会自动生成
的。
升级数据库——onUpgrade()
注意:一旦数据库创建成功,除非是你删掉应用重新装,否则数据库已经存在,再次获取数据库的时候不会再调用onCreate,所以不能直接在里面升级修改数据库。
最简单除暴的升级
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Book");
db.execSQL("drop table if exists Category");
onCreate(db);
}
onUpgrade()方法的执行
SQLiteOpenHelper 的构造方法里第四个参数,当前数据库的版本号,只要传入一个比 之前版本大的数,就可以让 onUpgrade()方法得到执行了。
比如
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
添加数据—— insert()
CRUD: C 代表添加(Create),R 代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。每一种操作又各自对应了一种 SQL命令,添加数据时使用 insert,查询数据时使用 select,更新数据时使用 update,删除数据时使用 delete。
SQLiteDatabase 中提供了一个 insert()方法,这个方法就是专门用于添加数据的。它接收三个
参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个
参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般我们用不到这
个功能,直接传入 null 即可。第三个参数是一个 ContentValues 对象,它提供了一系列的 put()
方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名以及相应的待添加
数据传入即可。
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 开始组装第一条数据
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
db.insert("Book", null, values); // 插入第一条数据
values.clear();
// 开始组装第二条数据
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
db.insert("Book", null, values); // 插入第二条数据
}
});
更新数据—— update()
参数:第一个参数和 insert()方法一样,也是表名,指定去更新哪张表里的数
据。第二个参数是 ContentValues 对象,把更新数据在这里组装进去。第三、第四个参数
用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] { "The Da
Vinci Code" });
删除数据—— delete()
参数:接收三个参数,第一个参数仍然是表名,第二、第三个参数又是用于去约束删除某一
行或某几行的数据,不指定的话默认就是删除所有行。
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
查询数据——query()
参数:参数非常复杂,最短的一个方法重载也需要传入七个参数。第一个参数还是表名,第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参数用于指定需要去 group by的列,不指定则表示不对查询结果进行 group by操作。第六个参数用于对 group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查询Book 表中所有的数据
Cursor cursor = db.query("Book", null, null, null, null, null, null);
Cursor query (String table, String[] columns, String selection, String[] selectionArgs,
String groupBy, String having, String orderBy, String limit)
使用SQL操作数据库——db.execSQL
1、 添加数据
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });
2、 更新数据
db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99",
"The Da Vinci Code" });
3、 删除数据
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
4、 查询数据
db.rawQuery("select * from Book", null);
数据库的最佳实践
使用事务
SQLite 数据库是支持事务的,事务的特性可以保证让某一系列的操作要么全部完成,要么一个都不会完成。
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction(); // 开启事务
try {
db.delete("Book", null, null);
if (true) {
// 在这里手动抛出一个异常,让事务失败
throw new NullPointerException();
}
ContentValues values = new ContentValues();
values.put("name", "Game of Thrones");
values.put("author", "George Martin");
values.put("pages", 720);
values.put("price", 20.85);
db.insert("Book", null, values);
db.setTransactionSuccessful(); // 事务已经执行成功
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction(); // 结束事务
}
}
上述代码就是Android中事务的标准用法,首先调用SQLiteDatabase的beginTransaction()
方法来开启一个事务,然后在一个异常捕获的代码块中去执行具体的数据库操作,当所有的
操作都完成之后,调用 setTransactionSuccessful()表示事务已经执行成功了,最后在 finally
代码块中调用 endTransaction()来结束事务。注意观察,我们在删除旧数据的操作完成后手动
抛出了一个 NullPointerException,这样添加新数据的代码就执行不到了。不过由于事务的存
在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。
升级数据库的最佳写法
前面数据库升级的方式会导致用户升级应用时候,数据全部丢失,太简单粗暴了。
比如,多增加了一个表,此时如果用户重新升级覆盖,不会creat,所以,在升级中处理
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
public MyDatabaseHelper(Context context, String name,CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
}
}
}
没过多久,新的需求又来了,这次要给 Book表和 Category表之间建立关联,需要在 Book表中添加一个 category_id 的字段。再次修改 MyDatabaseHelper中的代码,如下所示:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text, "
+ "category_id integer)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
public MyDatabaseHelper(Context context, String name,CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL("alter table Book add column category_id integer");
default:
}
}
}
switch 中每一个 case 的最后都是没有使用 break的,为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版程序的,那么 case 2中的逻辑就会执行。而如果用户是直接从第一版程序升级到第三版程序的,那么 case 1 和 case 2 中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。
实际项目中可以考虑这样
/**打开数据库*/
public SQLiteHelper open() throws SQLException {
mDbHelper = new SQLDbHelper(mCtx);
mDb = mDbHelper.getWritableDatabase();
return this;
}
/**关闭数据库*/
public void close() {
mDbHelper.close();
}
public void execSQL(String sql) throws java.sql.SQLException
{
mDb.execSQL(sql);
}
/**sql语句查询数据*/
public Cursor rawQuery(String sql,String[] selectionArgs)
{
Cursor cursor = mDb.rawQuery(sql, selectionArgs);
return cursor;
}
/**查询数据*/
public Cursor select(String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy)
{
Cursor cursor = mDb.query
(
table, columns, selection, selectionArgs,
groupBy, having, orderBy
);
return cursor;
}
/**添加数据*/
public long insert(String table, String fields[], String values[])
{
ContentValues cv = new ContentValues();
for (int i = 0; i < fields.length; i++)
{
cv.put(fields[i], values[i]);
}
return mDb.insert(table, null, cv);
}
/**删除数据*/
public int delete(String table, String where, String[] whereValue)
{
return mDb.delete(table, where, whereValue);
}
/**更新数据*/
public int update(String table, String updateFields[],
String updateValues[], String where, String[] whereValue)
{
ContentValues cv = new ContentValues();
for (int i = 0; i < updateFields.length; i++)
{
cv.put(updateFields[i], updateValues[i]);
}
return mDb.update(table, cv, where, whereValue);
}