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