Define a Schema and Contract
使用SQL数据库最重要的原则是Schema(架构),数组库如何组织,其实就是表结构。我们一般会用一个同伴类,Contract 类,
明确指定架构的布局以一个系统的和并且自我描述的方式。
一个Contract类是定义URIs,tables,columns这些常量的容器。Contract类允许在同一个包的不同类之间使用同一个常量。这样,
在一个地方修改,其他地方不需要修改。(问题是,这些常量必须在一个包里,所以说,数据相关的代码要放在同一个包里面?)
一个组织Contract类的好方式是放置全局数据库的定义在这个类的根层。然后为每一个表创建一个内部类,枚举他们的列。
sunshine的这个WeatherContract类,定义了全局的URI路径:
// 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.loveqiqi.sy.mysunshine"; // 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); // Possible paths (appended to base content URI for possible URI‘s) // For instance, content://com.example.android.sunshine.app/weather/ is a valid path for // looking at weather data. content://com.example.android.sunshine.app/givemeroot/ will fail, // as the ContentProvider hasn‘t been given any information on what to do with "givemeroot". // At least, let‘s hope not. Don‘t be that dev, reader. Don‘t be that dev. public static final String PATH_WEATHER = "weather"; public static final String PATH_LOCATION = "location";
每个表有一个静态内部类:
这个内部类一般实现BaseColumns,这样就会有一个primary key, _ID.
这个类列举了表列的名词,然后提供了一些内容提供器需要的Uri。
/* Inner class that defines the table contents of the location table Students: This is where you will add the strings. (Similar to what has been done for WeatherEntry) */ public static final class LocationEntry implements BaseColumns { public static final String TABLE_NAME = "location"; public static final String COLUMN_LOCATION_SETTING = "location_setting"; public static final String COLUMN_COORD_LAT = "coord_lat"; public static final String COLUMN_COORD_LONG = "coord_long"; public static final String COLUMN_CITY_NAME="city_name"; public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_LOCATION).build(); public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION; public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION; public static Uri buildLocationUri(long id) { return ContentUris.withAppendedId(CONTENT_URI, id); } }
Create a Database Using a SQL Helper
一旦我们定义了我们数据库的样子,应该实现方法,这些方法创建和维护数据库和表格。
这就要实现SQLiteOpenHelper了。
下面是例子:实现onCreate方法创建表。
/** * Manages a local database for weather data. */ public class WeatherDbHelper extends SQLiteOpenHelper { // If you change the database schema, you must increment the database version. private static final int DATABASE_VERSION = 2; static final String DATABASE_NAME = "weather.db"; public WeatherDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { final String SQL_CREATE_WEATHER_TABLE = "CREATE TABLE " + WeatherEntry.TABLE_NAME + " (" + // Why AutoIncrement here, and not above? // Unique keys will be auto-generated in either case. But for weather // forecasting, it‘s reasonable to assume the user will want information // for a certain date and all dates *following*, so the forecast data // should be sorted accordingly. WeatherEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + // the ID of the location entry associated with this weather data WeatherEntry.COLUMN_LOC_KEY + " INTEGER NOT NULL, " + WeatherEntry.COLUMN_DATE + " INTEGER NOT NULL, " + WeatherEntry.COLUMN_SHORT_DESC + " TEXT NOT NULL, " + WeatherEntry.COLUMN_WEATHER_ID + " INTEGER NOT NULL," + WeatherEntry.COLUMN_MIN_TEMP + " REAL NOT NULL, " + WeatherEntry.COLUMN_MAX_TEMP + " REAL NOT NULL, " + WeatherEntry.COLUMN_HUMIDITY + " REAL NOT NULL, " + WeatherEntry.COLUMN_PRESSURE + " REAL NOT NULL, " + WeatherEntry.COLUMN_WIND_SPEED + " REAL NOT NULL, " + WeatherEntry.COLUMN_DEGREES + " REAL NOT NULL, " + // Set up the location column as a foreign key to location table. " FOREIGN KEY (" + WeatherEntry.COLUMN_LOC_KEY + ") REFERENCES " + LocationEntry.TABLE_NAME + " (" + LocationEntry._ID + "), " + // To assure the application have just one weather entry per day // per location, it‘s created a UNIQUE constraint with REPLACE strategy " UNIQUE (" + WeatherEntry.COLUMN_DATE + ", " + WeatherEntry.COLUMN_LOC_KEY + ") ON CONFLICT REPLACE);"; final String SQL_CREATE_LOACTION_TABLE = "CREATE TABLE " + LocationEntry.TABLE_NAME + "(" + LocationEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + LocationEntry.COLUMN_LOCATION_SETTING + " TEXT UNIQUE NOT NULL," + LocationEntry.COLUMN_COORD_LAT + " REAL NOT NULL,"+ LocationEntry.COLUMN_COORD_LONG + " REAL NOT NULL," + LocationEntry.COLUMN_CITY_NAME + " TEXT NOT NULL); "; sqLiteDatabase.execSQL(SQL_CREATE_WEATHER_TABLE); sqLiteDatabase.execSQL(SQL_CREATE_LOACTION_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 " + LocationEntry.TABLE_NAME); sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + WeatherEntry.TABLE_NAME); onCreate(sqLiteDatabase); } }
这样我们要访问数据库,使用这个类获得数据库的引用。系统执行潜在的长时间运行的创建更新数据库操作仅仅在需要的时候,不会再
应用启动。我们只要调用getWritableDatabase或者getReadableDatabase,因为可能长时间运行,所以要在后台进程调用,比如AsyncTask
或者IntentService(sunshine一直到SyncAdapter)。
public SQLiteDatabase getWritableDatabase
()说明
上面讨论系统在需要的时候去执行这些潜在的长时间运行的操作,在getWritableDatabase方法第一次调用的时候,数据库会打开,
然后onCreate(SQLiteDatabase)
,onUpgrade(SQLiteDatabase,
and/or
int, int)onOpen(SQLiteDatabase)
will be
called.所以,
数据库的创建是在我们第一次调用这个函数的时候。数据库才被创建。
使用数据库的时候,直接这样:
然后sunshine的在ContentProvider的类的OnCreate中初始化定义的mOpenHelper。我们也只要这么使用就好。
mOpenHelper = new WeatherDbHelper(getContext());
然后就是对数据库的增删改查了。
数据库插入
使用ContentValues
// Gets the data repository in write mode SQLiteDatabase db = mDbHelper.getWritableDatabase(); // Create a new map of values, where column names are the keys ContentValues values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id); values.put(FeedEntry.COLUMN_NAME_TITLE, title); values.put(FeedEntry.COLUMN_NAME_CONTENT, content); // Insert the new row, returning the primary key value of the new row long newRowId; newRowId = db.insert( FeedEntry.TABLE_NAME, FeedEntry.COLUMN_NAME_NULLABLE, values);
第一个参数是表名,第二个参数是设置哪些列可以为空,如果设置为null,那么系统不会插入一个空行),一般是null吧,这样在定义
数据库的时候就需要知道哪些列是可为空的?如果要设置第二个参数的话.
数据库查询
SQLiteDatabase db = mDbHelper.getReadableDatabase(); // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { FeedEntry._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_UPDATED, ... }; // How you want the results sorted in the resulting Cursor String sortOrder = FeedEntry.COLUMN_NAME_UPDATED + " DESC"; Cursor c = db.query( FeedEntry.TABLE_NAME, // The table to query projection, // The columns to return selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don‘t group the rows null, // don‘t filter by row groups sortOrder // The sort order );
public Cursor query (String table,String[]
columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
查询的第一个参数:表名
第二个:返回列,string数组,传递null,返回所有列,不推荐,阻止返回冗余数据
第三:哪些行返回,null返回所有行,会格式化为where语句
第四:如果在第三个参数包含?,这里需要传递? 的表示是数组
第五:Group by语句
第六:having 语句
第七:orderBy语句
上面,返回的值在一个Cursor中,如果要读数据,首先首先要调用cursor.moveToFirst从第一个开始,最好是加一个判断,以为如果
cursor为null,这个方法返回false。
This method will return false if the cursor is empty
然后就可以使用了:
cursor.moveToFirst(); long itemId = cursor.getLong( cursor.getColumnIndexOrThrow(FeedEntry._ID) );
删除数据
删除数据要提供删除的行,像下面一样,防止SQL注入:
// Define ‘where‘ part of query. String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; // Specify arguments in placeholder order. String[] selectionArgs = { String.valueOf(rowId) }; // Issue SQL statement. db.delete(table_name, selection, selectionArgs);
public int delete(String table,String
whereClause, String[] whereArgs)
Returns
- 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.
更新数据
下面是例子:
SQLiteDatabase db = mDbHelper.getWritableDatabase(); // New value for one column ContentValues values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_TITLE, title); // Which row to update, based on the ID String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; String[] selectionArgs = { String.valueOf(rowId) }; int count = db.update( FeedReaderDbHelper.FeedEntry.TABLE_NAME, values, selection, selectionArgs);