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 static abstract class FeedEntry implements BaseColumns { 77 public static final String TABLE_NAME = "entry"; 78 public static final String COLUMN_NAME_ENTRY_ID = "entryid"; 79 public static final String COLUMN_NAME_TITLE = "title"; 80 public static final String COLUMN_NAME_SUBTITLE = "subtitle"; 81 ... 82} 83</pre> 84 85 86<p>To prevent someone from accidentally instantiating the contract class, give 87it an empty constructor. </p> 88 89<pre> 90// Prevents the FeedReaderContract class from being instantiated. 91private FeedReaderContract() {} 92</pre> 93 94 95 96<h2 id="DbHelper">Create a Database Using a SQL Helper</h2> 97 98<p>Once you have defined how your database looks, you should implement methods 99that create and maintain the database and tables. Here are some typical 100statements that create and delete a table:</P> 101 102<pre> 103private static final String TEXT_TYPE = " TEXT"; 104private static final String COMMA_SEP = ","; 105private static final String SQL_CREATE_ENTRIES = 106 "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" + 107 FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," + 108 FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + 109 FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + 110 ... // Any other options for the CREATE command 111 " )"; 112 113private static final String SQL_DELETE_ENTRIES = 114 "DROP TABLE IF EXISTS " + TABLE_NAME_ENTRIES; 115</pre> 116 117<p>Just like files that you save on the device's <a 118href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal 119storage</a>, Android stores your database in private disk space that's associated 120application. Your data is secure, because by default this area is not 121accessible to other applications.</p> 122 123<p>A useful set of APIs is available in the {@link 124android.database.sqlite.SQLiteOpenHelper} class. 125When you use this class to obtain references to your database, the system 126performs the potentially 127long-running operations of creating and updating the database only when 128needed and <em>not during app startup</em>. All you need to do is call 129{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or 130{@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase}.</p> 131 132<p class="note"><strong>Note:</strong> Because they can be long-running, 133be sure that you call {@link 134android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or {@link 135android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} in a background thread, 136such as with {@link android.os.AsyncTask} or {@link android.app.IntentService}.</p> 137 138<p>To use {@link android.database.sqlite.SQLiteOpenHelper}, create a subclass that 139overrides the {@link 140android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()}, {@link 141android.database.sqlite.SQLiteOpenHelper#onUpgrade onUpgrade()} and {@link 142android.database.sqlite.SQLiteOpenHelper#onOpen onOpen()} callback methods. You may also 143want to implement {@link android.database.sqlite.SQLiteOpenHelper#onDowngrade onDowngrade()}, 144but it's not required.</p> 145 146<p>For example, here's an implementation of {@link 147android.database.sqlite.SQLiteOpenHelper} that uses some of the commands shown above:</p> 148 149<pre> 150public class FeedReaderDbHelper extends SQLiteOpenHelper { 151 // If you change the database schema, you must increment the database version. 152 public static final int DATABASE_VERSION = 1; 153 public static final String DATABASE_NAME = "FeedReader.db"; 154 155 public FeedReaderDbHelper(Context context) { 156 super(context, DATABASE_NAME, null, DATABASE_VERSION); 157 } 158 public void onCreate(SQLiteDatabase db) { 159 db.execSQL(SQL_CREATE_ENTRIES); 160 } 161 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 162 // This database is only a cache for online data, so its upgrade policy is 163 // to simply to discard the data and start over 164 db.execSQL(SQL_DELETE_ENTRIES); 165 onCreate(db); 166 } 167 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 168 onUpgrade(db, oldVersion, newVersion); 169 } 170} 171</pre> 172 173<p>To access your database, instantiate your subclass of {@link 174android.database.sqlite.SQLiteOpenHelper}:</p> 175 176<pre> 177FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext()); 178</pre> 179 180 181 182 183<h2 id="WriteDbRow">Put Information into a Database</h2> 184 185<p>Insert data into the database by passing a {@link android.content.ContentValues} 186object to the {@link android.database.sqlite.SQLiteDatabase#insert insert()} method:</p> 187 188<pre> 189// Gets the data repository in write mode 190SQLiteDatabase db = mDbHelper.getWritableDatabase(); 191 192// Create a new map of values, where column names are the keys 193ContentValues values = new ContentValues(); 194values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id); 195values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); 196values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content); 197 198// Insert the new row, returning the primary key value of the new row 199long newRowId; 200newRowId = db.insert( 201 FeedReaderContract.FeedEntry.TABLE_NAME, 202 FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE, 203 values); 204</pre> 205 206<p>The first argument for {@link android.database.sqlite.SQLiteDatabase#insert insert()} 207is simply the table name. The second argument provides 208the name of a column in which the framework can insert NULL in the event that the 209{@link android.content.ContentValues} is empty (if you instead set this to {@code "null"}, 210then the framework will not insert a row when there are no values).</p> 211 212 213 214 215<h2 id="ReadDbRow">Read Information from a Database</h2> 216 217<p>To read from a database, use the {@link android.database.sqlite.SQLiteDatabase#query query()} 218method, passing it your selection criteria and desired columns. 219The method combines elements of {@link android.database.sqlite.SQLiteDatabase#insert insert()} 220and {@link android.database.sqlite.SQLiteDatabase#update update()}, except the column list 221defines the data you want to fetch, rather than the data to insert. The results of the query 222are returned to you in a {@link android.database.Cursor} object.</p> 223 224<pre> 225SQLiteDatabase db = mDbHelper.getReadableDatabase(); 226 227// Define a <em>projection</em> that specifies which columns from the database 228// you will actually use after this query. 229String[] projection = { 230 FeedReaderContract.FeedEntry._ID, 231 FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, 232 FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED, 233 ... 234 }; 235 236// How you want the results sorted in the resulting Cursor 237String sortOrder = 238 FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC"; 239 240Cursor c = db.query( 241 FeedReaderContract.FeedEntry.TABLE_NAME, // The table to query 242 projection, // The columns to return 243 selection, // The columns for the WHERE clause 244 selectionArgs, // The values for the WHERE clause 245 null, // don't group the rows 246 null, // don't filter by row groups 247 sortOrder // The sort order 248 ); 249</pre> 250 251<p>To look at a row in the cursor, use one of the {@link android.database.Cursor} move 252methods, which you must always call before you begin reading values. Generally, you should start 253by calling {@link android.database.Cursor#moveToFirst}, which places the "read position" on the 254first entry in the results. For each row, you can read a column's value by calling one of the 255{@link android.database.Cursor} get methods, such as {@link android.database.Cursor#getString 256getString()} or {@link android.database.Cursor#getLong getLong()}. For each of the get methods, 257you must pass the index position of the column you desire, which you can get by calling 258{@link android.database.Cursor#getColumnIndex getColumnIndex()} or 259{@link android.database.Cursor#getColumnIndexOrThrow getColumnIndexOrThrow()}. 260For example:</p> 261 262<pre> 263cursor.moveToFirst(); 264long itemId = cursor.getLong( 265 cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID) 266); 267</pre> 268 269 270 271 272<h2 id="DeleteDbRow">Delete Information from a Database</h2> 273 274<p>To delete rows from a table, you need to provide selection criteria that 275identify the rows. The database API provides a mechanism for creating selection 276criteria that protects against SQL injection. The mechanism divides the 277selection specification into a selection clause and selection arguments. The 278clause defines the columns to look at, and also allows you to combine column 279tests. The arguments are values to test against that are bound into the clause. 280Because the result isn't handled the same as a regular SQL statement, it is 281immune to SQL injection.</p> 282 283<pre> 284// Define 'where' part of query. 285String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; 286// Specify arguments in placeholder order. 287String[] selectionArgs = { String.valueOf(rowId) }; 288// Issue SQL statement. 289db.delete(table_name, selection, selectionArgs); 290</pre> 291 292 293 294<h2 id="UpdateDbRow">Update a Database</h2> 295 296<p>When you need to modify a subset of your database values, use the {@link 297android.database.sqlite.SQLiteDatabase#update update()} method.</p> 298 299<p>Updating the table combines the content values syntax of {@link 300android.database.sqlite.SQLiteDatabase#insert insert()} with the {@code where} syntax 301of {@link android.database.sqlite.SQLiteDatabase#delete delete()}.</p> 302 303<pre> 304SQLiteDatabase db = mDbHelper.getReadableDatabase(); 305 306// New value for one column 307ContentValues values = new ContentValues(); 308values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); 309 310// Which row to update, based on the ID 311String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; 312String[] selectionArgs = { String.valueOf(rowId) }; 313 314int count = db.update( 315 FeedReaderDbHelper.FeedEntry.TABLE_NAME, 316 values, 317 selection, 318 selectionArgs); 319</pre> 320 321