1page.title=Saving Data in SQL Databases 2parent.title=Data Storage 3parent.link=index.html 4 5trainingnavtop=true 6previous.title=Saving Data in Files 7previous.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, 44such as contact information. This class assumes that you are 45familiar with SQL databases in general and helps you get started with 46SQLite databases on Android. The APIs you'll need to use a database 47on 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 53declaration of how the database is organized. The schema is reflected in the SQL 54statements that you use to create your database. You may find it helpful to 55create a companion class, known as a <em>contract</em> class, which explicitly specifies 56the 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, 59tables, and columns. The contract class allows you to use the same constants 60across all the other classes in the same package. This lets you change a column 61name 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 64global to your whole database in the root level of the class. Then create an inner 65class for each table that enumerates its columns.</p> 66 67<p class="note"><strong>Note:</strong> By implementing the {@link 68android.provider.BaseColumns} interface, your inner class can inherit a primary 69key field called {@code _ID} that some Android classes such as cursor adaptors 70will expect it to have. It's not required, but this can help your database 71work harmoniously with the Android framework.</p> 72 73<p>For example, this snippet defines the table name and column names for a 74single table:</p> 75 76 77<pre> 78public 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 89it an empty constructor. </p> 90 91<pre> 92// Prevents the FeedReaderContract class from being instantiated. 93private 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 101that create and maintain the database and tables. Here are some typical 102statements that create and delete a table:</P> 103 104<pre> 105private static final String TEXT_TYPE = " TEXT"; 106private static final String COMMA_SEP = ","; 107private 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 115private 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 120href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal 121storage</a>, Android stores your database in private disk space that's associated 122application. Your data is secure, because by default this area is not 123accessible to other applications.</p> 124 125<p>A useful set of APIs is available in the {@link 126android.database.sqlite.SQLiteOpenHelper} class. 127When you use this class to obtain references to your database, the system 128performs the potentially 129long-running operations of creating and updating the database only when 130needed 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, 135be sure that you call {@link 136android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or {@link 137android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} in a background thread, 138such 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 141overrides the {@link 142android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()}, {@link 143android.database.sqlite.SQLiteOpenHelper#onUpgrade onUpgrade()} and {@link 144android.database.sqlite.SQLiteOpenHelper#onOpen onOpen()} callback methods. You may also 145want to implement {@link android.database.sqlite.SQLiteOpenHelper#onDowngrade onDowngrade()}, 146but it's not required.</p> 147 148<p>For example, here's an implementation of {@link 149android.database.sqlite.SQLiteOpenHelper} that uses some of the commands shown above:</p> 150 151<pre> 152public 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 176android.database.sqlite.SQLiteOpenHelper}:</p> 177 178<pre> 179FeedReaderDbHelper 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} 188object to the {@link android.database.sqlite.SQLiteDatabase#insert insert()} method:</p> 189 190<pre> 191// Gets the data repository in write mode 192SQLiteDatabase db = mDbHelper.getWritableDatabase(); 193 194// Create a new map of values, where column names are the keys 195ContentValues values = new ContentValues(); 196values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id); 197values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); 198values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content); 199 200// Insert the new row, returning the primary key value of the new row 201long newRowId; 202newRowId = 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()} 209is simply the table name. The second argument provides 210the 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"}, 212then 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()} 220method, passing it your selection criteria and desired columns. 221The method combines elements of {@link android.database.sqlite.SQLiteDatabase#insert insert()} 222and {@link android.database.sqlite.SQLiteDatabase#update update()}, except the column list 223defines the data you want to fetch, rather than the data to insert. The results of the query 224are returned to you in a {@link android.database.Cursor} object.</p> 225 226<pre> 227SQLiteDatabase 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. 231String[] 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 239String sortOrder = 240 FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC"; 241 242Cursor 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 254methods, which you must always call before you begin reading values. Generally, you should start 255by calling {@link android.database.Cursor#moveToFirst}, which places the "read position" on the 256first 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 258getString()} or {@link android.database.Cursor#getLong getLong()}. For each of the get methods, 259you 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()}. 262For example:</p> 263 264<pre> 265cursor.moveToFirst(); 266long 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 277identify the rows. The database API provides a mechanism for creating selection 278criteria that protects against SQL injection. The mechanism divides the 279selection specification into a selection clause and selection arguments. The 280clause defines the columns to look at, and also allows you to combine column 281tests. The arguments are values to test against that are bound into the clause. 282Because the result isn't handled the same as a regular SQL statement, it is 283immune to SQL injection.</p> 284 285<pre> 286// Define 'where' part of query. 287String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; 288// Specify arguments in placeholder order. 289String[] selelectionArgs = { String.valueOf(rowId) }; 290// Issue SQL statement. 291db.delete(table_name, selection, 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 299android.database.sqlite.SQLiteDatabase#update update()} method.</p> 300 301<p>Updating the table combines the content values syntax of {@link 302android.database.sqlite.SQLiteDatabase#insert insert()} with the {@code where} syntax 303of {@link android.database.sqlite.SQLiteDatabase#delete delete()}.</p> 304 305<pre> 306SQLiteDatabase db = mDbHelper.getReadableDatabase(); 307 308// New value for one column 309ContentValues values = new ContentValues(); 310values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); 311 312// Which row to update, based on the ID 313String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; 314String[] selelectionArgs = { String.valueOf(rowId) }; 315 316int count = db.update( 317 FeedReaderDbHelper.FeedEntry.TABLE_NAME, 318 values, 319 selection, 320 selectionArgs); 321</pre> 322 323