Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีบันทึกข้อมูลใน SQLite Android โดยไม่ใช้ ContentValues (ใช้ SQL Syntax โดยตรง)

ภาพรวม

ตัวอย่างนี้สาธิตวิธี 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;
    }
}

การทดสอบและผลลัพธ์

วิธีบันทึกข้อมูลใน SQLite Android โดยไม่ใช้ ContentValues (ใช้ SQL Syntax โดยตรง)

วิธีบันทึกข้อมูลใน SQLite Android โดยไม่ใช้ ContentValues (ใช้ SQL Syntax โดยตรง)

วิธีบันทึกข้อมูลใน SQLite Android โดยไม่ใช้ ContentValues (ใช้ SQL Syntax โดยตรง)

⚠️ ปัญหาสำคัญของโค้ดตัวอย่าง

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