(一)SQLite数据库
1、SQLite数据库简介
SQL数据库与我们之前学习的mysql数据库,oracle数据基本上都是相同的,其最大的特点是:我们可以将任意类型的数据保存到任意类型的字段中,而不用考虑数据库中字段的类型是什么(但是有一种情况时例外的,如果我们将某个字段声明为整形的主键,那么其只可以保存最大长度为64位的整数,否则会报错),例如我们可以在整型的字段中保存字符串.再假设,我们将字符串的最大长度设置为20,那么在oracle或者mysql中,我们存储的字符串是不可以超过20个字符的,但是对于sqlLite数据库,即使我们将字段的最大长度设置为20,我们也可以保存超过20个字符长度的字符串进去。
对于这些特点,我们通常将其称为无数据类型特点。
因为SQLite数据是无数据类型的,因此我们在创建数据库的时候,是可以省略字段后面的类型的。
也就是说以下语句中的红色部分是可以省略的:
CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))
但是在实际开发中,还是建议加上这些类型信息,因为标准的SQL语句是要求在字段后面声明类型的。同时也可以告诉其他程序员原先定义这个字段的时候是准备用来存储什么类型的数据的,并且数据的最大长度是多少。
SQLite可以支持绝大部分标准的sql语句,非标准语句如SQLServer的select top 10 from
Xxx 就是不是标准语句。当然对于sqlLite,其自身也有其语法特点,不遵循标准SQL。
2、创建数据库
在J2EE开发中,对于数据库的创建我们都是手工完成的,对于数据库表是否是手动创建,就看我们有没有使用ORM框架。
但是在安卓开发中,数据库是安装在用户的手机上,因此我们无法像J2EE那样,手工的创建数据库,因为我们无法得到用户的手机。
所以我们的应用程序要具有自动创建数据库功能。
自动创建数据库的原理:
当用户第一次使用软件的时候,就自动创建出数据库。
具体的操作如下,使用安卓给我们提供的一个工具类SQLiteOpenHelper实现数据库的自动创建。
这个类给我们提供了两个方法;
getWritableDatabase()和getReadableDatabase()方法
只要我们任意的调用其中一个方法,就会自动帮我们创建出一个数据库。
DBOpenHelper.java
package cn.itcast.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; /** * SQLite数据库帮助类 * @author Administrator *由于SQLiteOpenHelper是一个抽象类,因此我们不能直接获得其实例,需要写一个类继承 */ public class DBOpenHelper extends SQLiteOpenHelper { /** * SQLiteOpenHelper没有无参的构造方法,因此我们必须显示的调用父类的有参的构造方法 * @param context */ public DBOpenHelper(Context context) { //第一个参数无需介绍:代表的就是当前应用 //第二个参数name:指的是我们要生成数据库的名称,如果加后缀的话,建议以db结尾 //第三个参数CursorFactory factory:即游标工厂,即用来产生游标对象的工厂,游标的作用是迭代查询后的结果集,null表示使用默认的游标工厂 //第四个参数version:代表数据库文件的版本号,不能<1,建议填入一个>=1的值,当然这个值是任意的,最好是从1开始 super(context, "itcast.db", null, 1); } /** * 此方法在数据库第一次被调用的时候创建的,也就是说数据库文件不存在,当我们调用了getWritableDatabase()和getReadableDatabase()方法 * 时,就会创建数据库文件,并调用onCreate方法 */ @Override public void onCreate(SQLiteDatabase db) { // 在创建一个应用时,除了有数据库,还应该有数据库表, 因此在这个方法中,是比较适合生成数据库表的 //也就是说,当数据库一旦创建完成,就生成数据库表。 //SQLiteDatabase对象封装了对数据的所有操作,包括增删改查 //此处创建一个数据库表 db.execSQL("CREATE TABLE IF NOT EXISTS person (personid integer primary key autoincrement, name varchar(20), phone varchar(20)"); } /** * 此方法是在数据库的版本发生变更的时候调用的,假设数据库文件已经存在了,版本号为1 * 当软件版本需要升级,当我们将数据库文件的版本号改为2,这个方法就会被调用。 * * 此处假设版本号升级时,给数据库表中添加一个字段,可为空 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("ALTER TABLE person ADD phone VARCHAR(12) NULL"); } }
PersonServiceTest.java
package cn.itcast.test; import cn.itcast.db.DBOpenHelper; import android.test.AndroidTestCase; public class PersonServiceTest extends AndroidTestCase{ public void testCreateDB() throws Exception{ DBOpenHelper dbOpenHelper=new DBOpenHelper(this.getContext()); //自动创建数据库,当数据库被创建后,数据库文件默认保存在当前应用所在包的databases文件夹下 dbOpenHelper.getWritableDatabase(); } }
3、完成数据库的增删改查
Person.java
package cn.itcast.domain; public class Person { private Integer id; private String name; private String phone; public Person() { super(); } public Person(String name, String phone) { super(); this.name = name; this.phone = phone; } public Person(Integer id, String name, String phone) { super(); this.id = id; this.name = name; this.phone = phone; } 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 String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", phone=" + phone + "]"; } }
PersonService.java
public class PersonService { private DBOpenHelper dbOpenHelper = null; private PersonService(Context context) { super(); this.dbOpenHelper = new DBOpenHelper(context); } /** * 添加记录 * * @param person */ public void save(Person person) { // SQLite数据库的getWritableDatabase();或者getReadableDatabase();是具有缓存特点的,因此以下两句代码指向的是同一个数据库实例 // 要注意的是,这个缓存功能值针对于同一个SQLiteOpenHelper实例,不同的实例之间就没有缓存了 SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); // SQLiteDatabase db2=dbOpenHelper.getWritableDatabase(); // db.execSQL(sql, bindArgs); db.execSQL("INSERT INTO person(name,phone) VALUES(?,?)", new Object[] { person.getName(), person.getPhone() }); // 当在应用中只有一个地方使用数据库的话,数据库是可以不关闭的,不关闭的好处是可以提高性能,因为不需要频繁的打开数据库 // 对于性能的提升,也不是很大,所以到底关不关,由开发者自己决定 // db.close(); } /** * 删除记录 * * @param id */ public void delete(Integer id) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); db.execSQL("DELETE FROM person WHERE personid=?", new Object[] { id }); } /** * 修改记录 * * @param person */ public void update(Person person) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); db.execSQL( "UPDATE TABLE person SET name=?,phone=? WHERE personid=?", new Object[] { person.getName(), person.getPhone(), person.getId() }); } /** * 查找记录 * * @param id * @return */ public Person find(Integer id) { // getReadableDatabase();方法内部首先尝试使用getWritableDatabase();方法获得数据库实例 // 如果获取失败的话,则会只读方式获取数据库实例。 // 会失败的原因在于:数据库存储文件是由大小限制的,如果大小超过磁盘的存储容量,则不可以插入数据,就会获取失败 // 因此getReadableDatabase();与getWritableDatabase();的区别在于 // 如果数据库磁盘存储空间没有满,getReadableDatabase();与getWritableDatabase();获取的数据库实例都是可读可写的 // 如果数据库磁盘存储空间满了,getWritableDatabase();获取的数据库实例会报错 // 而getReadableDatabase();可以获取只读模式的数据库实例 // 建议:如果只需要读取数据,就使用getReadableDatabase(); SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM person WHERE personid=?", new String[] { String.valueOf(id) }); if (cursor.moveToFirst()) {// 游标移动到第一条记录,如果没有记录,则返回false,存在数据才会返回true // getXXX方法接受的是字段在结果集中的索引号 int personid = cursor.getInt(cursor.getColumnIndex("personid")); String name = cursor.getString(cursor.getColumnIndex("name")); String phone = cursor.getString(cursor.getColumnIndex("phone")); return new Person(personid, name, phone); } cursor.close(); return null; } /** * 获取分页数据 * * @param offset * --跳过前面多少条记录 * @param maxResult * --每页显示多少条记录 * @return */ public List<Person> getPageData(int offset, int maxResult) { List<Person> persons = new ArrayList<Person>(); SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); Cursor cursor = db.rawQuery( "SELECT * FROM person ORDER BY personid asc WHERE limit ?,?", new String[] { String.valueOf(offset), String.valueOf(maxResult) }); while (cursor.moveToNext()) { int personid = cursor.getInt(cursor.getColumnIndex("personid")); String name = cursor.getString(cursor.getColumnIndex("name")); String phone = cursor.getString(cursor.getColumnIndex("phone")); persons.add(new Person(personid, name, phone)); } cursor.close(); return persons; } /** * 获取记录总数 * * @return */ public Long getTotalRecordCount() { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM person", null); cursor.moveToFirst(); Long result = cursor.getLong(0); return result; } }
要注意的是,我们在创建数据库的时候指定了数据库的名字,但是在使用的时候并没有指定数据库文件的名称,其内部是如何执行的呢?
PersonServiceTest.java
package cn.itcast.test; import java.util.List; import android.test.AndroidTestCase; import android.util.Log; import cn.itcast.db.DBOpenHelper; import cn.itcast.domain.Person; import cn.itcast.service.PersonService; public class PersonServiceTest extends AndroidTestCase{ private static final String TAG="PersonServiceTest"; public void testCreateDB() throws Exception{ DBOpenHelper dbOpenHelper=new DBOpenHelper(this.getContext()); //自动创建数据库,当数据库被创建后,数据库文件默认保存在当前应用所在包的databases文件夹下 dbOpenHelper.getWritableDatabase(); } public void testSave(){ PersonService service=new PersonService(this.getContext()); for(int i=0;i<20;i++){ service.save(new Person("张三",i+"")); } } public void testDelete(){ PersonService service=new PersonService(this.getContext()); service.delete(1); } public void testUpdate(){ PersonService service=new PersonService(this.getContext()); Person person=service.find(1); person.setName("李四"); service.update(person); } public void testFind(){ PersonService service=new PersonService(this.getContext()); Person person=service.find(1); Log.i(TAG, person.toString()); } public void testGetPageData(){ PersonService service=new PersonService(this.getContext()); //获取第一页的数据 List<Person> persons=service.getPageData(0, 5); for (Person person : persons) { Log.i(TAG, person.toString()); } } public void testGetTotalRecordCount(){ PersonService service=new PersonService(this.getContext()); Long result=service.getTotalRecordCount(); Log.i(TAG, String.valueOf(result)); } }
要注意的是:在这段测试代码中,每个方法中都有
PersonService service=new PersonService(this.getContext());
但是这段代码是不可以被提取出来的
因为测试框架是通过反射技术来实例化测试类,而对于Context对象的注入,是在对象实例化完成之后才进行的。
而初始化一个类的实例,必须先初始化其成员变量。如果提取为成员变量的话,那么由于类还没有被实例化,Context对象也还没有注入,因此在成员变量中使用this.getContext()获得的实际上是null。
即使我们将这段代码放在构造方法中,也是不可以的,因为构造方法在执行的过程中,类还是没有实例化完成,而Context对象时类在实例化之后才注入的。
如果非要提取出来,可以覆盖父类的setUp方法,这个方法在每个方法执行之前都会调用。
4、在SQLite数据库中使用事务