SQLite是一個軟件庫,實現了自給自足的、無服務器的、零配置的、事務性的 SQL 數據庫引擎。詳細介紹參見https://www.runoob.com/sqlite/sqlite-intro.html
本系列的文章介紹如何在Android app開發中使用其自帶的數據庫SQLite,包括定義並建立數據庫,在本項目中對數據進行增刪改操作。
本文前提:
a、瞭解SQL數據庫基礎知識。
b、新建Android app項目TimeTable,開發環境:Android studio。
一、新建文件
在代碼目錄下新建一個文件夾data,用於存放數據庫操作相關的代碼。data目錄中新建三個文件:
TimeTablecontract.java —— 用於定義數據庫和表。
TimeTableDbHelper.java—— 繼承 SQLiteOpenHelper,用於創建和升級數據庫和表。
TimeTableProvider.java—— 繼承 ContentProvider,用於提供對數據庫和表的Uri形式的訪問方式。
二、上述三個新增文件的內容及介紹:
1、TimeTablecontract.java :
public class TimeTableContract {
// The "Content authority" is a name for the entire content provider, similar to the
// relationship between a domain name and its website. A convenient string to use for the
// content authority is the package name for the app, which is guaranteed to be unique on the
// device.
public static final String CONTENT_AUTHORITY = "com.xuzhi.jinshu.timetable";
// Use CONTENT_AUTHORITY to create the base of all URI's which apps will use to contact
// the content provider.
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String PATH_COURSE_INFO = CourseInfo.TABLE_NAME;
public static final String PATH_EXECUTING_SCHEDULE = ExecutingSchedule.TABLE_NAME;
public static final class CourseInfo implements BaseColumns {
static final public Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_COURSE_INFO).build();
public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_COURSE_INFO;
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_COURSE_INFO;
public static final String TABLE_NAME = "CourseInfo";
public static final String COLUMN_COURSE_NAME = "courseName";
public static final String COLUMN_TEACHER_NAME = "teacherName";
public static final String COLUMN_STUDENT_NAME = "studentName";
public static final String COLUMN_NUMBER_OF_CLASSES = "numberOfClasses";
public static final String COLUMN_START_DATE = "startDate";
public static final String COLUMN_COURSE_SCHEDULE = "courseSchedule";
public static final String COLUMN_PRICE = "price";
public static final String COLUMN_NOTE = "note";
public static final String COLUMN_IS_TODAY_START = "isTodayStart";
public static Uri buildCourseInfoUri(long id) {
return ContentUris.withAppendedId(CONTENT_URI, id);
}
public static Uri buildCourseInfoUriById(int id) {
Log.e("BY ID","ID = " + id);
return CONTENT_URI.buildUpon().appendPath(_ID).appendPath(Integer.toString(id)).build();
}
public static String getTheSecondPara(Uri uri) {
return uri.getPathSegments().get(2);
}
}
public static final class ExecutingSchedule implements BaseColumns {
private final String LOG_TAG = this.getClass().getSimpleName();
static final public Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_EXECUTING_SCHEDULE).build();
public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_EXECUTING_SCHEDULE;
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_EXECUTING_SCHEDULE;
public static final String TABLE_NAME = "ExecutingSchedule";
public static final String COLUMN_COURSE_ID = "courseId";/*COLUMN_ID in courseInfo: ExtendKey*/
public static final String COLUMN_SEQUENCE_ID = "sequenceId";
public static final String COLUMN_DATE = "date";
public static final String COLUMN_WEEKDAY = "weekday";
public static final String COLUMN_START_TIME = "start_time";//format hour:minute
public static final String COLUMN_END_TIME = "end_time";//format hour:minute
public static final String COLUMN_LEARN_STATUS = "learn_status";//"unLearned" or "unLearned"or "cancel" or "suspend"
public static final String COLUMN_LEAVE_STATUS = "leave_status";//"unLearned" or "unLearned"or "cancel" or "suspend"
public static final String COLUMN_CHANGE_STATUS = "change_status";//"unLearned" or "unLearned"or "cancel" or "suspend"
public static final String COLUMN_NOTE = "note";//leave_by_student leave_by_teacher change_by_legal_holiday
/*調課時保存調課前的數據*/
public static final String COLUMN_DATE_BEFORE_CHANGE = "date_before_change";
public static final String COLUMN_WEEKDAY_BEFORE_CHANGE = "weekday_before_change";
public static final String COLUMN_START_TIME_BEFORE_CHANGE = "start_time_before_change";//format hour:minute
public static final String COLUMN_END_TIME_BEFORE_CHANGE = "end_time_before_change";//format hour:minute
public static final String PATH_COURSE_ID = COLUMN_COURSE_ID;
public static final String PATH_ID = _ID;
public static final String PATH_SEQUENCE_ID = COLUMN_SEQUENCE_ID;
public static Uri buildExecutingScheduleUri(long id) {
return ContentUris.withAppendedId(CONTENT_URI, id);
}
public static Uri buildExecutingScheduleUriWithCourseId(long courseId) {
return CONTENT_URI.buildUpon().appendPath(PATH_COURSE_ID).appendPath(String.valueOf(courseId)).build();
}
public static Uri buildExecutingScheduleUriWithId(long id) {
return CONTENT_URI.buildUpon().appendPath(PATH_ID).appendPath(String.valueOf(id)).build();
}
public static String getTheSecondPara(Uri uri) {
return uri.getPathSegments().get(2);
}
public static String getTheThirdPara(Uri uri) {
return uri.getPathSegments().get(3);
}
}
}
各屬性定義說明見下表:
屬性 | 說明 |
---|---|
CONTENT_AUTHORITY | 本數據庫的唯一訪問標識 |
BASE_CONTENT_URI | 數據庫的Uri訪問地址 |
PATH_COURSE_INFO | 數據庫中表的相對訪問路徑 |
CourseInfo | 數據庫中表的定義 |
CourseInfo.CONTENT_URI | 表的Uri訪問地址 |
CourseInfo.TABLE_NAME | 表名稱 |
CourseInfo.COLUMN_xxx | 表中各列名稱定義 |
2、TimeTableDbHelper.java
public class TimeTableDbHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
static final public String DATABASE_NAME = "TimeTable.db";
private static TimeTableDbHelper instance;
/**
* 單例模式
* @param context 傳入上下文
* @return 返回TimeTableDbHelper 對象
*/
public static TimeTableDbHelper getInstance(Context context) {
if (null == instance) {
synchronized (TimeTableDbHelper.class) {
if (null == instance) {
instance = new TimeTableDbHelper(context);
}
}
}
return instance;
}
public TimeTableDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
// Create a table to hold CourseTable's info.
Log.v(LOG_TAG, "TimeTableDbHelper onCreate");
final String SQL_CREATE_COURSE_INFO_TABLE = "CREATE TABLE " + CourseInfo.TABLE_NAME + " (" +
CourseInfo._ID + " INTEGER PRIMARY KEY NOT NULL, " +
CourseInfo.COLUMN_COURSE_NAME + " TEXT NOT NULL, " +
CourseInfo.COLUMN_TEACHER_NAME + " TEXT NOT NULL, " +
CourseInfo.COLUMN_STUDENT_NAME + " TEXT NOT NULL, " +
CourseInfo.COLUMN_START_DATE + " TEXT NOT NULL, " +
CourseInfo.COLUMN_PRICE + " INTEGER NOT NULL, " +
CourseInfo.COLUMN_COURSE_SCHEDULE + " TEXT NOT NULL, " +
CourseInfo.COLUMN_NOTE + " TEXT NOT NULL, " +
CourseInfo.COLUMN_IS_TODAY_START + " TEXT NOT NULL, " +
CourseInfo.COLUMN_NUMBER_OF_CLASSES + " INTEGER NOT NULL);";
final String SQL_CREATE_EXECUTING_SCHEDULE_TABLE = "CREATE TABLE " + ExecutingSchedule.TABLE_NAME + " (" +
ExecutingSchedule._ID + " INTEGER PRIMARY KEY NOT NULL, " +
ExecutingSchedule.COLUMN_COURSE_ID + " INTEGER NOT NULL, " +
ExecutingSchedule.COLUMN_SEQUENCE_ID + " INTEGER NOT NULL, " +
ExecutingSchedule.COLUMN_LEARN_STATUS + " TEXT default('unLearned'), " +
ExecutingSchedule.COLUMN_LEAVE_STATUS + " TEXT default('no_leave'), " +
ExecutingSchedule.COLUMN_CHANGE_STATUS + " TEXT default('no_change'), " +
ExecutingSchedule.COLUMN_NOTE + " TEXT default(' '), " +
ExecutingSchedule.COLUMN_DATE + " TEXT NOT NULL, " +
ExecutingSchedule.COLUMN_WEEKDAY + " TEXT NOT NULL, " +
ExecutingSchedule.COLUMN_START_TIME + " TEXT NOT NULL, " +
ExecutingSchedule.COLUMN_END_TIME + " TEXT NOT NULL, " +
ExecutingSchedule.COLUMN_DATE_BEFORE_CHANGE + " TEXT default('null'), " +
ExecutingSchedule.COLUMN_WEEKDAY_BEFORE_CHANGE + " TEXT default('null'), " +
ExecutingSchedule.COLUMN_START_TIME_BEFORE_CHANGE + " TEXT default('null'), " +
ExecutingSchedule.COLUMN_END_TIME_BEFORE_CHANGE + " TEXT default('null'));";
sqLiteDatabase.execSQL(SQL_CREATE_COURSE_INFO_TABLE);
sqLiteDatabase.execSQL(SQL_CREATE_EXECUTING_SCHEDULE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
// Note that this only fires if you change the version number for your database.
// It does NOT depend on the version number for your application.
// If you want to update the schema without wiping data, commenting out the next 2 lines
// should be your top priority before modifying this method.
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + CourseInfo.TABLE_NAME);
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + ExecutingSchedule.TABLE_NAME);
Log.e(LOG_TAG,"onUpgrade");
onCreate(sqLiteDatabase);
}
}
SQLiteOpenHelper 是Android提供的一個類,用於SQLite數據庫的創建和版本管理。TimeTableDbHelper 作爲SQLiteOpenHelper 的繼承類,需要關注以下幾項。
內容 | 說明 |
---|---|
|
創建或獲取TimeTableDbHelper類。 |
|
當數據庫被創建時調用,在這裏實現表的創建和初始化。 |
|
當數據庫升級時調用,在這裏實現表的新增、刪除等操作。 |
|
數據庫版本,當數據庫需要升級時,需要更新此版本號。 |
|
數據庫名稱。 |
3、TimeTableProvider.java
public class TimeTableProvider extends ContentProvider {
private final String LOG_TAG = this.getClass().getSimpleName();
// The URI Matcher used by this content provider.
private static final UriMatcher sUriMatcher = buildUriMatcher();
private com.xuzhi.jinshu.timetable.data.TimeTableDbHelper mOpenHelper;
static final int TIME_TABLE_COURSE_INFO = 100;
static final int TIME_TABLE_COURSE_INFO_WITH_ID = 101;
static{
sTimeTableQueryBuilder = new SQLiteQueryBuilder();
}
static UriMatcher buildUriMatcher() {
// I know what you're thinking. Why create a UriMatcher when you can use regular
// expressions instead? Because you're not crazy, that's why.
// All paths added to the UriMatcher have a corresponding code to return when a match is
// found. The code passed into the constructor represents the code to return for the root
// URI. It's common to use NO_MATCH as the code for this case.
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = CONTENT_AUTHORITY;
// For each type of URI you want to add, create a corresponding code.
matcher.addURI(authority, PATH_COURSE_INFO, TIME_TABLE_COURSE_INFO);
matcher.addURI(authority, PATH_COURSE_INFO + "/" + TimeTableContract.CourseInfo._ID +"/*", TIME_TABLE_COURSE_INFO_WITH_ID);
return matcher;
}
@Override
public boolean onCreate() {
mOpenHelper = new TimeTableDbHelper(getContext());
return true;
}
@Override
public String getType(Uri uri) {
// Use the Uri Matcher to determine what kind of URI this is.
final int match = sUriMatcher.match(uri);
switch (match) {
case TIME_TABLE_COURSE_INFO:
return TimeTableContract.CourseInfo.CONTENT_TYPE;
case TIME_TABLE_COURSE_INFO_WITH_ID:
return TimeTableContract.CourseInfo.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
/***************************begin CourseInfo*******************************/
private Cursor getAllCourseInfo(
Uri uri, String[] projection, String sortOrder) {
Log.e(LOG_TAG, TimeTableContract.CourseInfo.TABLE_NAME);
sTimeTableQueryBuilder.setTables(TimeTableContract.CourseInfo.TABLE_NAME);
return sTimeTableQueryBuilder.query(mOpenHelper.getReadableDatabase(),
projection,
null,
null,
null,
null,
sortOrder);
}
/*********************end CourseInfo***********************************/
/***************************begin ExecutingSchedule*******************************/
//ExecutingSchedule.courseId = ?
private static final String sExecutingScheduleByCourseId =
TimeTableContract.ExecutingSchedule.TABLE_NAME +
"." + TimeTableContract.ExecutingSchedule.COLUMN_COURSE_ID + " = ? ";
private Cursor getExecutingScheduleWithCourseId(Uri uri, String[] projection, String sortOrder)
{
String courseId = com.xuzhi.jinshu.timetable.data.TimeTableContract.ExecutingSchedule.getTheSecondPara(uri);
Log.e(LOG_TAG, TimeTableContract.ExecutingSchedule.TABLE_NAME);
sTimeTableQueryBuilder.setTables(TimeTableContract.ExecutingSchedule.TABLE_NAME);
return sTimeTableQueryBuilder.query(mOpenHelper.getReadableDatabase(),
projection,
sExecutingScheduleByCourseId,
new String[]{courseId},
null,
null,
sortOrder);
}
//ExecutingSchedule._id = ? private static final String sExecutingScheduleByIdSelection = TimeTableContract.ExecutingSchedule.TABLE_NAME + "." + TimeTableContract.ExecutingSchedule._ID + " = ? ";
private int DeleteExecutingScheduleById(Uri uri)
{
String id = TimeTableContract.ExecutingSchedule.getTheSecondPara(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
return db.delete( TimeTableContract.ExecutingSchedule.TABLE_NAME, sExecutingScheduleByIdSelection, new String[]{id});
}
private int UpdateExecutingScheduleById(Uri uri, ContentValues values) {
String id = TimeTableContract.ExecutingSchedule.getTheSecondPara(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
return db.update(TimeTableContract.ExecutingSchedule.TABLE_NAME, values, sExecutingScheduleByIdSelection,
new String[]{id});
}
/*********************end ExecutingSchedule***********************************/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Here's the switch statement that, given a URI, will determine what kind of request it is,
// and query the database accordingly.
Cursor retCursor;
Log.e(LOG_TAG, "query uri = " + uri.toString());
switch (sUriMatcher.match(uri)) {
case TIME_TABLE_COURSE_INFO: {
retCursor = getAllCourseInfo(uri, projection, sortOrder);
break;
}
case TIME_TABLE_EXECUTING_SCHEDULE_WITH_COURSE_ID: {
retCursor = getExecutingScheduleWithCourseId(uri, projection, sortOrder);
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
return retCursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
Uri returnUri;
Log.e(LOG_TAG, "insert uri = " + uri.toString());
switch (match) {
case TIME_TABLE_COURSE_INFO: {
long _id = db.insert(TimeTableContract.CourseInfo.TABLE_NAME, null, values);
if ( _id > 0 )
returnUri = TimeTableContract.CourseInfo.buildCourseInfoUri(_id);
else
throw new android.database.SQLException("Failed to insert row into " + uri);
break;
}
case TIME_TABLE_EXECUTING_SCHEDULE: {
long _id = db.insert(TimeTableContract.ExecutingSchedule.TABLE_NAME, null, values);
if ( _id > 0 )
returnUri = TimeTableContract.ExecutingSchedule.buildExecutingScheduleUri(_id);
else
throw new android.database.SQLException("Failed to insert row into " + uri);
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return returnUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int rowsDeleted;
// this makes delete all rows return the number of rows deleted
if ( null == selection ) selection = "1";
switch (match) {
case TIME_TABLE_EXECUTING_SCHEDULE_WITH_ID:
rowsDeleted = DeleteExecutingScheduleById(uri);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// Because a null deletes all rows
if (rowsDeleted != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;
}
@Override
public int update(
Uri uri, ContentValues values, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int rowsUpdated;
switch (match) {
case TIME_TABLE_EXECUTING_SCHEDULE_WITH_ID:
rowsUpdated = UpdateExecutingScheduleById(uri, values);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if (rowsUpdated != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsUpdated;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
switch (match) {
case TIME_TABLE_COURSE_INFO:{
db.beginTransaction();
int returnCount = 0;
try {
for (ContentValues value : values) {
long _id = db.insert(TimeTableContract.CourseInfo.TABLE_NAME, null, value);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(uri, null);
return returnCount;
}
case TIME_TABLE_EXECUTING_SCHEDULE:{
db.beginTransaction();
int returnCount = 0;
try {
for (ContentValues value : values) {
long _id = db.insert(TimeTableContract.ExecutingSchedule.TABLE_NAME, null, value);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(uri, null);
return returnCount;
}
default:
return super.bulkInsert(uri, values);
}
}
// You do not need to call this method. This is a method specifically to assist the testing
// framework in running smoothly. You can read more at:
// http://developer.android.com/reference/android/content/ContentProvider.html#shutdown()
@Override
@TargetApi(11)
public void shutdown() {
mOpenHelper.close();
super.shutdown();
}
}
ContentProvider是Android的四大組件之一,它的作用是爲不同的模塊或應用之間數據共享提供統一的接口。ContentProvider通過Uri來標識被訪問的數據,通過ContentResolver的增、刪、改、查方法實現對共享數據的操作。
TimeTableProvider作爲ContentProvider的繼承類,它將TimeTable數據庫封裝起來,提供Uri形式的數據增、刪、改操作。本文的數據庫訪問僅限於本應用內。
TimeTableProvider需瞭解的內容如下:
內容 | 說明 |
---|---|
|
將外部應用和模塊訪問數據時所使用的Uri映射到ContentProvider內部。 |
|
幫助實現SQLite數據庫查詢操作的類 |
|
獲取數據庫查詢結果的類型。類型有兩種:0或多個結果、1個結果。 |
insert(),delete(),update(),query() | 數據庫操作:增刪改查 |
bulkinsert() | 數據庫操作:批量插入 |
shutdown() | 關閉數據庫 |
三、配置ContentProvider
爲了使用ContentProvider,還需要在App的AndroidManifest.xml中配置它。如下:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<provider
android:name=".data.TimeTableProvider"
android:authorities="com.aaa.bbb.timetable" />
<activity></activity></application>
————————————————————————
Android SQLite數據庫操作的核心文件到此就創建完畢了。
下一篇文我們將介紹在App使用這些文件中的方法來操作數據庫。