ภาพรวม
ตัวอย่างนี้สาธิตวิธี Insert ข้อมูลลง SQLite โดยใช้คำสั่ง SQL โดยตรง (execSQL) แทนที่จะใช้ ContentValues และ insert() method
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
Android Studio → File → New Project → กรอกรายละเอียด
ขั้นตอนที่ 2: ออกแบบ Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:hint="Enter Name"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/salary"
android:layout_width="match_parent"
android:inputType="numberDecimal"
android:hint="Enter Salary"
android:layout_height="wrap_content" />
<Button
android:id="@+id/save"
android:text="Save"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
ขั้นตอนที่ 3: เขียน MainActivity.java
package com.example.andy.myapplication;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
Button save;
EditText name, salary;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final DatabaseHelper helper = new DatabaseHelper(this);
name = findViewById(R.id.name);
salary = findViewById(R.id.salary);
findViewById(R.id.save).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String nameStr = name.getText().toString().trim();
String salaryStr = salary.getText().toString().trim();
if (!nameStr.isEmpty() && !salaryStr.isEmpty()) {
if (helper.insert(nameStr, salaryStr)) {
Toast.makeText(MainActivity.this, "Inserted", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this, "NOT Inserted", Toast.LENGTH_LONG).show();
}
} else {
name.setError("Enter NAME");
salary.setError("Enter Salary");
}
}
});
}
}
ขั้นตอนที่ 4: สร้าง DatabaseHelper.java
package com.example.andy.myapplication;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
class DatabaseHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "salaryDatabase1";
public static final String CONTACTS_TABLE_NAME = "SalaryDetails";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
// สร้างตารางพร้อม CHECK CONSTRAINT (salary >= 10000)
db.execSQL(
"create table " + CONTACTS_TABLE_NAME +
"(id INTEGER PRIMARY KEY AUTOINCREMENT, name text, salary text, CHECK (salary >= 10000))"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + CONTACTS_TABLE_NAME);
onCreate(db);
}
public boolean insert(String name, String salary) {
SQLiteDatabase db = this.getWritableDatabase();
// ใช้ execSQL ด้วยคำสั่ง INSERT โดยตรง
// ⚠️ ควรใช้ Parameterized Query เพื่อป้องกัน SQL Injection
String sql = "INSERT INTO " + CONTACTS_TABLE_NAME + " (id, name, salary) VALUES (1, '" + name + "', '" + salary + "')";
db.execSQL(sql);
/*
// วิธีเดิมด้วย ContentValues (แนะนำ)
ContentValues contentValues = new ContentValues();
contentValues.put("name", name);
contentValues.put("salary", salary);
db.insert(CONTACTS_TABLE_NAME, null, contentValues);
*/
return true;
}
}
การทดสอบและผลลัพธ์



⚠️ ปัญหาสำคัญของโค้ดตัวอย่าง
1. SQL Injection Vulnerability
// อันตราย! ผู้ใช้ป้อน name = "test'); DROP TABLE SalaryDetails; --"
String sql = "INSERT INTO table (name) VALUES ('" + name + "')";
// กลายเป็น: INSERT INTO table (name) VALUES ('test'); DROP TABLE SalaryDetails; --')
2. Hardcoded ID
VALUES (1, ...) // ID คงที่ = 1 จะทำให้ Insert ครั้งที่ 2 ล้มเหลว (Primary Key Conflict)
3. ข้าม CHECK Constraint
คำสั่ง execSQL ไม่คืนค่าผลลัพธ์ ไม่ทราบว่า Insert สำเร็จหรือถูก Constraint Block
วิธีที่ถูกต้องและปลอดภัย: ใช้ Parameterized Query
public boolean insert(String name, String salary) {
SQLiteDatabase db = this.getWritableDatabase();
// ใช้ ? เป็น Placeholder ป้องกัน SQL Injection
String sql = "INSERT INTO " + CONTACTS_TABLE_NAME + " (name, salary) VALUES (?, ?)";
// execSQL รองรับ Object[] arguments (API 11+)
db.execSQL(sql, new Object[]{name, salary});
// หรือใช้ insert() กับ ContentValues (แนะนำมากที่สุด)
ContentValues values = new ContentValues();
values.put("name", name);
values.put("salary", salary);
long rowId = db.insert(CONTACTS_TABLE_NAME, null, values);
return rowId != -1; // คืน true ถ้า Insert สำเร็จ
}
เปรียบเทียบวิธีการ Insert
| วิธี | ความปลอดภัย | ความสะดวก | คืนค่า Row ID | แนะนำ |
|---|---|---|---|---|
execSQL(sql) ตรงๆ | ❌ ต่ำ (SQL Injection) | ง่าย | ❌ ไม่ได้ | ไม่แนะนำ |
execSQL(sql, args[]) | ✅ สูง | ปานกลาง | ❌ ไม่ได้ | ได้ (กรณีต้องการ SQL ตรง) |
insert() + ContentValues | ✅ สูง | ✅ สะดวก | ✅ ได้ | ⭐ แนะนำมากที่สุด |
replace() + ContentValues | ✅ สูง | ✅ สะดวก | ✅ ได้ | ได้ (Upsert) |
ข้อดีของ ContentValues
- ป้องกัน SQL Injection อัตโนมัติ
- จัดการ Data Type ให้ถูกต้อง (Integer, Double, Blob, etc.)
- คืนค่า Row ID ของแถวที่ Insert สำเร็จ
- รองรับ
nullColumnHackสำหรับแถวว่าง
replace() ทำ Upsert (Insert หรือ Update ถ้ามี Key ซ้ำ)
สรุป
แม้จะสามารถใช้ execSQL กับคำสั่ง INSERT โดยตรงได้ แต่ ไม่แนะนำ เนื่องจากมีช่องโหว่ SQL Injection ไม่คืนค่า Row ID และยุ่งยากในการจัดการ Error ควรใช้ ContentValues ร่วมกับ insert() หรือ replace() ซึ่งเป็น Best Practice ของ Android SQLite