Android中不仅仅为我们提供了存储简单数据的方法,如文件存储和SharedPreference存储,还给我们提供了存储比较复杂的存储方式那就是使用SQLite数据库存储数据。下面就让我们来看看SQLite数据库是如何存储数据的。
在学习SQLite存储数据之前,让我们先了解了解它。
Android专门提供了一个SQLiteOpenHelper帮助类,借助这个类就可以非常简单地对数据库进行创建和升级了。SQLiteOpenHelper是一个抽象类,我们要定义一个类并重写它的两个方法:onCreate()方法:实现创建功能。onUpgrade()方法:实现升级数据库的功能。数据库文件会放在data/data/packagename/database/目录下。
SQLiteOpenHelper中有两个构造方法可供重写,我们一般使用参数写的那个构造方法。这个构造方法需要传入四个参数。第一个参数: Context。第二个参数:数据库名,创建数据库时使用的就是这里指定的名称。第三个参数:允许我们查询数据的时候返回一个自定义的Cursor,一般都是传入null。第四个参数:当前数据库的版本号,用于对数据库的升级。
我们可以通过使用getReadableDatabase()和getWritableDatabase()这两种方法开获得SQLiteOpenHelper的实例。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在就直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。这两个方法不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则出现异常。
学习SQLite数据库存储,我们怎么才能知道自己的数据库是否建成功了呢?还有我们创建的表是佛真的能成功的创建呢?下面我就跟大家分享一个工具,我们可以通过这个工具清楚的看见自己创建的表和数据库。
链接:http://pan.baidu.com/s/1hs6S7aO 密码:ajjt
这个工具的版本肯定有点点,更新不更新你们自己说了算。
integer 表示整形,real 表示浮点型,text表示文本型,blob 表示二进制类型,primary key 将 id列设为主键,autoincrement表示id列自增长。
public static final String CREATE_BOOk="create table book ("
+"id integer primary key autoincrement,"
+"author text,"
+"price real,"
+"name text)";
我自己创建一个类MyDatabaseHelper继承SQLiteOpenHelper类,界面就是一个按钮,点击按钮创建数据库。
1、布局文件的代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:id="@+id/create_database"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Create Database" />
LinearLayout>
2、MyDatabaseHelper和MainActivity的代码:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOk="create table book ("
+"id integer primary key autoincrement,"
+"author text,"
+"price real,"
+"name text)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext=context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOk);
Toast.makeText(mContext, "Create succeeded",
Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public class MainActivity extends Activity implements OnClickListener {
private MyDatabaseHelper dbHelper;
private Button createDatabaseeBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper=new MyDatabaseHelper(this, "BookStore.db", null, 1);
createDatabaseeBtn=(Button) findViewById(R.id.create_database);
createDatabaseeBtn.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.create_database:
dbHelper.getReadableDatabase();
break;
default:
break;
}
}
}
我们可以借助我的分享的那个工具,可以在视图中看见我创建数据库和表都存在,并且表和我写的SQL语句也完全相符。有图有真相;
我们已经成功的创建一个BookStore数据库和一张book的表了。光有表也不行呀。我们要往表里添加数据呀。SQLiteDatabase中提供了一个insert()方法,这个方法是专门用于添加数据的。它接收三个参数,第一个参数:表名,我们希望向哪张表里添加数据就传入该表的名字。第二个参数:在未指定添加数据的情况下给某些可为空的列自动添加null,一般我们用不到这个功能,直接传入null就行。 第三个参数:一个ContextValues对象,它提供了一系列的put()方法的重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。下面我们就往book表中添加一条数据。
因为代码涉及的比较都,我就只把有关向数据库中插入一条数据的代码给大家看一下吧:
1、activity_main.xml文件中添加了一个按钮,用来插入一天数据。
2、在MainActivity中找到这个按钮,并写点击事件:
private Button insertDataBtn;
insertDataBtn=(Button) findViewById(R.id.insert_data);
insertDataBtn.setOnClickListener(this);
case R.id.insert_data:
SQLiteDatabase insertDatabase=dbHelper.getReadableDatabase();
ContentValues insertContentValues =new ContentValues();
//第一条数据
insertContentValues.put("name", "ThinkInJava");
insertContentValues.put("author", "Dan Brown");
insertContentValues.put("pages", 454);
insertContentValues.put("price", 16.96);
//添加到数据库中
insertDatabase.insert("book", null, insertContentValues);
//清除数据,重新组装数据
insertContentValues.clear();
insertContentValues.put("name", "AndroidDeveloper");
insertContentValues.put("author", "Dan Brown");
insertContentValues.put("pages", 580);
insertContentValues.put("price", 23.58);
//添加第二条数据
insertDatabase.insert("book", null, insertContentValues);
break;
还是那句话,有图有真相,我们将新的Book.db这个数据库导出,用软件打开一下看看:
如果您更加习惯使用数据的SQL语句,那么您可以这么写:
insertDatabase.execSQL("insert into book(name, author, pages, price)values(?, ?, ?, ?)",
new String[]{"ThinkInJava", "Dan Brown", "454", "16.96"});
同Insert()方法一样,SQLiteDatabase也提供了一个非常好用的update()方法,用于对数据进行更新,这个方法接收四个参数。第一个参数;
表名,指定去更新那张表里的数据。第二个参数:*ContentValues对象,把要更新的数据放到这里组装进去。第三、四个参数:*用于去约束更新某一行或某几行中的数据,不指定的话就是默认更新所有行。
下面我们就更新book表中一条数据。
还是跟Insert()方法一样,我只贴出跟update()方法有关的代码:
private Button updateDataBtn;
updateDataBtn=(Button) findViewById(R.id.update_data);
updateDataBtn.setOnClickListener(this);
case R.id.update_data:
SQLiteDatabase updeteDatabase =dbHelper.getReadableDatabase();
ContentValues updetaContentValues=new ContentValues();
updetaContentValues.put("price", 10.99);
updeteDatabase.update("book", updetaContentValues, "name = ?", new String[]{"ThinkInJava"});
break;
有图有真相:
如果您更加习惯使用数据的SQL语句,那么您可以这么写:
updeteDatabase.execSQL("update book set price = ? where name = ?",new String[]{"10.99", "ThinkInJava"});
同update()方法一样,SQLiteDatabase也提供了一个非常好用delete()方法,用于对数据进行删除,这个方法接收三个参数。第一个参数;
表名,指定去删除那张表里的数据。第二、三个参数:用于去约束删除某一行或某几行中的数据,不指定的话就是默认删除所有行。
下面我们就删除book表中一条数据。
还是跟update()方法一样,我只贴出跟delete()方法有关的代码:
private Button deleteDataBtn;
deleteDataBtn=(Button) findViewById(R.id.delete_data);
deleteDataBtn.setOnClickListener(this);
case R.id.delete_data:
SQLiteDatabase deleteDatabase =dbHelper.getReadableDatabase();
deleteDatabase.delete("book", "pages > ?", new String[]{"500"});
break;
有图有真相:我们可以看见就剩下一条数据了。
如果您更加习惯使用数据的SQL语句,那么您可以这么写:
deleteDatabase.execSQL("delete from book where pages > ?", new String[]{"500"});
SQLiteDatabase中提够了一个query()方法用于对数据进行查询。这个方法有点变态需要传入7个参数,我们来看看这7个参数的具体意义吧:
第一个参数:表名,从哪张表中查找数据。第二个参数:查询那几列,如果不指定则默认查询所有列。第三、四个参数:约束某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参数:
指定需要 group by 的列,不指定则表示不对查询结果进行 group by。
第六个参数:用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数:用于指定查询结构的排序方式,不指定则表示使用默认的排序方式。
为了让大家更加清楚的这7个参数的意义,我给大家一张图:
query()方法会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。
还是跟delete()方法一样,我只贴出query()方法有关的代码:
private Button queryDataBtn;
queryDataBtn=(Button) findViewById(R.id.query_data);
queryDataBtn.setOnClickListener(this);
case R.id.query_data:
SQLiteDatabase queryDatabase=dbHelper.getReadableDatabase();
Cursor cursor =queryDatabase.query("book", null, null, null, null, null, null);
if (cursor.moveToNext()) {
do {
//遍历cursor对象,取出数据并打印
String name=cursor.getString(cursor.getColumnIndex("name"));
//getColumnIndex()方法获取到某一列在表中对 //应的位置索引
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price =cursor.getDouble(cursor.getColumnIndex("price"));
Log.i("MainActivity", "book name is "+name);
Log.i("MainActivity", "book author is "+author);
Log.i("MainActivity", "book pages is "+pages);
Log.i("MainActivity", "book price is "+price);
} while (cursor.moveToNext());
}
cursor.close();
break;
这次查询语句没有图让大家看了,但是,我有Log日志:
06-07 10:47:57.232: I/MainActivity(31744): book name is ThinkInJava
06-07 10:47:57.232: I/MainActivity(31744): book author is Dan Brown
06-07 10:47:57.232: I/MainActivity(31744): book pages is 454
06-07 10:47:57.232: I/MainActivity(31744): book price is 10.99
如果您更加习惯使用数据的SQL语句,那么您可以这么写:
queryDatabase.execSQL("select * from book", null);
SQLite数据库时支持事务的,事务的特性可以保证让某一些列的操作要么全部完成,要么一个都不会完成。什么时候使用事务(Transcation)?
比如你正在进行一次转账操作,银行会将转账的金额先从你的账户中扣除,然后再向收款方的账户添加等量的金融。如果当你账户中的金额刚刚被扣除,这时由于一些异常原因导致对方收款失败,这一部分钱就凭空消失了!当然银行肯定已经充分考虑到了这种情况,它会保证扣钱和收款的操作要么一起成功,要么都不会成功,而使用的技术就是事务。
还是跟query()方法一样,我只贴出使用事务有关的代码:
private Button useTransaction;
useTransaction=(Button) findViewById(R.id.use_Transaction);
useTransaction.setOnClickListener(this);
case R.id.use_Transaction:
SQLiteDatabase transactionDatabase=dbHelper.getReadableDatabase();
//开启事务
transactionDatabase.beginTransaction();
try {
transactionDatabase.delete("book", null, null);
if (true) {
//在这里手动抛出一个异常,让事务失败
throw new NullPointerException();
}
ContentValues transactionContentValues =new ContentValues();
//第一条数据
transactionContentValues.put("name", "C##");
transactionContentValues.put("author", "Google");
transactionContentValues.put("pages", 720);
transactionContentValues.put("price", 20.85);
//添加到数据库中
transactionDatabase.insert("book", null, insertContentValues);
//事务已经执行完成
transactionDatabase.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally{
transactionDatabase.endTransaction();
}
break;
由于我在执行完delete()方法会,手动的抛出了一个空指针异常,所以,数据库没有成功的删除我的数据。也灭有插入新的数据。
当我把手动抛出异常的代码注释掉以后,在运行程序,就执行成功了。
当我们继承SQLiteOpenHelper类的时候,我们需要重写两个方法,上面已经介绍了onCreate的作用了,现在就要介绍一个onUpgrade()方法。该方法主要是用于对数据库的升级作用。
如果我们想在BookStore.db数据库中添加另一张Category表,如何写呢?
这里我将MyDatabaseHelper中添加的代码贴出来,给大家看:
1、首先创建一条创建表的SQL语句。
public static final String CREATE_CATEGORY="create table category ("
+"id integer primary key autoincrement,"
+"category_name text,"
+"category_code integer)";
2、然后修改onCreate()方法和onUpgrade()方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOk);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded",
Toast.LENGTH_SHORT).show();
}
@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);
}
3、最后修改一行MainActivity中的代码:
//dbHelper=new MyDatabaseHelper(this, "BookStore.db", null, 1);
//这里将数据库的版本号从1变成了2,就是为了让onUpgrade()方法执行。
dbHelper=new MyDatabaseHelper(this, "BookStore.db", null, 2);
有图有真相:
作为程序猿的我们最烦的就是变幻不断的需求,然而这并不能在开发过程中避免,一个产品的畅行,就是因为不断让程序猿恶心的需求的不多改变。当我们面对这些需求是,如何写我们的数据库,才能做到最好呢?
下面我们就模拟一下,当我们面对变态的需求是,我们如何去写更新数据库:
1、版本一:我们的数据库中就有一张book表。
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOk="create table book ("
+"id integer primary key autoincrement,"
+"author text,"
+"price real,"
+"name text)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext=context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOk);
Toast.makeText(mContext, "Create succeeded",
Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2、版本二:过了几个星期,老板让你在数据库中添加一张Category表,如果你的APP已经上线了并且还有不错的下载量,但是现在的需求让你在数据库中添加一张Category表,如果你用的方法是:
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOk);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded",
Toast.LENGTH_SHORT).show();
}
@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);
}
当用户更新了这个版本之后发现以前程序中存储的本地数据全部丢失了,那么很遗憾,你的用户群体可能就会流失一半,那么如何才能避免这种情况呢?我们应该按照下面的方法去写。
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)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext=context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOk);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//如果用户使用的APP中的数据库的版本号是1的话,就只会创建Category表。
//当用户是直接安装的第二版本时,就会将两张表一起创建。
//当用户使用第二版本覆盖安装第一版本时,就会进入到升级数据库的操作,只会
//创建一张Category表。
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
break;
}
}
}
3、又过了几个星期,又有新的需求:给book表和category表之间建立联系,需要在book表中添加一个category_id的字段。
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)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext=context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOk);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//如果用户使用的APP中的数据库的版本号是1的话,就只会创建Category表。
//当用户是直接安装的第二版本时,就会将两张表一起创建。
//当用户使用第二版本覆盖安装第一版本时,就会进入到升级数据库的操作,只会
//创建一张Category表。
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL("alter table Book add column category_id integer");
default:
break;
}
}
}
switch中每一个case的最后都是没有使用break的,为什么要这么做呢?这是为了保证在跨越版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版本升级到第三版本程序中,那么case 2 中的逻辑就会执行。如果用户是直接从第一版本升级到第三版本程序的,那么case 1 和 case 2 中的逻辑都会执行。使用这种方式维护数据库的升级,不管版本怎么更新,都可以保证数据库的表结构时最新的,而且表中的数据也完全不会丢失。