新手開發 Android App 時,資料庫插入操作報錯,程式碼邏輯看似正確:
// 錯誤寫法
SQLiteDatabase db = dbHelper.getReadableDatabase();
db.execSQL("INSERT INTO dict VALUES (null, ?, ?)", new String[]{word, detail});
Logcat 顯示類似 attempt to write a readonly database 或無明顯錯誤但資料未寫入。
SQLiteOpenHelper 提供兩個方法取得 SQLiteDatabase 實例:
| 方法 | 回傳資料庫狀態 | 適用場景 |
|---|---|---|
getReadableDatabase() |
唯讀 (若磁碟空間不足或資料庫損壞,可能回傳唯讀) | 純查詢操作 (SELECT) |
getWritableDatabase() |
讀寫 (若無法取得寫入權限會拋出異常) | 增、刪、改操作 (INSERT, UPDATE, DELETE) |
插入資料屬於寫入操作,**必須使用 getWritableDatabase()**。
public class DictActivity extends Activity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dict); // 別忘了 setContentView!
dbHelper = new MyDatabaseHelper(this, "myDict.db3", null, 1);
Button btnInsert = findViewById(R.id.insert);
btnInsert.setOnClickListener(v -> {
String word = ((EditText)findViewById(R.id.word)).getText().toString();
String detail = ((EditText)findViewById(R.id.detail)).getText().toString();
// 正確:取得可寫入資料庫
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.execSQL("INSERT INTO dict (word, detail) VALUES (?, ?)",
new Object[]{word, detail}); // 建議用 Object[] 避免型別問題
db.close(); // 良好習慣:用完關閉
Toast.makeText(this, "插入成功", Toast.LENGTH_SHORT).show();
});
}
}
ContentValues 替代原始 SQL:防止 SQL 注入、自動處理特殊字元、可讀性更高。
ContentValues values = new ContentValues();
values.put("word", word);
values.put("detail", detail);
long newRowId = db.insert("dict", null, values);
AsyncTask、RxJava、Coroutines 或 Room (官方推薦 ORM)。Cursor、SQLiteDatabase 用完即關,防洩漏。onUpgrade() 處理結構變更。Android SQLite 寫入操作**務必呼叫 getWritableDatabase()**。這是新手最常犯的低級錯誤,改對方法、加上 ContentValues 與背景執行緒,資料庫操作即穩定可靠。