存储大量复杂的关系型数据
数据库的增(insert)删(delete)改(update)查(select)
创建数据库
数据库文件存放在/data/data/<package name>/databases
使用SQLiteOpenHelper类对数据库进行创建和升级。
两个抽象方法,onCreate()和onUpgrade(),在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑
两个实例方法, getReadableDatabase() 和getWritableDatabase():
这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。区别是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。
两个构造方法,使用少参的那个:
Public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
context:上下文,必须有才能对数据库进行操作
name:数据库的名称
factory:数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null。
version:当前数据库的版本号, 可用于对数据库进行升级操作。
建表语句: //integer 表示整型,real 表示浮点型,text 表示文本类型,blob 表示二进制类型
create table Book (id integer primary key autoincrement, author text, price real, pages integer, name text)
create table Book (
id integer primary key autoincrement, //id(主键),使用primary key 将id 列设为主键,autoincrement关键字表示id列是自增长的
author text, //作者
price real, //价格
pages integer, //页数
name text) //书名
布局:一个Button 创建数据库
1. 创建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)";
private Context context;
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { //构造方法
super(context, name, factory, version);
this.context = context;
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) { //当数据库存在了就不会再执行
sqLiteDatabase.execSQL(CREATE_BOOK); //执行建表语句
Toast.makeText(context, "创建成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
2. MainActivity
public class MainActivity extends AppCompatActivity {
MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
Button btn_create = (Button) findViewById(R.id.btn_create);
btn_create.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dbHelper.getWritableDatabase(); //创建数据库
}
});
}
}
升级数据库
要求:添加一个Category 表用于记录书籍的分类
create table Category (
id integer primary key autoincrement,
category_name text,
category_code integer)
接着例子A,作为例子B
MyDatabaseHelper的
onCreate()方法下添加:
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_BOOK);
sqLiteDatabase.execSQL(CREATE_CATEGORY);
Toast.makeText(context, "创建成功", Toast.LENGTH_SHORT).show(); //只有修改了MainActivity中的版本号才会重新创建成功
}
onUpgrade()方法下:
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
sqLiteDatabase.execSQL("drop table if exists Book"); //如果存在Book表,则废掉该表 //如果不废除Book表,那么在创建表时发现这张表已经存在了,就会报错。
sqLiteDatabase.execSQL("drop table if exists Category"); //如果存在Category表,则废掉该表
onCreate(sqLiteDatabase); //创建表
}
在MainActivity中
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2); //此处1变为2,即升级版本号(大于1均可)
添加数据
调用SQLiteOpenHelper 的getReadableDatabase()或getWritableDatabase()方法可以用于创建和升级数据库。同时这两个方法还都会返回一个SQLiteDatabase
对象,借助这个对象可以对数据进行增删改查操作。
insert(String table, String nullColumnHack, ContentValues values);
table:表名
nullColumnHack:如果给有些项没赋值,就自动赋值NULL(一般都为null)
values:ContentValues 对象,它提供了一系列的通过put()方法重载,向ContentValues 中添加数据
接着例子A,作为例子C
添加一个Button "添加数据"
Button btn_add = (Button) findViewById(R.id.btn_add);
btn_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db = dbHelper.getWritableDatabase(); //创建数据库
ContentValues values = new ContentValues(); //创建数据内容对象
values.put("name", "红楼梦"); //put()方法写入数据
values.put("author", "曹雪芹");
values.put("pages", 800);
values.put("price", 59.99);
db.insert("Book", null, values); //insert()方法向数据库添加数据
values.clear();
values.put("name", "三国演义");
values.put("author", "罗贯中");
values.put("pages", 600);
values.put("price", 49.99);
db.insert("Book", null, values);
}
});
Tip:同一个数据库做操作需要修改版本号
更新数据
update(String table, ContentValues values, String whereClause, String[] whereArgs);
table:表名
values:ContentValues 对象,它提供了一系列的通过put()方法重载,把更新的数据在这里组装进去
第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
接着例子C
添加一个Button "更新数据"
Button btn_update = (Button) findViewById(R.id.btn_update);
btn_update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db2 = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 44.99);
db2.update("Book", values, "name = ?", new String[]{"三国演义"}); //第三个参数表示修改name列,第四个参数表示name的值
}
});
(显示的BUG,为什么修改之后数据库就空了)
删除数据
delete(String table, String whereClause, String[] whereArgs); //参数说明如上
接着例子C
添加一个Button "删除数据"
Button btn_delete = (Button) findViewById(R.id.btn_delete);
btn_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db3 = dbHelper.getWritableDatabase();
db3.delete("Book", "pages > ?", new String[]{"750"}); //删除pages>750的数据
}
});
查询数据
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);
query()方法参数 | 对应SQL部分 | 描述 |
---|---|---|
table | from table_name | 指定查询的表名 |
columns | select column1, column2 | 指定查询的列名 |
selection | where column = value | 指定where 的约束条件 |
selectionArgs | - | 为where 中的占位符提供具体的值 |
groupBy | group by column | 指定需要group by 的列 |
having | having column = value | 对group by 后的结果进一步约束 |
orderBy | order by column1, column2 | 指定查询结果的排序方式 |
调用query()方法后会返回一个Cursor 对象,查询到的所有数据都将从这个对象中取出。
接着例子C
Button btn_delete = (Button) findViewById(R.id.btn_delete);
btn_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db4 = dbHelper.getWritableDatabase();
Cursor cursor = db4.query("Book", null, null, null, null, null, null); //调用query方法获取Cursor对象
if (cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
tv.setText(name + " " + author + " " + pages + " " + price); //添加一个显示数据的TextView
} while (cursor.moveToNext());
}
cursor.close();
}
});
使用SQL操作数据库
添加数据:
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "The Da Vinci Code", "Dan Brown", "454", "16.96" });
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });
更新数据:
db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99", "The Da Vinci Code" });
删除数据:
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
查询数据:
db.rawQuery("select * from Book", null);
使用事务
SQLite 数据库是支持事务的,事务的特性可以保证让某一系列的操作要么全部完成,要么一个都不会完成。例如完全更新数据库的数据,先删除数据,再添加数据,两个操作必须都完成或者都不完成。
接着例子C:
添加一个Button "废弃旧表创建新表"
Button btn_delete = (Button) findViewById(R.id.btn_delete);
btn_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db5 = dbHelper.getWritableDatabase();
db5.beginTransaction(); //开启事物
try {
db5.delete("Book", null, null);
// if (true) { //注释之后则会执行事务
// throw new NullPointerException(); //手动抛出异常,让事务失败
// }
//我们在删除旧数据的操作完成后手动抛出了一个NullPointerException,这样添加新数据的代码就执行不到了。不过由于事务的存在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。
ContentValues values3 = new ContentValues();
values3.put("name", "诗经");
values3.put("author", "未知");
values3.put("pages", 400);
values3.put("price", 38.99);
db5.insert("Book", null, values3);
db5.setTransactionSuccessful(); //事务执行成功
} catch (Exception e) {
e.printStackTrace();
} finally {
db5.endTransaction(); //结束事务
}
}
});
此为事务Transaction的标准用法↑
1. 调用SQLiteDatabase 的beginTransaction()方法来开启一个事务;
2. 在一个异常捕获的代码块中去执行具体的数据库操作;
3. 调用setTransactionSuccessful()表示事务已经执行成功;
4. 在finally代码块中调用endTransaction()来结束事务;
升级数据库的最佳写法
用onUpgrade()方法升级是删除了当前所有表,再重新创建表。此处要做的是在升级数据库的时候让数据不会丢失
在onUpgrade()方法中使用switch(oldVersion) - case 语句,都没有break
例子说明:
MyDatabaseHelper.java
初始:
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 MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_BOOK);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
}
}
要求1:升级数据库,向数据库 添加一张Category表
则需要添加的代码:
public static final String CREATE_CATEGORY = "create table Category (" //Category表
+ "id integer primary key autoincrement,"
+ "category_name text,"
+ "category_code integer)";
sqLiteDatabase.execSQL(CREATE_CATEGORY); //创建CATEGORY表
switch (oldVersion) { case 1: sqLiteDatabase.execSQL(CREATE_CATEGORY);
}default:
如果用户当前数据库的版本号是1,就只会创建一张Category 表。这样当用户是直接安装的第二版的程序时,就会将两张表一起创建。而当用户是使用第二版的程序覆盖安装第一版的程序时,就会进入到升级数据库的操作中,此时由于Book 表已经存在了,因此只需要创建一张Category 表即可。
要求2(继承要求1):给Book 表和Category 表之间建立关联,需要在Book 表中添加一个category_id 的字段
修改CREATE_BOOK,加入category_id字段
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)";
修改onUpgrade()方法:switch (oldVersion) { case 1: sqLiteDatabase.execSQL(CREATE_CATEGORY); case 2: sqLiteDatabase.execSQL("alter table Book add column category_id integer"); //当版本为2的时候,执行alter命令来为Book表新增category_id列 default: }
都没有break,是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版程序的,那么case 2 中的逻辑就会执行。而如果用户是直接从第一版程序升级到第三版程序的,那么case 1 和case 2 中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。