(SQLite)数据库
特点
- 轻量型的数据库
- 多用于嵌入式开发中
- 存储数据时不区分类型(除非是主键被定义为Integer这时只能存储64 位整数)
- 创建数据库可以不指定数据类型
CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20))
CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name)
- SQLite 与MySql 的不同之处
主键自增长:
1.SQLite 是autoincrement
2.MySql 是auto_increment
主键: SQLite 主键一般定义为_id,在做查询时要求主键列名必须是_id(本身不是_id,可以起别名),不然拿不到主键值。
创建数据库
public class PersonOpenHelper extends SQLiteOpenHelper {
/**
*
* @param context
* 上下文对象
* @param name
* 数据库名称
* @param factory
* 游标结果集工厂,如果需要使用则需要自定义结果集工 厂,null 值代表使用默认结果集工厂
* @param version
* 数据库版本号,必须大于等于1
*/
public PersonOpenHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
/**
* 数据库第一次被创建时调用该方法,这里面主要进行对数据库的初始化 操作
*/
public void onCreate(SQLiteDatabase db) {
// 数据库第一次被创建的时候执行如下sql 语句创建一个person 表
db.execSQL("create table person(id integer primary key autoincrement,name varchar(20), phone varchar(20), money integer(20),age integer(10));");
}
/**
* 数据库更新的时候调用该方法
*
* @param db
* 当前操作的数据库对象
* @param oldVersion
* 老版本号
* @param enwVersion
* 新版本号
*/
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 数据库的版本更新的时候执行
if (oldVersion == 1 && newVersion == 2) {
db.execSQL("alter table person add column balance integer");
}
}
}
创建一个PersonOpenHelperTest 类,用于测试上面的代码:
public class PersonOpenHelperTest extends AndroidTestCase {
public SQLiteDatabase getDataBase() {
PersonOpenHelper helper = new PersonOpenHelper(getContext(),
"person.db", null, 1);
SQLiteDatabase writableDatabase = helper.getWritableDatabase();
return writableDatabase;
}
}
执行完上面代码,通过DDMS查看
/data/data/包名/databases目录产生了两个文件
person.db:数据库文件
person.db-journal:临时的日志文件,用于事务回滚
数据库并不是初始化MyHelper 时创建
调用getWritableDatabase() 或者getReadableDatabase()时:
- 如果数据库不存在,创建数据库文件,执行onCreate()方法,并获取数据库对象
- 如果数据库存在,版本号没有发生改变,直接获取数据库对象
- 如果数据库存在,版本号提升,先执行onUpgrade()方法,再获取数据库对象
操作数据库(SQLiteDatabase)
- 和JDBC 访问数据库不同,操作SQLite 数据库无需加载驱动,不用获取连接,直接可以使用
- 数据库对象缓存,getWritableDatabase()方法最后会使用一个成员变量记住这个数据库对象,下次打开时判断是否重用
- 常用方法
1.执行SQL语句
sqliteDatabase.execSQL() :可以执行insert、delete、update 和CREATETABLE
sqliteDatabase.rawQuery() :用于执行select 查询语句。
2.SQLiteDatabase 封装了insert()、delete()、update()、query()四个方法 - 游标结果集Cursor
Cursor与JDBC中的ResultSet作用很相似
moveToNext(): 将游标从当前行移动到下一行,如果已经移过了结果集的最后一行,返回结果为false,否则为true。
//插入
public void insert(View v){
db.execSQL("insert into person (name)values(?)", new String[]{"lisi"});
Toast.makeText(this, "插入数据成功", 0).show();
}
//查询
public void query(View v){
Cursor cursor = db.rawQuery("select * from person",null);
//移动游标,返回值为true表示没有移动到数据集的最后(空),如果为false已经数据集的最后(没有数据了)
while(cursor.moveToNext()){
int id = cursor.getInt(0);
String name = cursor.getString(1);
System.out.println("id="+id+"; name="+name);
}
Toast.makeText(this, "查询数据成功", 0).show();
}
//更新
public void update(View v){
db.execSQL("update person set name='wangwu' where id=?", new Object[]{1});
Toast.makeText(this, "更新数据成功", 0).show();
}
//删除
public void delete(View v){
db.execSQL("delete from person where id=?", new Object[]{1});
Toast.makeText(this, "删除数据成功", 0).show();
}
- 事务
public void testTransaction() {
// 这里的最后一个参数(数据库版本号)设置为2,那么会执行PersonOpenHelper 类中的onUpgrade 方法
PersonOpenHelper helper = new PersonOpenHelper(getContext(), "person",
null, 2);
SQLiteDatabase database = helper.getWritableDatabase();
try {
// 开启事务
database.beginTransaction();
database.execSQL(
"update person set balance = balance-100 wherename=?",
new String[] { "lisi" });
// 当把int a=1/0;放开的时候,发现抛出异常,那么事务就会回滚,上面的扣除lsii 的100 元钱不会被真正执行
// 如果把int a = 1/0;注释掉才发现事务成功了,lisi 的钱被扣除了100 元,同时zhangsan 的钱也多了100元
int a = 1 / 0;
database.execSQL(
"update person set balance = balance+100 where name=?",
new String[] { "zhangsan" });
// 设置事务成功,也就是只有当代码执行到此行,才代表事务已经成功
database.setTransactionSuccessful();
} finally {
// 提交事务,如果setTransactionSuccessful()方法已经执行,则beginTransaction()后的语句执行成功
// 否则,事务回滚到开启事务前的状态
database.endTransaction();
}
}
TIPS:
在批量修改数据的时候,
由于事务是在进行事务提交时将要执行的SQL 操作一次性打开数据库连接执行,
其执行速度比逐条执行SQL 语句的速度快了很多倍。
因此当我们开发中遇到对数据库的批量操作那么,使用事务是提高效率的重要原则。
查看数据库
1.通过SQLite Expert 工具
在DDMS 视图中打开/data/data/包名/中,导出数据库文件
然后打开SQLite Expert 软件,将person 数据拖拽到如下图的左侧区域即可
2.通过Android sqlite3 工具
ListView控件
ListView 是我们Android 中最重要的控件之一,是用于对数据进行列表展示的件。
- 屏幕上可以展示几个控件, ListView 就初始化几个,节省内存,防止内存溢出。
- 通过使用convertView 对创建的视图对象进行复用ListView 始终保持创建的对象个数为: 屏幕显示的条目的个数+ 1。
- ListView 自带ScrollView 的功能,可以实现界面滚动。
- ListView 控件的设计遵循MVC 设计模式:
mode 数据模型(数据) :要被显示到ListView 上的数据集合
view 视图(展示数据) : ListView
controller 控制层(把数据展示到空间上) : 适配器Adapter
使用
使用listview显示数据列表的步骤:
1、在布局文件中添加一个listview控件
2、在代码中找到这个listview控件
3、创建一个数据适配器为listview填充数据
ps:布局文件的名字必须全部都是小写字母!
1. 创建ListView 展示样式布局文件,文件名为listview_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/ic_launcher" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="用户名" />
<TextView
android:id="@+id/tv_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="年龄" />
<TextView
android:id="@+id/tv_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
使用并修改该工程默认的Activity 类,MainActivity。该类的主要业务功能有:
(1)调用Dao 获取数据库的全部数据
(2)获取ListView 控件的实例
(3)自定义适配器,继承BaseAdapter,重写getCount 以及getView 方法
int getCount():用于获取要展示的数据的总条数,即ListView 的总长度view getView(int position,View convertView,ViewGroup parent)
参数:
第一个:position:当前要显示的项在ListView 的索引
第二个:convertView:就是被拖出去的View 对象,getView 的返回值,可以利用这个对象使得拖出去即将销毁的条目重用,即缓存对象。ListView 中创建对象的个数=屏幕显示的条目数+1,当滑动屏幕时,被隐藏的项会作为缓存对象,作为getView的参数传递进来。只需修改此缓存对象的内容,直接使用即可,而不需要再重新new一个新的对象出来,节省了内存,防止内存溢出
View view =convertView==null?View.inflate(MainActivity.this,R.layout.item,null):convertView;
public class MainActivity extends Activity {
private ListView lv;
private List<Person> persons;
private PersonDao dao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.lv);
dao = new PersonDao(this);
persons = dao.queryAll();
lv.setAdapter(new MyAdapter());
}
private class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return persons.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView != null) {
view = convertView;
} else {
view = View.inflate(MainActivity.this, R.layout.listview_item,
null);
}
TextView tv_username = (TextView) view
.findViewById(R.id.tv_username);
TextView tv_age = (TextView) view.findViewById(R.id.tv_age);
TextView tv_phone = (TextView) view.findViewById(R.id.tv_phone);
Person person = persons.get(position);
tv_age.setText("年龄:" + person.getAge() + "");
tv_phone.setText("电话:" + person.getPhone());
tv_username.setText("姓名:" + person.getName());
return view;
}
}
}
Listview优化
1.复用旧的convertView
2.利用ViewHolder 减少findviewByid 的时间
3.如果只涉及到一个Item 的某一个控件的更改, 不应该去刷新整个ListView(notifyDataSetChanged())而是给这个控件使用setTag(Position)的方式设置不同的tag,然后使用ListView.findViewByTag,找到这个对应的控件进行更改(什么时候需要使用notifyDataSetChanged,当有一个条目添加或者删除,这个时候就必须刷新)