续上一博文(Android核心基础-5.Android 数据存储与访问-2.使用SharedPreferences进行数据存储)
三、使用Sqlite进行数据存储
3.1Sqlite数据库简介
SQLite,是一款轻型的数据库,是遵守ACID(原子性、一致性、隔离性、持久性)的关联式数据库管理系统,多用于嵌入式开发中。
SQLite的数据类型:Typelessness(无类型), 可以保存任何类型的数据到你所想要保存的任何表的任何列中. 但它又支持常见的类型比如: NULL, VARCHAR, TEXT, INTEGER, BLOB, CLOB…等. 唯一的例外:integer primary key 此字段只能存储64位整数
3.2Android对Sqlite的支持
- 在Android系统,提供了一个SQLiteOpenHelper抽象类,该类用于对数据库版本进行管理.该类中常用的方法:
- onCreate 数据库创建时执行(第一次连接获取数据库对象时执行)
- onUpgrade 数据库更新时执行(版本号改变时执行)
- onOpen 数据库每次打开时执行(每次打开数据库时调用,在 onCreate,onUpgrade方法之后)
MyHelper.class
package net.dxs.sqlite.dao;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class MyHelper extends SQLiteOpenHelper {// 自定义类继承SQLiteOpenHelper
public MyHelper(Context context) {// 由于父类没有无参构造函数, 定义一个构造函数调用父类有参的构造函数
/*
* 参数1: Context代表应用程序的环境, 用来确定数据库文件的位置
* 参数2: 数据文件的名字
* 参数3: 用来创建Cursor(结果集)的工厂, 默认传null就可以
* 参数4: 数据库的版本, 后期用来更新数据库, 从1开始
*/
super(context, "dxs.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {// 在数据库文件创建之后执行
System.out.println("onCreate");
db.execSQL("CREATE TABLE account(_id INTEGER PRIMARY KEY AUTOINCREMENT,name VARCHAR(20))");// 执行SQL语句, 创建表
db.execSQL("ALTER TABLE account ADD balance INTEGER");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {// 在数据库版本提升之后执行
System.out.println("onUpgrade");
db.execSQL("ALTER TABLE account ADD balance INTEGER");
}
}
3.3Android操作Sqlite的API
Android提供了一个名为SQLiteDatabase的类,该类封装了一些操作数据库的API,使用该类可以完成对数据进行添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)操作(这些操作简称为CRUD)。对SQLiteDatabase的学习,我们应该重点掌握execSQL()和rawQuery()方法。 execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句; rawQuery()方法用于执行select语句。
3.3.1execSQL()方法使用
execSQL()方法的使用例子:
SQLiteDatabase db = ....;
db.execSQL("insert into person(name, age) values('深情小建', 4)");
db.close();
执行上面SQL语句会往person表中添加进一条记录,在实际应用中, 语句中的“深情小建”这些参数值会由用户输入界面提供,如果把用户输入的内容原样组拼到上面的insert语句, 当用户输入的内容含有单引号时,组拼出来的SQL语句就会存在语法错误。要解决这个问题需要对单引号进行转义,也就是把单引号转换成两个单引号。有些时候用户往往还会输入像“ & ”这些特殊SQL符号,为保证组拼好的SQL语句语法正确,必须对SQL语句中的这些特殊SQL符号都进行转义,显然,对每条SQL语句都做这样的处理工作是比较烦琐的。 SQLiteDatabase类提供了一个重载后的execSQL(String sql, Object[] bindArgs)方法,使用这个方法可以解决前面提到的问题,因为这个方法支持使用占位符参数(?)。
使用例子如下:
SQLiteDatabase db = ....;
db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"深情小建", 4});
db.close();
execSQL(String sql, Object[] bindArgs)方法的第一个参数为SQL语句,第二个参数为SQL语句中占位符参数的值,参数值在数组中的顺序要和占位符的位置对应。
3.3.2rawQuery()方法使用
SQLiteDatabase的rawQuery() 用于执行select语句,
使用例子如下:
SQLiteDatabase db = ....;
Cursor cursor = db.rawQuery(“select * from person”, null);
while (cursor.moveToNext()) {
int personid = cursor.getInt(0); //获取第一列的值,第一列的索引从0开始
String name = cursor.getString(1);//获取第二列的值
int age = cursor.getInt(2);//获取第三列的值
}
cursor.close();
db.close();
rawQuery()方法的第一个参数为select语句;第二个参数为select语句中占位符参数的值,如果select语句没有使用占位符,该参数可以设置为null。
带占位符参数的select语句使用例子如下:
Cursor cursor = db.rawQuery("select * from person where name like ? and age=?", new String[]{"%小建%", "4"});
Cursor是结果集游标,用于对结果集进行随机访问,如果大家熟悉jdbc, 其实Cursor与JDBC中的ResultSet作用很相似。
使用moveToNext()方法可以将游标从当前行移动到下一行,如果已经移过了结果集的最后一行,返回结果为false,否则为true。
另外Cursor 还有常用的moveToPrevious()方法(用于将游标从当前行移动到上一行,如果已经移过了结果集的第一行,返回值为false,否则为true )。
moveToFirst()方法(用于将游标移动到结果集的第一行,如果结果集为空,返回值为false,否则为true )和moveToLast()方法(用于将游标移动到结果集的最后一行,如果结果集为空,返回值为false,否则为true ) 。
3.3.3其他封装好的方法
除了execSQL()和rawQuery()方法, SQLiteDatabase还专门提供了对应于添加、删除、更新、查询的操作方法:
insert() 增加数据
delete() 删除数据
update() 修改数据
query() 查询数据
这些方法实际上是给那些不太了解SQL语法的开发者使用的,对于熟悉SQL语法的程序员而言,直接使用execSQL()和rawQuery()方法执行SQL语句就能完成数据的添加、删除、更新、查询操作。
3.3.4事务
使用SQLiteDatabase的beginTransaction()方法可以开启一个事务,程序执行到endTransaction() 方法时会检查事务的标志是否为成功,如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 方法设置事务的标志为成功则提交事务,如果没有调用setTransactionSuccessful() 方法则回滚事务。
使用例子如下:
SQLiteDatabase db = ....;
db.beginTransaction();//开始事务
try {
db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"深情小建", 4});
db.execSQL("update person set name=? where personid=?", new Object[]{"小建", 1});
db.setTransactionSuccessful();//调用此方法会在执行到endTransaction() 时提交当前事务,如果不调用此方法会回滚事务
} finally {
db.endTransaction();//由事务的标志决定是提交事务,还是回滚事务
}
db.close();
上面两条SQL语句在同一个事务中执行。
AccountDao.class
package net.dxs.sqlite.dao;
import java.util.ArrayList;
import java.util.List;
import net.dxs.sqlite.bean.Account;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
public class AccountDao {
private MyHelper helper;
public AccountDao(Context context) {
helper = new MyHelper(context);
}
public void insert(Account a) {
// 获取SQLiteDatabase对象
SQLiteDatabase db = helper.getWritableDatabase();
// 执行一条SQL语句
db.execSQL("insert into account(name,balance) values(?,?)", new Object[] { a.getName(), a.getBalance() });
// 关闭
db.close();
}
public void delete(int id) {
// 获取SQLiteDatabase对象
SQLiteDatabase db = helper.getWritableDatabase();
//执行删除语句
db.execSQL("delete from account where _id=?", new Object[] { id });
// 关闭
db.close();
}
public void update(Account a) {
// 获取SQLiteDatabase对象
SQLiteDatabase db = helper.getWritableDatabase();
//执行删除语句
db.execSQL("update account set name=?,balance=? where _id=?", new Object[] { a.getName(), a.getBalance(), a.getId() });
// 关闭
db.close();
}
public Account query(int id) {
// 先调用getWriteableDatabse(), 如果出了异常, 就开一个只读的
SQLiteDatabase db = helper.getReadableDatabase();
// 执行查询语句, 得到结果集
Cursor c = db.rawQuery("select name,balance from account where _id=?", new String[] { String.valueOf(id) });
Account a = null;
if (c.moveToNext()) {// 判断结果集是否包含下一条数据, 如果包含, 指针自动向后移动
String name = c.getString(0);// 从结果集中获取数据(根据列的索引获取)
int balance = c.getInt(c.getColumnIndex("balance"));// 从结果集中获取数据(先根据列名获取索引, 再根据索引获取数据)
a = new Account(id, name, balance);// 创建对象, 把数据设置到对象中
return a;
}
// 关闭
c.close();
db.close();
return null;// 返回对象
}
public List<Account> queryAll() {
SQLiteDatabase db = helper.getReadableDatabase();
Cursor c = db.rawQuery("select * from account", null);// 查询表中所有数据
List<Account> list = new ArrayList<Account>();
while (c.moveToNext()) {
int id = c.getInt(0);// 获取表中的第一列(索引从0开始)
String name = c.getString(1);
int balance = c.getInt(2);
list.add(new Account(id, name, balance));// 把表中的数据封装成对象
}
c.close();
db.close();
return list;
}
public Cursor queryCursor() {
SQLiteDatabase db = helper.getReadableDatabase();
return db.rawQuery("select * from account order desc balance", null);
}
/**
*
* @param pageSize 每页显示多少条
* @param pageNum 要第几页的数据
* @return
*/
public List<Account> queryPage(int pageSize, int pageNum) {
String index = (pageNum - 1) * pageSize + ""; // 翻页时的起始索引
String count = pageSize + ""; // 查询多少条数据
List<Account> list = new ArrayList<Account>();
SQLiteDatabase db = helper.getReadableDatabase();
Cursor c = db.rawQuery("select * from account limit ?,?", new String[]{index,count});
// Cursor c = db.query("account", null, null, null, null, null, null, index + "," + count);
while (c.moveToNext()) {
int id = c.getInt(0);
String name = c.getString(1);
int balance = c.getInt(2);
list.add(new Account(id, name, balance));
}
c.close();
db.close();
return list;
}
public int queryCount() {
SQLiteDatabase db = helper.getReadableDatabase();
Cursor c = db.rawQuery("select count(*) from account", null);
c.moveToNext();
int count = c.getInt(0);
c.close();
db.close();
return count;
}
public void remit(int fromId, int toId, int amount) {
SQLiteDatabase db = helper.getReadableDatabase();
try {
db.beginTransaction(); //开启事务
db.execSQL("update account set balance=balance-? where _id=?", new Object[] { amount, fromId });
db.execSQL("update account set balance=balance+? where _id=?", new Object[] { amount, toId });
db.setTransactionSuccessful();// 设置事务成功标记
} finally {
db.endTransaction();//结束事务
db.close();
}
}
}
3.4Android sqlite3工具的使用
这里提供下我百度网盘的下载链接(SQLiteExpert)
sqlite3 <数据库名称> 进入数据库操作模式 eg: sqlite3 contacts.db
tables 查看所有的表 eg: table
schema 查看查看库中所有表的DDL语句 eg: schema
help 查看帮助 eg: help
headers on/off 显示表头 默认off eg: headers on
mode list|column|insert|line|tabs|tcl|csv 改变输出格式 eg: mode column
nullValue NULL空值数据显示问题 eg: nullValue NULL
dump <表名> 生成形成表的SQL脚本 eg: dump person
dump 生成整个数据库的SQL脚本 eg: dump
exit 退出sqlite操作模式 eg: exit
3.5写在最后
一.SQLite数据库**
1.SQLite数据库的特点
安卓手机自带, 小巧, 适合在手机中使用
不区分数据类型(主键除外)
SQL语句和MySQL几乎相同
SQLite不使用JDBC连接, 使用的是Android自有的API
每个数据库对应一个文件
* 2.创建数据库
定义类继承SQLiteOpenHelper, 实现onCreate(), onUpgrade()
创建该类对象, 调用getWritableDatabse()或者getReadableDatabse()
情况1: 数据库文件不存在, 创建文件, 打开数据库连接(得到SQLiteDatabase对象), 执行onCreate()方法
情况2: 数据库文件存在, 版本号没变, 打开数据库连接
情况3: 数据库文件存在, 版本号提升, 升级数据库, 打开数据库连接,执行onUpgrade()方法
情况4: 数据库文件存在, 版本号降低, 执行onDowngrade()方法, 方法中默认会抛出一个异常
* 3.创建表或修改表
SQLiteDatabase类的execSQL()方法可以执行一条SQL语句
如果希望创建数据库的时候就创建一些表, 那么这个操作就可以在onCreate()方法中执行
如果希望在数据库升级的时候做类似修改表添加表的操作, 可以在onUpgrade()方法中执行
*** 4.增删改查
execSQL()方法可以进行增删改操作
rawQuery()执行查询操作, 得到Cursor, 调用moveToNext()判断是否包含数据, 调用getString(), getInt()等方法获取数据
insert(), delete(), update(), query() 四个方法内部也是调用execSQL()和rawQuery()的, 它们在ContentProvider中使用更方便(另一篇博文讲)
* 5.事务管理
beginTransaction() 开启事务
setTransactionSuccessful() 设置事务成功标记
endTransaction() 结束事务.
事务结束的时候, 会把最后一个成功标记之前的操作提交, 成功标记之后的操作回滚
Account.class
package net.dxs.sqlite.bean;
public class Account {
private Integer id;
private String name;
private Integer balance;
public Account() {
}
public Account(String name, Integer balance) {
super();
this.name = name;
this.balance = balance;
}
public Account(Integer id, String name, Integer balance) {
super();
this.id = id;
this.name = name;
this.balance = balance;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getBalance() {
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", balance=" + balance + "]";
}
}
SQLiteTest.class
package net.dxs.sqlite.test;
import java.util.List;
import java.util.Random;
import net.dxs.sqlite.bean.Account;
import net.dxs.sqlite.dao.AccountDao;
import net.dxs.sqlite.dao.MyHelper;
import android.test.AndroidTestCase;
public class SQLiteTest extends AndroidTestCase {
// 测试类中的Context对象是在测试类创建之后(构造函数执行之后), 虚拟机自动调用setContext()传入的, 如果在成员变量位置就getContext()则拿不到
private AccountDao dao;
@Override
protected void setUp() throws Exception {// 测试方法执行之前执行
dao = new AccountDao(getContext());
}
@Override
protected void tearDown() throws Exception {// 测试方法执行之后执行
}
public void testCreateDB(){
new MyHelper(getContext()).getWritableDatabase();// 获取数据库对象
/*
* 情况1: 数据库文件不存在, 创建文件, 打开数据库连接(得到SQLiteDatabase对象), 执行onCreate()方法
* 情况2: 数据库文件存在, 版本号没变, 打开数据库连接
* 情况3: 数据库文件存在, 版本号提升, 升级数据库, 打开数据库连接,执行onUpgrade()方法
* 情况4: 数据库文件存在, 版本号降低, 执行onDowngrade()方法, 方法中默认会抛出一个异常
*/
}
public void testInsert(){
for (int i = 0; i < 100; i++) {
dao.insert(new Account("深情小建"+i, new Random().nextInt(10000)));
}
}
public void testDelete(){
dao.delete(20);
}
public void testUpdate(){
dao.update(new Account(1,"深情小建", 12000));
dao.update(new Account(2,"刘德华", 12000));
dao.update(new Account(3,"武状元", 12000));
}
public void testQuery(){
System.out.println(dao.query(1));
System.out.println(dao.query(3));
System.out.println(dao.query(4));
System.out.println(dao.query(5));
}
public void testQueryAll(){
List<Account> list = dao.queryAll();
for (Account account : list) {
System.out.println(account);
}
}
public void testQueryPage(){
List<Account> list = dao.queryPage(20, 1);
for (Account account : list) {
System.out.println(account);
}
}
public void testQueryCount(){
System.out.println(dao.queryCount());
}
public void testRemit(){
dao.remit(2, 1, 200);
}
}