|
| 1 | +page.title=Saving Data in SQL Databases |
| 2 | +parent.title=Data Storage |
| 3 | +parent.link=index.html |
| 4 | + |
| 5 | +trainingnavtop=true |
| 6 | +previous.title=Saving Data in Files |
| 7 | +previous.link=files.html |
| 8 | + |
| 9 | +@jd:body |
| 10 | + |
| 11 | + |
| 12 | +<div id="tb-wrapper"> |
| 13 | +<div id="tb"> |
| 14 | + |
| 15 | +<h2>This lesson teaches you to</h2> |
| 16 | +<ol> |
| 17 | + <li><a href="#DefineContract">Define a Schema and Contract</a></li> |
| 18 | + <li><a href="#DbHelper">Create a Database Using a SQL Helper</a></li> |
| 19 | + <li><a href="#WriteDbRow">Put Information into a Database</a></li> |
| 20 | + <li><a href="#ReadDbRow">Read Information from a Database</a></li> |
| 21 | + <li><a href="#DeleteDbRow">Delete Information from a Database</a></li> |
| 22 | + <li><a href="#UpdateDbRow">Update a Database</a></li> |
| 23 | +</ol> |
| 24 | + |
| 25 | +<h2>You should also read</h2> |
| 26 | +<ul> |
| 27 | + <li><a href="{@docRoot}guide/topics/data/data-storage.html#db">Using Databases</a></li> |
| 28 | +</ul> |
| 29 | + |
| 30 | +<!-- |
| 31 | +<h2>Try it out</h2> |
| 32 | + |
| 33 | +<div class="download-box"> |
| 34 | + <a href="{@docRoot}shareables/training/Sample.zip" class="button">Download the sample</a> |
| 35 | + <p class="filename">Sample.zip</p> |
| 36 | +</div> |
| 37 | +--> |
| 38 | + |
| 39 | +</div> |
| 40 | +</div> |
| 41 | + |
| 42 | + |
| 43 | +<p>Saving data to a database is ideal for repeating or structured data, |
| 44 | +such as contact information. This class assumes that you are |
| 45 | +familiar with SQL databases in general and helps you get started with |
| 46 | +SQLite databases on Android. The APIs you'll need to use a database |
| 47 | +on Android are available in the {@link android.database.sqlite} package.</p> |
| 48 | + |
| 49 | + |
| 50 | +<h2 id="DefineContract">Define a Schema and Contract</h2> |
| 51 | + |
| 52 | +<p>One of the main principles of SQL databases is the schema: a formal |
| 53 | +declaration of how the database is organized. The schema is reflected in the SQL |
| 54 | +statements that you use to create your database. You may find it helpful to |
| 55 | +create a companion class, known as a <em>contract</em> class, which explicitly specifies |
| 56 | +the layout of your schema in a systematic and self-documenting way.</p> |
| 57 | + |
| 58 | +<p>A contract class is a container for constants that define names for URIs, |
| 59 | +tables, and columns. The contract class allows you to use the same constants |
| 60 | +across all the other classes in the same package. This lets you change a column |
| 61 | +name in one place and have it propagate throughout your code.</p> |
| 62 | + |
| 63 | +<p>A good way to organize a contract class is to put definitions that are |
| 64 | +global to your whole database in the root level of the class. Then create an inner |
| 65 | +class for each table that enumerates its columns.</p> |
| 66 | + |
| 67 | +<p class="note"><strong>Note:</strong> By implementing the {@link |
| 68 | +android.provider.BaseColumns} interface, your inner class can inherit a primary |
| 69 | +key field called {@code _ID} that some Android classes such as cursor adaptors |
| 70 | +will expect it to have. It's not required, but this can help your database |
| 71 | +work harmoniously with the Android framework.</p> |
| 72 | + |
| 73 | +<p>For example, this snippet defines the table name and column names for a |
| 74 | +single table:</p> |
| 75 | + |
| 76 | + |
| 77 | +<pre> |
| 78 | +public static abstract class FeedEntry implements BaseColumns { |
| 79 | + public static final String TABLE_NAME = "entry"; |
| 80 | + public static final String COLUMN_NAME_ENTRY_ID = "entryid"; |
| 81 | + public static final String COLUMN_NAME_TITLE = "title"; |
| 82 | + public static final String COLUMN_NAME_SUBTITLE = "subtitle"; |
| 83 | + ... |
| 84 | +} |
| 85 | +</pre> |
| 86 | + |
| 87 | + |
| 88 | +<p>To prevent someone from accidentally instantiating the contract class, give |
| 89 | +it an empty constructor. </p> |
| 90 | + |
| 91 | +<pre> |
| 92 | +// Prevents the FeedReaderContract class from being instantiated. |
| 93 | +private FeedReaderContract() {} |
| 94 | +</pre> |
| 95 | + |
| 96 | + |
| 97 | + |
| 98 | +<h2 id="DbHelper">Create a Database Using a SQL Helper</h2> |
| 99 | + |
| 100 | +<p>Once you have defined how your database looks, you should implement methods |
| 101 | +that create and maintain the database and tables. Here are some typical |
| 102 | +statements that create and delete a table:</P> |
| 103 | + |
| 104 | +<pre> |
| 105 | +private static final String TEXT_TYPE = " TEXT"; |
| 106 | +private static final String COMMA_SEP = ","; |
| 107 | +private static final String SQL_CREATE_ENTRIES = |
| 108 | + "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" + |
| 109 | + FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," + |
| 110 | + FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + |
| 111 | + FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + |
| 112 | + ... // Any other options for the CREATE command |
| 113 | + " )"; |
| 114 | + |
| 115 | +private static final String SQL_DELETE_ENTRIES = |
| 116 | + "DROP TABLE IF EXISTS " + TABLE_NAME_ENTRIES; |
| 117 | +</pre> |
| 118 | + |
| 119 | +<p>Just like files that you save on the device's <a |
| 120 | +href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal |
| 121 | +storage</a>, Android stores your database in private disk space that's associated |
| 122 | +application. Your data is secure, because by default this area is not |
| 123 | +accessible to other applications.</p> |
| 124 | + |
| 125 | +<p>A useful set of APIs is available in the {@link |
| 126 | +android.database.sqlite.SQLiteOpenHelper} class. |
| 127 | +When you use this class to obtain references to your database, the system |
| 128 | +performs the potentially |
| 129 | +long-running operations of creating and updating the database only when |
| 130 | +needed and <em>not during app startup</em>. All you need to do is call |
| 131 | +{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or |
| 132 | +{@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase}.</p> |
| 133 | + |
| 134 | +<p class="note"><strong>Note:</strong> Because they can be long-running, |
| 135 | +be sure that you call {@link |
| 136 | +android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or {@link |
| 137 | +android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} in a background thread, |
| 138 | +such as with {@link android.os.AsyncTask} or {@link android.app.IntentService}.</p> |
| 139 | + |
| 140 | +<p>To use {@link android.database.sqlite.SQLiteOpenHelper}, create a subclass that |
| 141 | +overrides the {@link |
| 142 | +android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()}, {@link |
| 143 | +android.database.sqlite.SQLiteOpenHelper#onUpgrade onUpgrade()} and {@link |
| 144 | +android.database.sqlite.SQLiteOpenHelper#onOpen onOpen()} callback methods. You may also |
| 145 | +want to implement {@link android.database.sqlite.SQLiteOpenHelper#onDowngrade onDowngrade()}, |
| 146 | +but it's not required.</p> |
| 147 | + |
| 148 | +<p>For example, here's an implementation of {@link |
| 149 | +android.database.sqlite.SQLiteOpenHelper} that uses some of the commands shown above:</p> |
| 150 | + |
| 151 | +<pre> |
| 152 | +public class FeedReaderDbHelper extends SQLiteOpenHelper { |
| 153 | + // If you change the database schema, you must increment the database version. |
| 154 | + public static final int DATABASE_VERSION = 1; |
| 155 | + public static final String DATABASE_NAME = "FeedReader.db"; |
| 156 | + |
| 157 | + public FeedReaderDbHelper(Context context) { |
| 158 | + super(context, DATABASE_NAME, null, DATABASE_VERSION); |
| 159 | + } |
| 160 | + public void onCreate(SQLiteDatabase db) { |
| 161 | + db.execSQL(SQL_CREATE_ENTRIES); |
| 162 | + } |
| 163 | + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { |
| 164 | + // This database is only a cache for online data, so its upgrade policy is |
| 165 | + // to simply to discard the data and start over |
| 166 | + db.execSQL(SQL_DELETE_ENTRIES); |
| 167 | + onCreate(db); |
| 168 | + } |
| 169 | + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { |
| 170 | + onUpgrade(db, oldVersion, newVersion); |
| 171 | + } |
| 172 | +} |
| 173 | +</pre> |
| 174 | + |
| 175 | +<p>To access your database, instantiate your subclass of {@link |
| 176 | +android.database.sqlite.SQLiteOpenHelper}:</p> |
| 177 | + |
| 178 | +<pre> |
| 179 | +FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext()); |
| 180 | +</pre> |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | + |
| 185 | +<h2 id="WriteDbRow">Put Information into a Database</h2> |
| 186 | + |
| 187 | +<p>Insert data into the database by passing a {@link android.content.ContentValues} |
| 188 | +object to the {@link android.database.sqlite.SQLiteDatabase#insert insert()} method:</p> |
| 189 | + |
| 190 | +<pre> |
| 191 | +// Gets the data repository in write mode |
| 192 | +SQLiteDatabase db = mDbHelper.getWritableDatabase(); |
| 193 | + |
| 194 | +// Create a new map of values, where column names are the keys |
| 195 | +ContentValues values = new ContentValues(); |
| 196 | +values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id); |
| 197 | +values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); |
| 198 | +values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content); |
| 199 | + |
| 200 | +// Insert the new row, returning the primary key value of the new row |
| 201 | +long newRowId; |
| 202 | +newRowId = db.insert( |
| 203 | + FeedReaderContract.FeedEntry.TABLE_NAME, |
| 204 | + FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE, |
| 205 | + values); |
| 206 | +</pre> |
| 207 | + |
| 208 | +<p>The first argument for {@link android.database.sqlite.SQLiteDatabase#insert insert()} |
| 209 | +is simply the table name. The second argument provides |
| 210 | +the name of a column in which the framework can insert NULL in the event that the |
| 211 | +{@link android.content.ContentValues} is empty (if you instead set this to {@code "null"}, |
| 212 | +then the framework will not insert a row when there are no values).</p> |
| 213 | + |
| 214 | + |
| 215 | + |
| 216 | + |
| 217 | +<h2 id="ReadDbRow">Read Information from a Database</h2> |
| 218 | + |
| 219 | +<p>To read from a database, use the {@link android.database.sqlite.SQLiteDatabase#query query()} |
| 220 | +method, passing it your selection criteria and desired columns. |
| 221 | +The method combines elements of {@link android.database.sqlite.SQLiteDatabase#insert insert()} |
| 222 | +and {@link android.database.sqlite.SQLiteDatabase#update update()}, except the column list |
| 223 | +defines the data you want to fetch, rather than the data to insert. The results of the query |
| 224 | +are returned to you in a {@link android.database.Cursor} object.</p> |
| 225 | + |
| 226 | +<pre> |
| 227 | +SQLiteDatabase db = mDbHelper.getReadableDatabase(); |
| 228 | + |
| 229 | +// Define a <em>projection</em> that specifies which columns from the database |
| 230 | +// you will actually use after this query. |
| 231 | +String[] projection = { |
| 232 | + FeedReaderContract.FeedEntry._ID, |
| 233 | + FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, |
| 234 | + FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED, |
| 235 | + ... |
| 236 | + }; |
| 237 | + |
| 238 | +// How you want the results sorted in the resulting Cursor |
| 239 | +String sortOrder = |
| 240 | + FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC"; |
| 241 | + |
| 242 | +Cursor c = db.query( |
| 243 | + FeedReaderContract.FeedEntry.TABLE_NAME, // The table to query |
| 244 | + projection, // The columns to return |
| 245 | + selection, // The columns for the WHERE clause |
| 246 | + selectionArgs, // The values for the WHERE clause |
| 247 | + null, // don't group the rows |
| 248 | + null, // don't filter by row groups |
| 249 | + sortOrder // The sort order |
| 250 | + ); |
| 251 | +</pre> |
| 252 | + |
| 253 | +<p>To look at a row in the cursor, use one of the {@link android.database.Cursor} move |
| 254 | +methods, which you must always call before you begin reading values. Generally, you should start |
| 255 | +by calling {@link android.database.Cursor#moveToFirst}, which places the "read position" on the |
| 256 | +first entry in the results. For each row, you can read a column's value by calling one of the |
| 257 | +{@link android.database.Cursor} get methods, such as {@link android.database.Cursor#getString |
| 258 | +getString()} or {@link android.database.Cursor#getLong getLong()}. For each of the get methods, |
| 259 | +you must pass the index position of the column you desire, which you can get by calling |
| 260 | +{@link android.database.Cursor#getColumnIndex getColumnIndex()} or |
| 261 | +{@link android.database.Cursor#getColumnIndexOrThrow getColumnIndexOrThrow()}. |
| 262 | +For example:</p> |
| 263 | + |
| 264 | +<pre> |
| 265 | +cursor.moveToFirst(); |
| 266 | +long itemId = cursor.getLong( |
| 267 | + cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID) |
| 268 | +); |
| 269 | +</pre> |
| 270 | + |
| 271 | + |
| 272 | + |
| 273 | + |
| 274 | +<h2 id="DeleteDbRow">Delete Information from a Database</h2> |
| 275 | + |
| 276 | +<p>To delete rows from a table, you need to provide selection criteria that |
| 277 | +identify the rows. The database API provides a mechanism for creating selection |
| 278 | +criteria that protects against SQL injection. The mechanism divides the |
| 279 | +selection specification into a selection clause and selection arguments. The |
| 280 | +clause defines the columns to look at, and also allows you to combine column |
| 281 | +tests. The arguments are values to test against that are bound into the clause. |
| 282 | +Because the result isn't handled the same as a regular SQL statement, it is |
| 283 | +immune to SQL injection.</p> |
| 284 | + |
| 285 | +<pre> |
| 286 | +// Define 'where' part of query. |
| 287 | +String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; |
| 288 | +// Specify arguments in placeholder order. |
| 289 | +String[] selelectionArgs = { String.valueOf(rowId) }; |
| 290 | +// Issue SQL statement. |
| 291 | +db.delete(table_name, mySelection, selectionArgs); |
| 292 | +</pre> |
| 293 | + |
| 294 | + |
| 295 | + |
| 296 | +<h2 id="UpdateDbRow">Update a Database</h2> |
| 297 | + |
| 298 | +<p>When you need to modify a subset of your database values, use the {@link |
| 299 | +android.database.sqlite.SQLiteDatabase#update update()} method.</p> |
| 300 | + |
| 301 | +<p>Updating the table combines the content values syntax of {@link |
| 302 | +android.database.sqlite.SQLiteDatabase#insert insert()} with the {@code where} syntax |
| 303 | +of {@link android.database.sqlite.SQLiteDatabase#delete delete()}.</p> |
| 304 | + |
| 305 | +<pre> |
| 306 | +SQLiteDatabase db = mDbHelper.getReadableDatabase(); |
| 307 | + |
| 308 | +// New value for one column |
| 309 | +ContentValues values = new ContentValues(); |
| 310 | +values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); |
| 311 | + |
| 312 | +// Which row to update, based on the ID |
| 313 | +String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; |
| 314 | +String[] selelectionArgs = { String.valueOf(rowId) }; |
| 315 | + |
| 316 | +int count = db.update( |
| 317 | + FeedReaderDbHelper.FeedEntry.TABLE_NAME, |
| 318 | + values, |
| 319 | + selection, |
| 320 | + selectionArgs); |
| 321 | +</pre> |
| 322 | + |
0 commit comments