android菜鸟学习笔记20----Android数据存储(四))Android数据库操作
Android内置了一个名为SQLite的关系型数据库,这是一款轻量型的数据库,操作十分简便。SQLite与别的数据库不同的是,它没有数据类型。可以保存任何类型的数据到你所想要保存的任何表的任何列中。但它又支持常见的类型比如: NULL, VARCHAR, TEXT, INTEGER, BLOB, CLOB...等。
唯一的例外是:integer primary key 此字段只能存储64位整数。
在JAVA项目中,要使用JDBC操作数据库需要加载数据库驱动,连接数据库等操作。Android简化了我们的数据库操作,无需由我们进行数据库驱动加载、连接等操作。
Android中进行数据库操作,需要涉及到如下几个类:
1)SQLiteOpenHelper:
在android.database.sqlite包下,这是一个抽象的帮助类,用于管理数据库的创建及版本维护操作。
我们在需要获取该类的对象时,需要自定义类继承自SQLiteOpenHelper类,并实现其中的onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int),可以选择性地实现 onOpen(SQLiteDatabase)。这个类会自动帮助我们在需要时打开数据库,在不存在时创建数据库,在必要时更新数据库。
常用方法:
一般,在自定义的子类中调用父类中第一个构造方法即可。
构造函数的参数说明:
context:应用的上下文对象
name:要操作的数据库的名称
factory:cursor工厂类对象,一般指定为null
version:数据库的版本号,必须大于等于1,由于控制数据库的升级。
注意到,只有onCreate()和onUpgrade()是抽象方法,所以自定义子类继承SQLiteOpenHelper时,一定要实现这两个方法。
其中:
onCreate()方法会在数据库不存在,第一次创建时调用,所以数据库中的初始化操作,如创建表等操作需要在该方法中完成。
onUpgrade()方法,新的版本号比原来有提升时,调用,用以完成数据库的升级操作,如新版本的app中,需要添加一张表,或者修改某个表,就需要在新版本的app创建SQLiteOpenHelper对象时,向其构造函数传入一个更大的版本号,这个版本号会被newVersion接收。
getReadaleDatabase()方法,创建或打开一个数据库,返回代表该数据库的只读的SQLiteDatabase对象
getWritableDatabase()方法,创建或打开一个数据库,返回代表该数据库的可读可写的SQLiteDatabase对象。
close()方法,用于关闭打开的数据库对象。
2)SQLiteDatabase:
通过SQLiteOpenHelper对象获取SQLiteDatabase对象后,便可以调用SQLiteDatabase类的相关方法进行数据库的增删改查操作了。
该类的常用方法有:
execSQL()方法用于执行SQL语句,可以用于执行不需要返回值的一些数据库操作。
rawQuery()方法一般被用于执行需要返回值的查询操作,查询的结果保存在Cursor对象中。
除了直接执行SQL语句进行数据库操作的方法之外,该类还封装几个更易用的增删改查方法。
insert()方法,用于向数据库中插入数据,参数说明:
table:指定要插入数据的表明
nullColumnHack:一般指定为null即可
注意:当values也为null,表示想向数据库中插入一条空记录时,该方法实际执行的SQL语句为insert into table(null) values(null);这样一条sql语句是没法执行的,所以,需要将nullColumnHack指定为任意一个可以为空的字段的字段名。如:nullColumnHack指定为”name”,values为null,此时SQL语句为insert into table(name) values(null);则可以正常执行了。
values:是一个ContentValues对象,用于存放要插入的各个字段名与值的对应关系。
delete()方法,用于删除表中的记录,参数说明:
table指定要操作的表名
whereClause指定where字句,可以包含占位符”?”,如“name = ?”,实际执行时,占位符会被第三个参数中对应索引的值替换
whereArgs:指定where字句中,占位符对应的值,如new String[]{“zhangsan”}
实际执行的语句就是delete from table where name = “zhangsan”
update()方法,用于执行表中记录的更新操作,参数说明:
table,whereClause,whereArgs与delete相同含义
values用于指定对应字段名要更新的值的映射关系。
query()记录查询方法,最简单的一个重载形式也有七个参数,都是select中的各个字句部分,如where子句部分,group by子句部分,having,order by等。参数说明:
table指定要查询的表名
colmns指定要查询的字段
selection:可以带占位符的where子句部分
slectionArgs:where子句占位符对应的值
groupBy:group by子句部分
having:having子句部分
orderBy:order by 子句部分
等等
isOpen()判断数据库是否已打开。
beginTransaction()用于开启事务操作
setTransactionSuccessful()方法用于标记事务操作成功
endTransaction()方法用于关闭事务,若事务标记成功,则提交事务操作,否则,则回滚失败的事务操作。
其他方法,在需要时,可以查询API帮助手册。
3)Cursor:
Cursor是一个接口,其实现类用于存放SQL语句查询的结果集。SQLiteDatabase的rawQuery()及query()方法均会返回该接口的对象,用以存储操作查询返回的结果集。
Cursor对象维持一个游标,默认指向结果集第一条记录之前的位置,可以通过下面几个方法,来移动游标,从而取得需要的记录。当游标已在第一条记录之前,调用moveToPreious(),或者已指向最后一条记录,调用moveToNext()方法时,均会返回false()标识移动失败。
getShort()、getString()、getInt()等getXXX()方法,用于根据当前记录的字段的索引获取字段的值。
getColumnName()用于根据索引获取字段名
getColumnNames()用于获取所有的字段名,其顺序与在Cursor中保存的顺序相同。
getCount()用于获取当前Cursor对象中保存的记录数。
getColumnCount()方法用于获取记录的字段总数
getColumnIndex()用于根据字段名获取其索引,不存在时返回-1。
close()方法,用于关闭当前Cursor对象。
4)ContentValues:
该类用于存放键值对的数据,在数据库的插入更新等操作中,可以使用字段名作为键,使用要插入或更新的字段值作为值。
常用的是put()方法,向ContentValues对象中存储数据。
如:put(“name”,”zhangsan”);等
以上便是数据库操作所涉及的几个主要的类和接口。
下面通过一个具体的代码,来学习使用这些个API:
第一步:
自定义类继承SQLiteOpenHelper类,实现onCreate()和onUpgrade()方法:
public class MySqliteHelper extends SQLiteOpenHelper { public static final String TAG = "MYSQLITEHELPER"; public static final String CREATE_STUDENT = "create table t_student (" + "id integer primary key, name varchar(20), gender varchar(10), age integer)"; public MySqliteHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onOpen(SQLiteDatabase db) { Log.i(TAG,"open db"); super.onOpen(db); } @Override public void onCreate(SQLiteDatabase db) { Log.i(TAG,"create db"); Log.i(TAG,"before excSql"); db.execSQL(CREATE_STUDENT); Log.i(TAG,"after excSql"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
第二步,定义实体类:
package cn.csc.sqlite.bean; public class Student { private int id; private String name; private String gender; private int age; 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; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Student() { super(); } public Student(int id, String name, String gender, int age) { super(); this.id = id; this.name = name; this.gender = gender; this.age = age; } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + "]"; } }
第三步,定义数据库操作类:
public class StudentDao { private SQLiteOpenHelper helper; private Context context; public StudentDao(Context context){ this.context = context; } public void insert(Student stu){ helper = new MySqliteHelper(context,"students.db", null, 1); Log.i("MYSQLITEHELPER","before get db"); SQLiteDatabase db = helper.getWritableDatabase(); Log.i("MYSQLITEHELPER","after get db"); db.execSQL("insert into t_student(name, gender, age) values(?,?,?)" , new Object[]{stu.getName(),stu.getGender(),stu.getAge()}); db.close(); } }
第四步,定义测试类,测试StudentDao的insert()方法:
public class TestDao extends AndroidTestCase { public void testInsert(){ StudentDao dao = new StudentDao(getContext()); dao.insert(new Student(0,"zhangsan", "male", 23)); } }
运行该测试方法,运行结果如下:
注意到,定义SQLiteOpenHelper对象,并不会创建数据库,只有调用getWritableDatabase()或者getReadableDatabase()才会调用onCreate()方法,onCreate()内部执行了execSQL()方法,但是并没有调用onOpen()方法。onCreate()方法执行完成后,才接着调用了onOpen()方法。
从File Explorer中可以看出,students.db存放在/data/data/应用包名/databases/下。
再次运行,由于数据库已经存在,onCreate()方法就没再被调用。
可以通过sqlite3命令来查看数据库内容:
sqlite的简单使用说明:
adb shell挂载虚拟机控制台
cd /data/data/cn.csc.sqlite/databases进入数据库文件所在目录
sqlite3 数据库文件名: 管理数据库文件名所指定的数据库,如sqlite3 students.db,然后光标变为sqlite>,表示进入sqlite操作模式。
.tables 查看数据库中所有的表
可以直接输入sql语句并执行
.exit 退出sqlite操作模式
.schema 查看查看库中所有表的DDL语句
.mode list|column|insert|line|tabs|tcl|csv 改变输出格式
若觉得命令行用着不习惯,可以从File Explorer中把数据库文件导出到电脑中,然后使用图形化工具,如sqlite expert来查看管理数据库。
下载地址:http://pan.baidu.com/s/1jG8G7QY
StudentDao中添加getAllStudents()方法:
public List<Student> getAllStudents(){ List<Student> list = new ArrayList<Student>(); helper = new MySqliteHelper(context,"students.db", null, 1); SQLiteDatabase db = helper.getWritableDatabase(); Cursor cursor = db.rawQuery("select id,name,gender,age from t_student", null); if(cursor == null){ return null; } while(cursor.moveToNext()){ Student stu = new Student(cursor.getInt(0),cursor.getString(1),cursor.getString(2),cursor.getInt(3)); Log.i("MYSQLITEHELPER",stu.toString()); list.add(stu); } return list; }
测试,输出:
上面用的都是直接写完整的SQL语句,下面添加几个方法,调用SQLiteDatabase封装的几个简单操作的API:
update()方法的使用
StudentDao类添加:修改指定id的学生的姓名:
public void updateNameById(int id, String newName){ helper = new MySqliteHelper(context,"students.db", null, 1); SQLiteDatabase db = helper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", newName); db.update("t_student", values, "id=?", new String[]{id+""}); }
测试代码:
public void testUpdate(){ StudentDao dao = new StudentDao(getContext()); dao.updateNameById(1, "dqrcsc"); }
运行前后:
delete()方法的使用:
StudentDao类添加:删除指定id的学生
public void deleteById(int id){ helper = new MySqliteHelper(context,"students.db", null, 1); SQLiteDatabase db = helper.getWritableDatabase(); db.delete("t_student", "id=?", new String[]{id+""}); }
测试代码:
public void testDelete(){ StudentDao dao = new StudentDao(getContext()); dao.deleteById(2); }
运行前后:
insert()方法的使用:
StudentDao类中添加如下方法:
public void addStudent(Student stu){ helper = new MySqliteHelper(context,"students.db", null, 1); SQLiteDatabase db = helper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", stu.getName()); values.put("gender", stu.getGender()); values.put("age", stu.getAge()); db.insert("t_student", null, values); }
测试代码:
public void testAddStudent(){ StudentDao dao = new StudentDao(getContext()); dao.addStudent(new Student(0,"csc","male",24)); }
运行前后:
query()方法的使用:
public Student getStudentById(int id){ Student stu = null; helper = new MySqliteHelper(context,"students.db", null, 1); SQLiteDatabase db = helper.getWritableDatabase(); Cursor cursor = db.query("t_student", new String[]{"id","name","gender","age"}, "id=?", new String[]{id+""}, null, null, null); if(cursor == null){ return null; } if(cursor.moveToFirst()){ stu = new Student(cursor.getInt(0),cursor.getString(1),cursor.getString(2),cursor.getInt(3)); } return stu; }
测试代码:
public void testGetStudentById(){ StudentDao dao = new StudentDao(getContext()); Student stu = dao.getStudentById(1); Log.i("MYSQLITEHELPER",stu.toString()); }
输出:
关于事务的操作:
被用烂来的例子,就是银行转账问题,一个账户转出,一个账户转入,两个操作要么同时成功,要么同时失败。
这里懒得再建一张表了,就转年龄吧,其实换汤不换药,原理完全一样。
假设年龄可以在学生之间转换,我要把自己的年龄转10岁给lisi这个同学,要保证这整个操作的原子性,就需要用到事务。
在StudentDao中添加转账年龄的方法:
public void transAge(){ helper = new MySqliteHelper(context,"students.db", null, 1); SQLiteDatabase db = helper.getWritableDatabase(); db.execSQL("update t_student set age = age - 10 where name = ?", new String[]{"dqrcsc"}); int i = 1/0;
db.execSQL("update t_student set age = age + 10 where name = ?", new String[]{"lisi"}); }
注意两条update语句之间有个1/0的操作,会导致程序异常终止,只有第一条被执行。
执行前后:
可以看到,我的年龄减去10岁,而lisi的年龄并没有加上10岁。
修改代码,改用事务处理:
public void transAge(){ helper = new MySqliteHelper(context,"students.db", null, 1); SQLiteDatabase db = helper.getWritableDatabase(); db.beginTransaction(); try{ db.execSQL("update t_student set age = age - 10 where name = ?", new String[]{"dqrcsc"}); int i = 1/0; db.execSQL("update t_student set age = age + 10 where name = ?", new String[]{"lisi"}); db.setTransactionSuccessful(); }finally{ db.endTransaction(); } }
这一次,我的年龄没有减少,lisi的年龄也没有增加,保证了一致性。
关于更新数据库版本的简单示例:
如,由于业务需要,更新了APP,新版本的APP在数据库中增加了一张教师表,现在修改onCreate()方法:
public static final String CREATE_TEACHER = "create table t_teacher(id integer primary key, name varchar(20))"; public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL(CREATE_STUDENT); db.execSQL(CREATE_TEACHER); }
运行,发现根本没有增加t_teacher这张表,因为数据库已存在,onCreate()方法不会被运行,这时,就需要用到onUpgrade()方法了:
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub if(oldVersion == 1 && newVersion == 2){ db.execSQL(CREATE_TEACHER); } }
在StudentDao中添加addTeacher()方法:
public void addTeacher(){ helper = new MySqliteHelper(context,"students.db", null, 2); SQLiteDatabase db = helper.getWritableDatabase(); db.execSQL("insert into t_teacher(name) values(?)",new String[]{"wanger"}); }
注意,这里指定的版本号为2。
测试运行结果:
数据库操作的学习,就简单学到这里。
android菜鸟学习笔记20----Android数据存储(四))Android数据库操作的更多相关文章
- android菜鸟学习笔记18----Android数据存储(二)SharedPreferences
数据存储的方式,有比直接文件读写更加简便的方式,那就是操作SharedPreferences. SharedPreferences一般用于存储用户的偏好设定,暂时不支持多进程操作. SharedPre ...
- android菜鸟学习笔记25----与服务器端交互(二)解析服务端返回的json数据及使用一个开源组件请求服务端数据
补充:关于PHP服务端可能出现的问题: 如果你刚好也像我一样,用php实现的服务端程序,采用的是apache服务器,那么虚拟主机的配置可能会影响到android应用的调试!! 在android应用中访 ...
- android菜鸟学习笔记23----ContentProvider(三)利用内置ContentProvider监听短信及查看联系人
要使用一个ContentProvider,必须要知道的是它所能匹配的Uri及其数据存储的表的结构. 首先想办法找到访问短信及联系人数据的ContentProvider能接受的Uri: 到github上 ...
- android菜鸟学习笔记21----ContentProvider(一)ContentProvider的简单使用
ContentProvider是Android四大组件之一,它用来封装数据,并通过ContentResolver接口将数据提供给其他应用.只有当需要在多个应用之间共享数据时才会用到ContentPro ...
- android菜鸟学习笔记16----Android项目打包安装过程(Run as Android Application)
右击项目名称,Run as Android Appication之后,Android项目打包安装过程: 1.打包生成.apk文件: 1)把源码中的.java文件编译生成.class文件 2)将所有的. ...
- android菜鸟学习笔记26----Android广播消息及BroadcastReceiver
1.广播类型: Android中的广播有两种类型:标准广播和有序广播.其中,标准广播是完全异步发送的广播,发出之后,几乎所有的广播接收者都会在同一时刻收到这条广播消息,因而,这种类型的广播消息是不可拦 ...
- android菜鸟学习笔记14----Android控件(三) ListView的简单使用
MVC模式: MVC的基本原理就是通过Controller连接View和Model.当View中所显示的数据发生变化时,会通知Controller,然后由Controller调用Model中的相关方法 ...
- android菜鸟学习笔记8----Activity(一)
Activity是android应用程序中重要的组件之一,常听到的android四大组件是Activity.Service.BroadcastReceiver和ContentProvider.它间接继 ...
- android菜鸟学习笔记7----android布局(二)
3.FrameLayout:帧布局 如同Flash或者photoshop中图层的概念,在上面的图层遮盖下面的图层,没被遮到的地方仍然显示出来. 右击res/layout,然后在弹出的菜单中选择new, ...
随机推荐
- CountDownLatch模拟高并发测试代码
直接上代码进行验证吧 /** * 通过countdownlatch的机制,来实现并发运行 * 模拟200个并发测试 * @author ll * @date 2018年4月18日 下午3:55:59 ...
- [转]js模块化编程之彻底弄懂CommonJS和AMD/CMD!
原文: https://www.cnblogs.com/chenguangliang/p/5856701.html ------------------------------------------ ...
- 转: 阿里跨平台移动开发工具Weex
对于移动开发者来说,Weex主要解决了频繁发版和多端研发两大痛点,同时解决了前端语言性能差和显示效果受限的问题.开发者可通过Weex官网申请内测.(http://alibaba.github.io/w ...
- 转: MyEclipse 10.0,9.0,8.0 下添加jadClipse反编译插件
MyEclipse 10.0,9.0,8.0 下添加jadClipse反编译插件 (2012-11-19 15:36:35) 转载▼ 标签: myeclipse jad 反编译 插件 it 分类: M ...
- 全双工音频播放器在c#中使用waveIn / waveOut api
http://www.codeproject.com/Articles/4889/A-full-duplex-audio-player-in-C-using-the-waveIn-w 一篇关于低级音频 ...
- 输出C语言中 变量的类型
使用gcc的警告信息间接知道变量的类型 #include <stdio.h> #include <stdlib.h> #include <stddef.h> #in ...
- Python 实现指定目录下 删除指定大小的文件
import os, sys from stat import * BIG_FILE_THRESHOLD = 6000L #1000000L dict1 = {} # dict2 = {} # def ...
- S1:适配器 Adapter
将一个类的接口转换为用户期望的另外一个接口.适配器使得原本由于接口不兼容而不能一起工作的类可以一起工作 UML: 一.类适配器: class A { public function methodA ...
- icvEvalCARTHaarClassifier
/* *icvEvalCARTHaarClassifier *作用:通过计算haar特征值,来分配非叶子节点直到出现叶子节点,最后返回输出值val. */ float icvEvalCARTHaar ...
- jquery插件:aotocomplete
aotocomplete.js http://blog.csdn.net/smeyou/article/details/7980273?_t_t_t=0.3565731019350138 $(func ...