使用嵌入式关系型SQLite 数据库存储数据:
我们知道,在Android 中为数据存储提供了多种的方式,分别有:文件、SharedPreferences( 参数) 、SQLite 数据库,Content provider( 内容提供者 ) 、网络。今天我想先简单说明一下SQLite 数据库。
在Android 平台上,集成了一个嵌入式关系型数据库—SQLite , SQLite3 支持 NULL 、 INTEGER 、 REAL ( 浮点数字 ) 、 TEXT( 字符串文本 ) 和 BLOB( 二进制对象 ) 数据类型 , 虽然它支持的类型只有五种 , 但实际上 sqlite3 也接受 varchar(n) 、 char(n) 、 decimal(p,s) 等数据类型 , 只不过在运算或保存时会转成对应的五种数据类型。 SQLite最大的特点是你可以把各种类型的数据保存到任何字段中,而不用关心字段声明的数据类型是什么(即字段是无数据类型的) 。例如:可以在Integer类型的字段中存放字符串,或者在布尔型字段中存放浮点数,或者在字符型字段中存放日期型值。 但有一种情况例外:定义为 INTEGER PRIMARY KEY 的字段只能存储64位整数, 当向这种字段保存除整数以外的数据时,将会产生错误 。 SQLite还有另一个特点,如果说你把这个字段声明为文本类型的,那么这个文本类型是没有长度的限制的 ,就是说如果你声明字段的长度为10个字符,实际上你可以存20个字符长度也是可以的。
SQLite可以解析大部分标准SQL语句,如:
查询语句: select * from 表名 where 条件子句 group by 分组字句 having ... order by 排序子句;
分页与MySQL 类似, 下面SQL语句获取5条记录,跳过前面3条记录
select * from Person limit 5 offset 3; 或者 select * from Person limit 3,5;
插入语句 : insert into 表名 ( 字段列表 ) values( 值列表 ) 。 如 : insert into person(name, age) values(‘iflytek’,10);
更新语句 : update 表名 set 字段名 = 值 where 条件子句。 如 :update person set name=‘iflytek' where id=11;
删除语句 : delete from 表名 where 条件子句。 如 :delete from person where id=10;
使用SQLiteOpenHelper 对数据库进行版本管理:
我们都知道, 以前在 J2EE 中操作数据库首先需要加载驱动 , 但是在 SQLite 数据库操作中第一步则不需要加载驱动 , 因为 Android 它集成进了 SQLite 数据库 , 那么作为 Android 这个系统的本身它是知道目前要使用的数据库是什么数据库。所以他的内部已经自动帮我们装载驱动,而不用我们手动装载了。所以这样我们要做的就是打开数据库和执行SQL语句就可以了。
需要做的:
1、创建数据库
2、编写代码完成CRUD操作
那么我们如何创建数据库呢?首先可以考虑到我们的应用程序具备自动创建数据库的能力。也就是说只要第一次使用我们的软件,它就会把软件中需要使用到的数据库给创建出来。这里我们就需要SQLiteOpenHelper这个类。
我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用软件时创建出应用使用到的数据库表结构及添加一些初始化记录,另外在软件升级的时候,也需要对数据表结构进行更新。那么,我们如何才能实现在用户初次使用或升级软件时自动在用户的手机上创建出应用需要的数据库表呢?总不能让我们在每个需要安装此软件的手机上通过手工方式创建数据库表吧?因为这种需求是每个数据库应用都要面临的,所以在Android系统,为我们提供了一个名为SQLiteOpenHelper的抽象类,必须继承它才能使用,它是通过对数据库版本进行管理来实现前面提出的需求。
为了实现对数据库版本进行管理,SQLiteOpenHelper类提供了两个重要的方法,分别是onCreate(SQLiteDatabase db)和onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion),前者用于初次使用软件时生成数据库表,后者用于升级软件时更新数据库表结构。当调用SQLiteOpenHelper的getWritableDatabase()或者getReadableDatabase()方法获取用于操作数据库的SQLiteDatabase实例的时候,如果数据库不存在,Android系统会自动生成一个数据库,接着调用onCreate()方法,onCreate()方法在初次生成数据库时才会被调用,在onCreate()方法里 可以生成数据库表结构及添加一些应用使用到的初始化数据 。onUpgrade()方法 在数据库的版本发生变化时会被调用,一般在软件升级时才需改变版本号 ,而数据库的版本是由程序员控制的,假设数据库现在的版本是1,由于业务的变更,修改了数据库表结构,这时候就需要升级软件,升级软件时希望更新用户手机里的数据库表结构,为了实现这一目的,可以把原来的数据库版本设置为2(这里设置大于1的都可以),并且在onUpgrade()方法里面实现表结构的更新。当软件的版本升级次数比较多,这时在onUpgrade()方法里面可以根据原版号和目标版本号进行判断,然后作出相应的表结构及数据更新。
DBOpenHelper.java:
package com.iflytek.service; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; /** * @author xudongwang 2011-8-22 * * Email:[email protected] * */ public class DBOpenHelper extends SQLiteOpenHelper { /** * 数据库名称 */ private static final String DATABASENAME = "iflytek.db"; /** * 数据库版本 */ private static final int VERSION = 2; /** * * @param context * :上下文对象 * @param name * :数据库名称,创建出来的数据库是以文件存放的,类似于access,其后缀没有要求,但建议.db,不要后缀也可以 * @param CursorFactory * factory:通过他来访问结果集中的数据,指定在执行查询时获得一个游标实例的工厂类,设置为null, * 代表使用系统默认的工厂类 * @param version * :数据库版本号,不指定的话默认为0,一般最好指定一下 */ public DBOpenHelper(Context context) { super(context, DATABASENAME, null, VERSION); } /** * 数据库第一被创建出来时被调用,所以说这个方法只会执行一次 * * @param db,操作数据库,实现CRUD操作,包括创建表的操作 */ @Override public void onCreate(SQLiteDatabase db) { String sql = "CREATE TABLE person (personid integer primary key autoincrement, name varchar(20),amount integer)"; db.execSQL(sql);// 执行sql语句 } /** * 该方法只有在你的软件升级的时候,如果修改到了数据库表的结构,那么这个方法就会被使用到, * 也就是说数据库的版本被改变(一般在软件升级的时候,会出现数据库办法的改变)了才会调用, 如果数据库的版本没有改变,则永远 * 不会被调用。版本号不一样时,调用 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS person"); db.execSQL("CREATE TABLE person (personid integer primary key autoincrement, name varchar(20) ,amount integer)"); } }
上面 onUpgrade() 方法在数据库版本每次发生变化时都会把用户手机上的数据库表删除 , 然后再重新创建。一般在实际项目中是不能这样做的,正确的做法是在更新数据库表结构时,还要考虑用户存放于数据库中的数据不会丢失。
测试 PersonServiceTest.java :
package com.iflytek.test; import com.iflytek.service.DBOpenHelper; import android.test.AndroidTestCase; /** * @author xudongwang 2011-8-22 * * Email:[email protected] */ public class PersonServiceTest extends AndroidTestCase { public void testCreateDB() throws Exception{ DBOpenHelper dbOpenHelper = new DBOpenHelper(this.getContext()); dbOpenHelper.getWritableDatabase();//第一次调用该方法就会创建数据库 } }
然后我们到 File Explorer 里的 data--- 》 data--- 》 com.iflytek.db--- 》 databases--- 》 iflytek.db
然后我们使用SQLiteManager 管理工具打开:
下面我们数据库的创建工作就算完成了,接下来就是对数据库进行CRUD 操作。
第一种方式完成CRUD 操作(推荐):
首先我们创建一个实体类
Persion.java :
package com.iflytek.domain; /** * @author xudongwang 2011-8-22 * * Email:[email protected] */ public class Person { /** * 编号 */ private int id; /** * 姓名 */ private String name; /** * 存款 */ private int amount; public Person() { } public Person(int id, String name) { this.id = id; this.name = name; } public Person(int id, String name, int amount) { this.id = id; this.name = name; this.amount = amount; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person [amount=" + amount + ", id=" + id + ", name=" + name + "]"; } }
PersonService.java :
package com.iflytek.service; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import com.iflytek.domain.Person; /** * @author xudongwang 2011-8-22 * * Email:[email protected] * * 业务层,这里没有使用面向接口编程 */ public class PersonService { private DBOpenHelper dbOpenHelper; public PersonService(Context context) { dbOpenHelper = new DBOpenHelper(context); } public void payment() { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); // 默认情况下,事务会回滚 db.beginTransaction();// 开启事务 try { db.execSQL("update person set amount =amount-10 where personid=?", new Object[] { 1 }); db.execSQL("update person set amount =amount+10 where personid=?", new Object[] { 2 }); db.setTransactionSuccessful();// 设置事务标志为成功,当结束事务时就会提交事务 } catch (SQLException e) { e.printStackTrace(); } finally { db.endTransaction();// 结束事务,由事务的标志决定是提交事务,还是回滚事务 } } /** * 添加Person * * @param person * Person对象 */ public void save(Person person) { // 如果要对数据库进行更改的,那么就建议使用getWritableDatabase()方法,得到用于操作数据库的实例, // 因为通过此方法得的到这个数据库实例,它是以读和写的方式打开数据库的 // 如果只对数据进行读取,建议使用getReadableDatabase()方法, 但调用此方法并不是以只读方式打开数据库,查看源码 // 得知此方法内部调用的还是getWritableDatabase方法,但是getWritableDatabase此方法执行出现异常时,会捕获异常 // 然后再以只读的方式打开数据库 // 当出现数据库磁盘空间满了,这时就不能继续向里面插入数据库,数据库就只能读而不能写,getWritableDatabase() 方法就会出错 SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); db.execSQL("insert into person (name,amount) values (?,?)", new Object[] { person.getName(), person.getAmount() }); // 这里,因为这个数据库是私有的,打开数据库后SQLiteDatabase类的内部就会一直维持连接(如果两次调用getWritableDatabase方法, // 则返回的还是同一个数据库实例,因为它的内部有缓存功能还会维持这个数据库的连接对象的,除非dbOpenHelper这个实例被销毁,但是如果调用 // getReadableDatabase,如果磁盘满了,则不是同一个实例,而如果磁盘没有满,则是同一个实例),这样就不用再打开数据库了, // 为提高性能有很多帮助,但是如果这个数据库被多个应用访问则需要关闭数据库 db.close(); // 而在J2EE中所用的数据库是大型数据库,而这种数据库不是单用户的,很多应用多可以连接到这个数据库上去。 } /** * 更新Person * * @param person * Person对象 */ public void update(Person person) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); db.execSQL("update person set name=? where personid=?", new Object[] { person.getName(), person.getId() }); } /** * 根据Id删除Person * * @param id * Person Id */ public void delete(int id) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); db.execSQL("delete from person where personid=?", new Object[] { id }); } /** * 根据Id查找Person * * @param id * Person Id * @return Person对象 */ public Person find(int id) { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("select * from person where personid=?", new String[] { id + "" });// 得到游标 if (cursor.moveToFirst()) { int personid = cursor.getInt(cursor.getColumnIndex("personid")); String name = cursor.getString(cursor.getColumnIndex("name")); int amount = cursor.getInt(cursor.getColumnIndex("amount")); return new Person(personid, name, amount); } return null; } /** * 分页 * * @param offset * 跳过前面offset条记录 * @param maxResult * 每页显示记录 * @return 分页后的Person集合 */ public List<Person> getPersonByPage(int offset, int maxResult) { List<Person> persons = new ArrayList<Person>(); SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("select * from person limit ?,?", new String[] { offset + "", maxResult + "" });// 得到游标 while (cursor.moveToNext()) { int personid = cursor.getInt(cursor.getColumnIndex("personid")); String name = cursor.getString(cursor.getColumnIndex("name")); int amount = cursor.getInt(cursor.getColumnIndex("amount")); Person person = new Person(personid, name, amount); persons.add(person); } cursor.close(); return persons; } public Cursor getCursorPersonByPage(int offset, int maxResult) { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); return db.rawQuery( "select personid as _id,name,amount from person limit ?,?", new String[] { offset + "", maxResult + "" });// 得到游标 } /** * 获取数据库表中总数据量 * * @return */ public Long getAllCount() { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("select count(*) from person", null); cursor.moveToFirst(); return cursor.getLong(0); } }
说明:
1、 注意在Android开发中,不是一味的追求面向接口编程,或者尽可能的解耦,因为手机中的内存很宝贵,所以说有时候在 可读性的前提下要尽可能的少写类, 因为类越多,虚拟机里越消耗性能,所以在android开发中会发现很多时候的内部类使用的很多。
2、 getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。但getWritableDatabase() 方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,如果使用的是getWritableDatabase() 方法就会出错。getReadableDatabase()方法先以读写方式打开数据库,如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。
查看getReadableDatabase方法的源码:
public synchronized SQLiteDatabase getReadableDatabase() { if (mDatabase != null && mDatabase.isOpen()) { return mDatabase; // The database is already open for business } if (mIsInitializing) { throw new IllegalStateException("getReadableDatabase called recursively"); } try { 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; return mDatabase; } finally { mIsInitializing = false; if (db != null && db != mDatabase) db.close(); } }
测试PersonServiceTest.java:
package com.iflytek.test; import java.util.List; import android.test.AndroidTestCase; import android.util.Log; import com.iflytek.domain.Person; import com.iflytek.service.DBOpenHelper; import com.iflytek.service.PersonService; /** * @author xudongwang 2011-8-22 * * Email:[email protected] */ public class PersonServiceTest extends AndroidTestCase { private static final String TAG = "PersonServiceTest"; /** * 测试创建数据库 * * @throws Exception */ public void testCreateDB() throws Exception { DBOpenHelper dbOpenHelper = new DBOpenHelper(this.getContext()); dbOpenHelper.getWritableDatabase();// 第一次调用该方法,就会创建数据库 } public void testPayment() { PersonService personService = new PersonService(this.getContext()); personService.payment(); } /** * 测试保存 * * @throws Exception */ public void testSave() throws Exception { PersonService personService = new PersonService(this.getContext()); Person person = new Person(); person.setName("xdwang"); person.setAmount(10000); personService.save(person); person.setName("xdwang2"); person.setAmount(50); personService.save(person); person.setName("xdwang3"); person.setAmount(45); personService.save(person); person.setName("xdwang4"); person.setAmount(80); personService.save(person); } /** * 测试更新 * * @throws Exception */ public void testUpdate() throws Exception { PersonService personService = new PersonService(this.getContext()); Person person = personService.find(1); person.setName("xdwangiflytek"); personService.update(person); } /** * 测试删除 * * @throws Exception */ public void testDelete() throws Exception { PersonService personService = new PersonService(this.getContext()); personService.delete(1); } /** * 测试查找 * * @throws Exception */ public void testFind() throws Exception { PersonService personService = new PersonService(this.getContext()); Person person = personService.find(1); Log.i(TAG, person.toString()); } /** * 测试分页 * * @throws Exception */ public void testGetScollData() throws Exception { PersonService personService = new PersonService(this.getContext()); List<Person> persons = personService.getPersonByPage(0, 3); for (Person person : persons) { Log.i(TAG, person.toString()); } } /** * 测试获取所有数量 * * @throws Exception */ public void testGetCount() throws Exception { PersonService personService = new PersonService(this.getContext()); Long count = personService.getAllCount(); Log.i(TAG, count + ""); } }
另一种方法完成CRUD 操作:
Android 提供了一个名为 SQLiteDatabase 的类 , 该类封装了一些操作数据库的 API , 使用该类可以完成对数据进行添加 (Create) 、查询 (Retrieve) 、更新 (Update) 和删除 (Delete) 操作 ( 这些操作简称为 CRUD ) 。对SQLiteDatabase的学习,我们应该重点掌握execSQL()和rawQuery()方法。 execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句; rawQuery()方法用于执行select语句。
execSQL()方法的使用例子:
SQLiteDatabase db = ....;
db.execSQL("insert into person(name, age) values('xdwang', 4)");
db.close();
执行上面SQL语句会往person表中添加进一条记录,在实际应用中, 语句中的“xdwang”这些参数值会由用户输入界面提供,如果把用户输入的内容原样组拼到上面的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[]{"xdwang", 4});
db.close();
execSQL(String sql, Object[] bindArgs) 方法的第一个参数为 SQL 语句 , 第二个参数为 SQL 语句中占位符参数的值 , 参数值在数组中的顺序要和占位符的位置对应。
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[]{"%dwan%", "4"});
Cursor 是结果集游标 , 用于对结果集进行随机访问 , 如果大家熟悉 jdbc , 其实 Cursor 与 JDBC 中的 ResultSet 作用很相似。使用moveToNext()方法可以将游标从当前行移动到下一行,如果已经移过了结果集的最后一行,返回结果为false,否则为true。另外Cursor 还有常用的moveToPrevious()方法(用于将游标从当前行移动到上一行,如果已经移过了结果集的第一行,返回值为false,否则为true )、moveToFirst()方法(用于将游标移动到结果集的第一行,如果结果集为空,返回值为false,否则为true )和moveToLast()方法(用于将游标移动到结果集的最后一行,如果结果集为空,返回值为false,否则为true ) 。
除了前面给大家介绍的execSQL()和rawQuery()方法, SQLiteDatabase还专门提供了对应于添加、删除、更新、查询的操作方法: insert()、delete()、update()和query() 。这些方法实际上是给那些不太了解SQL语法的菜鸟使用的,对于熟悉SQL语法的程序员而言,直接使用execSQL()和rawQuery()方法执行SQL语句就能完成数据的添加、删除、更新、查询操作。
Insert()方法用于添加数据 ,各个字段的数据使用ContentValues进行存放。 ContentValues类似于MAP,相对于MAP,它提供了存取数据对应的put(String key, Xxx value)和getAsXxx(String key)方法, key为字段名称,value为字段值,Xxx指的是各种常用的数据类型,如:String、Integer等。
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", "xdwang");
values.put("age", 4);
long rowid = db.insert(“person”, null, values); // 返回新添记录的行号 , 与主键 id 无关
不管第三个参数是否包含数据,执行Insert()方法必然会添加一条记录,如果第三个参数为空,会添加一条除主键之外其他字段值为Null的记录。Insert()方法内部实际上通过构造insert SQL语句完成数据的添加,Insert()方法的第二个参数用于指定空值字段的名称,相信大家对该参数会感到疑惑,该参数的作用是什么?是这样的:如果第三个参数values 为Null或者元素个数为0, 由于Insert()方法要求必须添加一条除了主键之外其它字段为Null值的记录,为了满足SQL语法的需要, insert语句必须给定一个字段名,如:insert into person( name ) values( NULL ),如果不给定字段名 , insert语句就成了这样: insert into person() values(),显然这不满足标准SQL的语法。对于字段名,建议使用主键之外的字段,如果使用了INTEGER类型的主键字段,执行类似insert into person(personid) values(NULL)的insert语句后,该主键字段值也不会为NULL。如果第三个参数values 不为Null并且元素的个数大于0 ,可以把第二个参数设置为null。
delete() 方法的使用 :
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete("person", "personid<?", new String[]{"2"});
db.close();
上面代码用于从 person 表中删除 personid 小于 2 的记录。
update() 方法的使用 :
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(“name”, “xdwang”); //key 为字段名 ,value 为值
db.update("person", values, "personid=?", new String[]{"1"});
db.close();
上面代码用于把 person 表中 personid 等于 1 的记录的 name 字段的值改为 “xdwang” 。
query()方法实际上是把select语句拆分成了若干个组成部分,然后作为方法的输入参数:
SQLiteDatabase db = databaseHelper.getWritableDatabase();
Cursor cursor = db.query("person", new String[]{"personid,name,age"}, "name like ?", new String[]{"%uwan%"}, null, null, "personid desc", "1,2");
while (cursor.moveToNext()) {
int personid = cursor.getInt(0); // 获取第一列的值 , 第一列的索引从 0 开始
String name = cursor.getString(1); // 获取第二列的值
int age = cursor.getInt(2); // 获取第三列的值
}
cursor.close();
db.close();
上面代码用于从 person 表中查找 name 字段含有 “ 传智 ” 的记录 , 匹配的记录按 personid 降序排序 , 对排序后的结果略过第一条记录 , 只获取 2 条记录。
query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit) 方法各参数的含义 :
table : 表名。相当于 select 语句 from 关键字后面的部分。如果是多表联合查询,可以用逗号将两个表名分开。
columns :要查询出来的列名。相当于select语句select关键字后面的部分。
selection :查询条件子句,相当于select语句where关键字后面的部分,在条件子句允许使用占位符“?”
selectionArgs :对应于selection语句中占位符的值,值在数组中的位置与占位符在语句中的位置必须一致,否则就会有异常。
groupBy :相当于select语句group by关键字后面的部分
having :相当于select语句having关键字后面的部分
orderBy :相当于select语句order by关键字后面的部分,如:personid desc, age asc;
limit :指定偏移量和获取的记录数,相当于select语句limit关键字后面的部分。
OtherPersonService.java :
package com.iflytek.service; import java.util.ArrayList; import java.util.List; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.iflytek.domain.Person; /** * @author xudongwang 2011-8-22 * * Email:[email protected] */ public class OtherPersonService { private DBOpenHelper dbOpenHelper; public OtherPersonService(Context context) { dbOpenHelper = new DBOpenHelper(context); } /** * 添加Person * * @param person * Person对象 */ public void save(Person person) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", person.getName()); // 其内部也是使用insert into方式实现,但是其性能没第一种高,因为这种有sql语句的构造过程,所以建议不要使用此方法 // 只有确定第三个参数不为null,并且元素的个数大于0,则第二个参数可以不传或传一个null // 只有在第三个参数为null,或者个数为0,那么第二参数才需要传,因为insert方法需要添加一条记录到数据库,所以 // 它需要我们指定一个空值字段,主要是为了满足insert 语句的语法 db.insert("person", null, values); } /** * 更新Person * * @param person * Person对象 */ public void update(Person person) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", person.getName()); // 第三个参数为条件部分,不包括where,第三个参数表示占位符的值 db.update("person", values, "personid=?", new String[] { person.getId() + "" }); } /** * 根据Id删除Person * * @param id * Person Id */ public void delete(int id) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); db.delete("person", "personid=?", new String[] { id + "" }); } /** * 根据Id查找Person * * @param id * Person Id * @return Person对象 */ public Person find(int id) { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); Cursor cursor = db.query("person", new String[] { "personid", "name" }, "personid=?", new String[] { id + "" }, null, null, null); if (cursor.moveToFirst()) { int personid = cursor.getInt(cursor.getColumnIndex("personid")); String name = cursor.getString(cursor.getColumnIndex("name")); return new Person(personid, name); } return null; } /** * 分页 * * @param offset * 跳过前面offset条记录 * @param maxResult * 每页显示记录 * @return */ public List<Person> getPersonByPage(int offset, int maxResult) { List<Person> persons = new ArrayList<Person>(); SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); // 第二个参数说明,如果要获取到表中所有字段,则可以使用null Cursor cursor = db.query("person", null, null, null, null, null, null, offset + "," + maxResult); while (cursor.moveToNext()) { int personid = cursor.getInt(cursor.getColumnIndex("personid")); String name = cursor.getString(cursor.getColumnIndex("name")); Person person = new Person(personid, name); persons.add(person); } cursor.close(); return persons; } /** * 获取数据库表中总数据量 * * @return */ public Long getAllCount() { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); Cursor cursor = db.query("person", new String[] { "count(*)" }, null, null, null, null, null); cursor.moveToFirst(); return cursor.getLong(0); } }
测试OtherPersonServiceTest.java:
package com.iflytek.test; import java.util.List; import android.test.AndroidTestCase; import android.util.Log; import com.iflytek.domain.Person; import com.iflytek.service.OtherPersonService; /** * @author xudongwang 2011-8-22 * * Email:[email protected] */ public class OtherPersonServiceTest extends AndroidTestCase { private static final String TAG = "OtherPersonServiceTest"; public void testSave() throws Exception { OtherPersonService otherPersonService = new OtherPersonService(this .getContext()); Person person = new Person(); person.setName("xdwang"); otherPersonService.save(person); person.setName("xdwang2"); otherPersonService.save(person); person.setName("xdwang3"); otherPersonService.save(person); person.setName("xdwang4"); otherPersonService.save(person); } public void testUpdate() throws Exception { OtherPersonService otherPersonService = new OtherPersonService(this .getContext()); Person person = otherPersonService.find(1); person.setName("xdwangiflytek"); otherPersonService.update(person); } public void testDelete() throws Exception { OtherPersonService otherPersonService = new OtherPersonService(this .getContext()); otherPersonService.delete(1); } public void testFind() throws Exception { OtherPersonService otherPersonService = new OtherPersonService(this .getContext()); Person person = otherPersonService.find(1); Log.i(TAG, person.toString()); } public void testGetScollData() throws Exception { OtherPersonService otherPersonService = new OtherPersonService(this .getContext()); List<Person> persons = otherPersonService.getPersonByPage(0, 3); for (Person person : persons) { Log.i(TAG, person.toString()); } } public void testGetCount() throws Exception { OtherPersonService otherPersonService = new OtherPersonService(this .getContext()); Long count = otherPersonService.getAllCount(); Log.i(TAG, count + ""); } }
使用事务操作SQLite 数据库:
使用 SQLiteDatabase 的 beginTransaction() 方法可以开启一个事务 , 程序执行到 endTransaction() 方法时会检查事务的标志是否为成功 , 如果程序执行到 endTransaction() 之前调用了 setTransactionSuccessful() 方法设置事务的标志为成功则提交事务 , 如果没有调用 setTransactionSuccessful() 方法则回滚事务。使用例子 银行转账 如下:
首先一个人帐号减少10元钱:update person set amount =amount-10 where personid=?
然后另一个人帐号曾加10元钱:update person set amount =amount+10 where personid=?
public void payment() { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); // 默认情况下,事务会回滚 db.beginTransaction();// 开启事务 try { db.execSQL("update person set amount =amount-10 where personid=?",new Object[] { 1 }); db.execSQL("update person set amount =amount+10 where personid=?",new Object[] { 2 }); db.setTransactionSuccessful();// 设置事务标志为成功,当结束事务时就会提交事务,如果没有设置,默认是回滚的 } catch (SQLException e) { e.printStackTrace(); } finally { db.endTransaction();// 结束事务,由事务的标志决定是提交事务,还是回滚事务 } }
大家可以发现和JDBC 里的事物使用有点不同
上面两条 SQL 语句在同一个事务中执行。
最后在Android 系统中显示数据:
首先我们肯定是列表中显示数据,在Android 中一般使用ListView 显示数据,每一行为Item ,一个Item 包含两个控件,一个是用来显示图片的控件ImageView ,一个是用来显示文字的控件TextView 。
1、定义Item显示界面
2、把数据绑定到ListView中,并且要ListView中的Item指定界面
在res--- 》layout 下新建item.xml 文件
Item.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:layout_width="80dip" android:layout_height="wrap_content" android:id="@+id/id" /> <TextView android:layout_width="100dip" android:layout_height="wrap_content" android:id="@+id/name" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/amount" /> </LinearLayout>
Main.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="添加记录" android:id="@+id/insertbutton" /> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:layout_width="80dip" android:layout_height="wrap_content" android:text="编号" /> <TextView android:layout_width="100dip" android:layout_height="wrap_content" android:text="姓名" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="存款" /> </LinearLayout> <ListView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/listview" /> </LinearLayout>
MainActivity.java:
package com.iflytek.db; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.Toast; import com.iflytek.service.PersonService; public class MainActivity extends Activity { /** Called when the activity is first created. */ private PersonService personService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.personService = new PersonService(this); ListView listView = (ListView) this.findViewById(R.id.listview); /** * //显示列表,将数据加载到列表上 方式一(使用SimpleAdapter适配器) List<Person> persons = personService.getPersonByPage(0, 5); List<HashMap<String, Object>> data = new ArrayList<HashMap<String, Object>>(); for (Person person : persons) { HashMap<String, Object> item = new HashMap<String, Object>(); item.put("id", person.getId()); item.put("name", person.getName()); item.put("amount", person.getAmount()); data.add(item); } SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.item, new String[] { "id", "name", "amount" }, new int[] { R.id.id, R.id.name, R.id.amount }); listView.setAdapter(adapter); // 点击ListView中的每一行时,触发一个事件 listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ListView lView = (ListView) parent; HashMap<String, Object> item = (HashMap<String, Object>) lView .getItemAtPosition(position); Toast.makeText(MainActivity.this, item.get("id").toString(), 1) .show(); } }); */ // 显示列表,将数据加载到列表上 方式二(使用SimpleCursorAdapter适配器) // 因为这里其底层需要_id,所以需要将sql语句写为: // "select personid as _id,name,amount from person limit ?,?",否则会报找不到_id的错误 // 因为SQLite数据库建议表的主键的名称为_id Cursor cursor = personService.getCursorPersonByPage(0, 5); // 如果业务层返回的数据是cursor,那么可以使用SimpleCursorAdapter适配器 SimpleCursorAdapter simpleCursorAdapter = new SimpleCursorAdapter(this, R.layout.item, cursor, new String[] { "_id", "name", "amount" }, new int[] { R.id.id, R.id.name, R.id.amount }); listView.setAdapter(simpleCursorAdapter); // 点击ListView中的每一行时,触发一个事件 listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ListView lView = (ListView) parent; Cursor data = (Cursor) lView.getItemAtPosition(position); int personid = data.getInt(data.getColumnIndex("_id")); Toast.makeText(MainActivity.this, personid + "", 1).show(); } }); // 添加按钮,实现添加数据 Button button = (Button) this.findViewById(R.id.insertbutton); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 通过此对象访问内容提供者 ContentResolver contentResolver = getContentResolver(); Uri insertUri = Uri .parse("content://com.iflytek.providers.personprovider/person"); ContentValues values = new ContentValues(); values.put("name", "xdwang"); values.put("amount", 888); contentResolver.insert(insertUri, values); Toast.makeText(MainActivity.this, "添加完成", 1).show(); } }); } }
PersonProvider.java:
package com.iflytek.db; import com.iflytek.service.DBOpenHelper; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; /** * @author xudongwang 2011-8-23 * * Email:[email protected] */ /** * 注意:这里将ContentProvider这个类放在应用的包下而不放在其他的包下(放在其他包下会出错),因为 * 对于组件应该放在应用的包下面或是应用的包的子包下,但是不能放在其他包下 ,(ContentProvider是android中提供的一种组件) */ public class PersonProvider extends ContentProvider { private DBOpenHelper dbOpenHelper; // NO_MATCH表示如果用户传进来的uri它跟对象里的所有uri都不匹配则返回NO_MATCH即-1 private static final UriMatcher MATCHER = new UriMatcher( UriMatcher.NO_MATCH); private static final int PERSON1 = 1; private static final int PERSON2 = 2; static { MATCHER.addURI("com.iflytek.providers.personprovider", "person", PERSON1); MATCHER.addURI("com.iflytek.providers.personprovider", "person/#", PERSON2); } // 删除person表中所有记录 /person // 删除person表中指定id的记录 /person/id @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; SQLiteDatabase database = dbOpenHelper.getWritableDatabase(); switch (MATCHER.match(uri)) { case PERSON1:// 删除person表中所有记录 /person count = database.delete("person", selection, selectionArgs);// 返回删除的记录数 return count; case PERSON2: // 删除person表中指定id的记录 /person/id long id = ContentUris.parseId(uri); String where = "personid=" + id; if (selection != null && !"".equals(selection)) { where = selection + " and " + where; } count = database.delete("person", where, selectionArgs); return count; default: throw new IllegalArgumentException("Uknow Uri" + uri); } } /** * 该方法是用来返回当前操作的数据的内容类型(MimeType) image/jpg */ @Override public String getType(Uri uri) { switch (MATCHER.match(uri)) { case PERSON1:// 注意这里的前缀是固定的 return "vnd.android.cursor.dir/person"; case PERSON2: return "vnd.android.cursor.item/person"; default: throw new IllegalArgumentException("Uknow Uri" + uri); } } @Override public Uri insert(Uri uri, ContentValues values) {// 要求/person SQLiteDatabase database = dbOpenHelper.getWritableDatabase(); switch (MATCHER.match(uri)) { case PERSON1: // 第二个参数是为了保证values为null时,sql不出错,即为insert into person (personid) // values(NULL),但是在SQLite中如何出现这样的语句,则因为personid为主键,所以personid仍不会为null, // 自增长为多少它就是多少,现在这里使用name // 返回新增加的这一行的id,如果主键是int,则rowid也就是主键的值,如果主键为string,那么返回的id为这条记录的行号 long rowid = database.insert("person", "name", values); Uri insertUri = ContentUris.withAppendedId(uri, rowid);// 得到代表新增记录的uri // content://com.iflytek.providers.personprovider/person/10 // 监听,发出一个通知(这个通知只能被监听这个uri的应用所捕获到),告诉外部的应用数据发生变化,这时需要在other项目的OtherActivity类中注册监听 this.getContext().getContentResolver() .notifyChange(insertUri, null); return insertUri; default: throw new IllegalArgumentException("Uknow Uri" + uri); } } /** * 内容提供者被实例化之后被调用,在整个生命周期里只会被调用一次,适合做数据的初始化工作 */ @Override public boolean onCreate() { this.dbOpenHelper = new DBOpenHelper(this.getContext()); return false; } // 查询person表中所有记录 /person // 查询person表中指定id的记录 /person/id @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor cursor; SQLiteDatabase database = dbOpenHelper.getReadableDatabase(); switch (MATCHER.match(uri)) { case PERSON1:// 查询person表中所有记录 /person cursor = database.query("person", projection, selection, selectionArgs, null, null, sortOrder); return cursor; case PERSON2: // 查询person表中指定id的记录 /person/id long id = ContentUris.parseId(uri); String where = "personid=" + id; if (selection != null && !"".equals(selection)) { where = selection + " and " + where; } cursor = database.query("person", projection, where, selectionArgs, null, null, sortOrder); return cursor; default: throw new IllegalArgumentException("Uknow Uri" + uri); } } // 更新person表中所有记录 /person // 更新person表中指定id的记录 /person/id @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; SQLiteDatabase database = dbOpenHelper.getWritableDatabase(); switch (MATCHER.match(uri)) { case PERSON1:// 更新person表中所有记录 /person count = database.update("person", values, selection, selectionArgs);// 返回更新过的记录数 return count; case PERSON2: // 更新person表中指定id的记录 /person/id long id = ContentUris.parseId(uri); String where = "personid=" + id; if (selection != null && !"".equals(selection)) { where = selection + " and " + where; } count = database.update("person", values, where, selectionArgs); return count; default: throw new IllegalArgumentException("Uknow Uri" + uri); } } }
最后运行效果: