一. SQLiteOpenHelper类介绍
1. SQLiteOpenHelper是什么?
- 用来创建、打开、更新和管理数据库的类,通过getReadableDatabase()或getWriteableDatabase()方法可以获得SQLiteDatabase实例,从而可以进行CRUD操作。
2. 方法介绍
- 构造方法
/**
* 创建帮助类以创建、打开和管理数据库。
* 这个方法总是很快返回,执行构造方法之后,在未执行 getWritableDatabase或getReadableDatabase方法前数据库不会创建
* @param 上下文
* @param 数据库名
* @param 游标工厂,传入空使用默认的游标工厂
* @param 数据库版本(起始值为1),当这个参数发生变化时,会执行onUpgrade或onDowngrade方法。
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
- onCreate方法(抽象方法)
/**
* 数据库第一次创建的时候会调用,在该方法内执行建表语句和初始化操作
* @param db 对应的数据库
*/
public abstract void onCreate(SQLiteDatabase db);
- onOpen方法
/**
* 打开数据库的时候调用,在更改数据库之前应该调用SQLiteDatabase的isReadOnly方法判断数据库是否可读写
* @param db The database.
*/
public void onOpen(SQLiteDatabase db) {}
- onUpgrade方法
/**
* 关系数据库的时候调用,在更新数据库之前应该调用此方法先删除旧的数据库模式,然后再创建新的数据库模式(会丢失旧的数据)
* 当你需要更改数据库信息的时候(如新建表,更改表信息),应该先将旧的数据库表进行重命名,然后创建新的表(名字为当前表名),然后将旧表中的数据复制到新表中去。
* you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
* you can use ALTER TABLE to rename the old table, then create the new table and then
* populate the new table with the contents of the old table.
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
*
* @param db The database.
* @param oldVersion 旧的版本号
* @param newVersion 新的版本号
*/
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
3. SQLiteOpenHelper的执行顺序
- 构造方法在每次执行实例化的时候都会执行
- onCreate只在第一次调用的时候执行,进行数据库的创建
- onOpen在每次打开数据库的时候都会执行
- onUpgrade在对数据库结构进行修改,在构造方法里传入version版本大于当前版本的时候进行执行
二. ContentValues类介绍
- ContentValues是用来在数据库增加和更新的时候用来存储要增加或者更新的值的HashMap的封装类
- 常用方法介绍
/**
* 将数据添加到ContentValues中,可以添加任何类型的数据
*
* @param key 要添加数据的键
* @param value 要添加的值
*/
public void put(String key, String value) {
mValues.put(key, value);
}
/**
* 将另一个ContentValues中的所有数据添加到当前ContentValues中
*
* @param 用来复制数据的另一个ContentValues
*/
public void putAll(ContentValues other) {
mValues.putAll(other.mValues);
}
/**
* 求当前ContentValues的大小
*
* @return 当前ContentValues的大小
*/
public int size() {
return mValues.size();
}
/**
* 在当前ContentValues中移除对应键的数据
*
* @param key 要移除的数据的键
*/
public void remove(String key) {
mValues.remove(key);
}
/**
* 移除当前ContentValues的所有数据
*/
public void clear() {
mValues.clear();
}
/**
* 获取到指定键对应的值
*
* @param key the value to get
* @return 给定键对应的值,或者null(当对应的键没有值的时候或者值为null的时候)
*/
public Object get(String key) {
return mValues.get(key);
}
三. 用法
- 新建Contract类,用于存储要创建的数据库的数据(如数据库名,表名,列名等),创建该类的目的是方便以后信息的修改,如果以后需要修改数据库信息则只需要在该类中进行修改就好了。该步骤可以省略,但是为了以后方便管理,最好还是写一下
- 新建SQLiteOpenHelper类,用于创建,升级数据库(管理数据库信息的类)
/**
* Created by NickelFox on 2017/5/10.
* helper的作用是创建数据库以及想要进行增删改查操作的时候获取数据库
*/
public class MyDatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
private final String TAG = getClass().getSimpleName();
private final String CREATE_TABLE_BOOK = "create table Book (" +
"id integer primary key autoincrement, " +
"author text, " +
"price real, " +
"pages integer, " +
"name text)";
private final String CREATE_TABLE_CATEGORY = "create table category (" +
"id integer primary key autoincrement, " +
"category_name text, " +
"category_code integer)";
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
Log.i(TAG, "MyDatabaseHelper: ");
}
/*数据库表的创建*/
@Override
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "onCreate: ");
db.execSQL(CREATE_TABLE_BOOK);
//db.execSQL(CREATE_TABLE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
/*数据库升级的时候调用,如新增表*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "onUpgrade: ");
db.execSQL(CREATE_TABLE_CATEGORY);
Log.i(TAG, "onUpgrade: old version is " + oldVersion + ", new version is " + newVersion);
}
/*每次getReadableDatabase的时候都会调用*/
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
Log.i(TAG, "onOpen: ");
}
}
- 调用SQLiteOpenHelper类的getReadableDataBase或getWritableDataBase方法得到SQLiteDatabse(如果是第一次调用会创建数据库,以后就不会创建而是获取到)
- 调用SQLiteDatabase的增删改查方法(安卓系统提供的)或者executeSQL方法(直接写sql语句)进行数据库操作,其中 增删改查对应的方法如下:
- 增
- insert方法
- 增
/**
* 在数据库中插入一行的简便方法(一点也不简便)
*
* @param table 要插入的表名
* @param nullColumnHack optional; may be <code>null</code>.
* SQL doesn't allow inserting a completely empty row without
* naming at least one column name. If your provided <code>values</code> is
* empty, no column names are known and an empty row can't be inserted.
* If not set to null, the <code>nullColumnHack</code> parameter
* provides the name of nullable column name to explicitly insert a NULL into
* in the case where your <code>values</code> is empty.
* @param values 要插入的值的ContentValues
* @return 返回插入的数据的行号,插入错误时返回-1
*/
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
}
}
- execSQL方法
- 删
- delete方法
/**
* 删除数据库中行的简单方法
*
* @param table 表名
* @param where条件,用的时占位符的形式
* @param whereArgs 占位符对应的参数,是一个String数组
* @return the number of rows affected if a whereClause is passed in, 0
* otherwise. To remove all rows and get a count pass "1" as the
* whereClause.
*/
public int delete(String table, String whereClause, String[] whereArgs) {
acquireReference();
try {
SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table +
(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
try {
return statement.executeUpdateDelete();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
- execSQL方法
- 改
- update方法
/**
*
* @param table 表名
* @param values 要更新的新值的ContentValues
* @param whereClause where条件,用的时占位符的形式
* @param whereArgs 占位符对应的值,是String数组
* @return 更新的行数
*/
public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
- execSQL方法
- 查
-
query方法(该方法参数有点多,上图)
rawQuery方法
-
/**
* Runs the provided SQL and returns a {@link Cursor} over the result set.
*
* @param sql the SQL query. The SQL string must not be ; terminated
* @param selectionArgs You may include ?s in where clause in the query,
* which will be replaced by the values from selectionArgs. The
* values will be bound as Strings.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
* when the query is executed.
* @return A {@link Cursor} object, which is positioned before the first entry. Note that
* {@link Cursor}s are not synchronized, see the documentation for more details.
*/
public Cursor rawQuery(String sql, String[] selectionArgs,
CancellationSignal cancellationSignal) {
return rawQueryWithFactory(null, sql, selectionArgs, null, cancellationSignal);
}
- query方法返回的是一个Cursor,和JDBC中ResultSet类似,只需要循环读取数据就好了
四. 源码
MainActivity
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper mMyDatabaseHelper;
private final String TAG = getClass().getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMyDatabaseHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
}
/*第一次实例化SQLiteOpenHelper,创建数据库*/
public void createDatabase(View view) {
mMyDatabaseHelper.getWritableDatabase();
}
/*通过sql语句添加数据*/
public void addDataBySql(View view) {
SQLiteDatabase db = mMyDatabaseHelper.getWritableDatabase();//获取到数据库
String insertData = "insert into Book(author,price,pages,name) values ('Dan Brown',16.96,454,'The Da Vinci Code')";//写sql语句
Log.i(TAG, "addData: "+insertData);
db.execSQL(insertData);//执行sql语句
}
/*通过insert方法插入数据*/
public void addDataByInsert(View view) {
SQLiteDatabase db = mMyDatabaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("author","Dan Brown");
values.put("price",19.95);
values.put("pages",510);
values.put("name","The lost Symbol");
db.insert("Book",null,values);
}
/*通过sql语句更新数据*/
public void updateBySql(View view) {
SQLiteDatabase db = mMyDatabaseHelper.getWritableDatabase();
String updateData = "update Book set price = 15.00 where name = 'The Da Vinci Code'";
Log.i(TAG, "updateBySql: "+updateData);
db.execSQL(updateData);
}
/*通过update方法更新数据*/
public void updateByUpdate(View view) {
SQLiteDatabase db = mMyDatabaseHelper.getWritableDatabase();
ContentValues updateValues = new ContentValues();
updateValues.put("price",10.99);
db.update("Book",updateValues,"name = ?",new String[]{"The Da Vinci Code"});
}
/*通过sql语句删除数据*/
public void deleteBySql(View view) {
SQLiteDatabase db = mMyDatabaseHelper.getWritableDatabase();
String deleteData = "delete from Book where pages > 500";
db.execSQL(deleteData);
}
/*通过delete方法删除数据*/
public void deleteByDelete(View view) {
SQLiteDatabase db = mMyDatabaseHelper.getWritableDatabase();
db.delete("Book","pages < ?",new String[]{"500"});
}
/*通过sql语句查询数据*/
public void selectBySql(View view) {
SQLiteDatabase db = mMyDatabaseHelper.getWritableDatabase();
String select = "select * from Book where pages = ?";
Cursor cursor = db.rawQuery(select,new String[]{"510"});
while(cursor.moveToNext()){
Log.i(TAG, "selectBySql: name: "+cursor.getString(cursor.getColumnIndex("name")));
Log.i(TAG, "selectBySql: author: "+cursor.getString(cursor.getColumnIndex("author")));
Log.i(TAG, "selectBySql: price: "+cursor.getFloat(cursor.getColumnIndex("price")));
Log.i(TAG, "selectBySql: pages: "+cursor.getString(cursor.getColumnIndex("pages")));
}
cursor.close();
}
/*通过query方法查询数据*/
public void selectByQuery(View view) {
SQLiteDatabase db = mMyDatabaseHelper.getWritableDatabase();
Cursor cursor = db.query("Book",null,null,null,null,null,null);
if(cursor.moveToFirst()){
do {
Log.i(TAG, "selectByQuery: name: "+cursor.getString(cursor.getColumnIndex("name")));
Log.i(TAG, "selectByQuery: author: "+cursor.getString(cursor.getColumnIndex("author")));
Log.i(TAG, "selectByQuery: price: "+cursor.getFloat(cursor.getColumnIndex("price")));
Log.i(TAG, "selectByQuery: pages: "+cursor.getString(cursor.getColumnIndex("pages")));
}while (cursor.moveToNext());
}
cursor.close();
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.foxnickel.databasedemo.MainActivity">
<Button
android:id="@+id/bt_select_by_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:onClick="selectByQuery"
android:text="select by query"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_select_by_sql"/>
<Button
android:id="@+id/bt_select_by_sql"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:onClick="selectBySql"
android:text="select by sql"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_delete_by_delete"/>
<Button
android:id="@+id/bt_delete_by_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:text="delete by delete"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_delete_by_sql"
android:onClick="deleteByDelete"/>
<Button
android:id="@+id/bt_delete_by_sql"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:text="delete by sql"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_update_by_update"
android:onClick="deleteBySql"/>
<Button
android:id="@+id/bt_update_by_update"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:text="update by update"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_update_by_sql"
android:onClick="updateByUpdate"/>
<Button
android:id="@+id/bt_create_database"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Create Database"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="createDatabase"/>
<Button
android:id="@+id/bt_add_data_by_sql"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Add Data By SQL"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/bt_create_database"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="addDataBySql"/>
<Button
android:id="@+id/bt_add_data_by_insert"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="add data by insert"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/bt_add_data_by_sql"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:onClick="addDataByInsert"/>
<Button
android:id="@+id/bt_update_by_sql"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:text="update by sql"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_add_data_by_insert"
android:onClick="updateBySql"/>
</android.support.constraint.ConstraintLayout>
helper类
/**
* Created by NickelFox on 2017/5/10.
* helper的作用是创建数据库以及想要进行增删改查操作的时候获取数据库
*/
public class MyDatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
private final String TAG = getClass().getSimpleName();
private final String CREATE_TABLE_BOOK = "create table Book (" +
"id integer primary key autoincrement, " +
"author text, " +
"price real, " +
"pages integer, " +
"name text)";
private final String CREATE_TABLE_CATEGORY = "create table category (" +
"id integer primary key autoincrement, " +
"category_name text, " +
"category_code integer)";
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
Log.i(TAG, "MyDatabaseHelper: ");
}
/*数据库表的创建*/
@Override
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "onCreate: ");
db.execSQL(CREATE_TABLE_BOOK);
//db.execSQL(CREATE_TABLE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
/*数据库升级的时候调用,如新增表*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "onUpgrade: ");
db.execSQL(CREATE_TABLE_CATEGORY);
Log.i(TAG, "onUpgrade: old version is " + oldVersion + ", new version is " + newVersion);
}
/*每次getReadableDatabase的时候都会调用*/
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
Log.i(TAG, "onOpen: ");
}
}
五. 后记:
- Helper类在任何地方都可以实例化,当保证version版本不变的时候数据库就不会变,当版本变得时候就会执行onUpgrade方法,数据库会得到更新。注意每次获取Helper对象的时候数据库版本一定不能小于当前版本。
- SQLite中可以使用pragma table_info(TABLE_NAME)这个命令来查看表的数据结构
- SQLite有关东西
- 一些技巧
- 用常量类(Contract类)封装数据库名称,列名等信息,方便以后更改
- 构造helper的时候多写一个构造方法,参数为Context,这样以后实例化helper的时候就不用传很多参数了
- 建立一个DBManager类,专门用来管理数据库,在Manager类中用单例模式构建Helper类对象,将增删改查方法都封装在这个类中,这样运行过程就只有一个全局的Helper对象,不会导致数据库版本等问题。
- 获取数据库的方法
/**
* @param path 打开/创建数据库的路径
* @param factory 游标工厂,默认传空
* @param flags 用来控制数据库的访问权限,可选值有OPEN_READWRITE(以读写的方式打开数据库),OPEN_READONLY(以只读的方式打开数据库),CREATE_IF_NECESSARY(必要的话进行数据库创建)
* @return the newly opened database
* @throws SQLiteException if the database cannot be opened
*/
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
return openDatabase(path, factory, flags, null);
}
- 数据库事务有关的问题
- 如果自己不手动开启事务,则数据库在每次读写的时候都会开启事务,当操作数据很多的时候就会降低运行速度,因此在进行大量操作的时候可以手动开启和关闭事务,运行速度会提高十倍甚至二十倍以上(亲测)
db.beginTransaction();//开启事务
/*执行操作*/
for(int i=0;i<1000;i++){
String sql = "insert into book(author,price,pages,name) values('The author',20.0,500,'The book')";
db.execSQL(sql);
}
db.setTransactionSuccessful();//设置事务执行成功
db.endTransaction();//结束事务
- 数据库分页
- 分页的目的:避免在加载很多数据的时候因为查询较慢造成界面卡顿和内存溢出
- 分页的思想:就是采用limit关键字,指定每次加载的数据条目,采用分页多次加载的形式。比如给ListView等控件加载数据的时候,只有当下拉的时候才加载后面的数据。(类似网络数据的分页加载,但是又有区别)
- Demo