コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

Androidアプリ入門 No.74 SQLiteの使用 安全な登録をする

SQLiteの使用

安全な登録をする

先の例では、INSERT文に入力値をそのまま渡していた。そのためSQLインジェクションの恐れがある。Webアプリと違い、ローカルのアプリケーションとなるため実際には問題にならないケースが多かもしれない(不具合にはなる)が不正な入力はできないほうがよい。そのため、JDBCのPreparedStatementのような仕組みがAndroidにもある。先の例のDbTest.javaを以下のようにする。

package sample.dt;

import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class DbTest extends Activity {
    private EditText searchEditText;
    private Button readButton;
    private TextView resultTextView;

    private EditText nameEditText;
    private EditText ageEditText;
    private Button insertButton;

    private DatabaseHelper databaseHelper;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // DB作成
        databaseHelper = new DatabaseHelper(getApplicationContext());

        searchEditText = (EditText) findViewById(R.id.searchEditText);
        readButton = (Button) findViewById(R.id.readButton);
        readButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                read();
            }
        });
        resultTextView = (TextView) findViewById(R.id.resultTextView);

        nameEditText = (EditText) findViewById(R.id.nameEditText);
        ageEditText = (EditText) findViewById(R.id.ageEditText);
        insertButton = (Button) findViewById(R.id.insertButton);
        insertButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                insert();
            }
        });
    }

    private void read() {
        String search = searchEditText.getText().toString();
        if (search.equals("")) {
            Toast.makeText(getApplicationContext(), "検索条件を入力してください。",
                    Toast.LENGTH_SHORT).show();
            return;
        }

        SQLiteDatabase db = databaseHelper.getReadableDatabase();
        Cursor cursor = db.query("emp", new String[] { "name", "age" },
                "name LIKE '%' || ? || '%'", new String[] { search }, null, null, "age DESC");
        cursor.moveToFirst();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < cursor.getCount(); i++) {
            sb.append(cursor.getString(0)).append(",");
            sb.append(cursor.getInt(1)).append("\n");
            cursor.moveToNext();
        }
        cursor.close();
        resultTextView.setText(sb.toString());
    }

    private void insert() {
        String name = nameEditText.getText().toString();
        if (name.equals("")) {
            Toast.makeText(getApplicationContext(), "登録条件(名前)を入力してください。",
                    Toast.LENGTH_SHORT).show();
            return;
        }

        String age = ageEditText.getText().toString();
        if (age.equals("")) {
            Toast.makeText(getApplicationContext(), "登録条件(年齢)を入力してください。",
                    Toast.LENGTH_SHORT).show();
            return;
        }

        SQLiteDatabase db = databaseHelper.getWritableDatabase();
        SQLiteStatement stmt = db.compileStatement("INSERT INTO emp(name, age) VALUES(?, ?)");
        stmt.bindString(1, name);
        stmt.bindLong(2, Long.parseLong(age));
        stmt.executeInsert();
        nameEditText.setText("");
        ageEditText.setText("");
        Toast.makeText(getApplicationContext(), "データを登録しました。",
                Toast.LENGTH_SHORT).show();
    }
}

実行結果は以下のようになる。「'」を入力しても問題なく動作する。

DBへのINSERTの部分は以下のようになっている。

SQLiteDatabase db = databaseHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO emp(name, age) VALUES(?, ?)");
stmt.bindString(1, name);
stmt.bindLong(2, Long.parseLong(age));
stmt.executeInsert();

ここで使用している、SQLiteDatabase#compileStatementがJDBCのPreparedStatementに近いものとなる。具体的なメソッド定義は以下のようになる。

public SQLiteStatement compileStatement(String sql)

引数の詳細は以下。

引数 説明
sql SQL。?が置き換え文字となっている。

上記の例で、SQLの中で「VALUES(?, ?)」と実際に登録する値が置き換え文字列となっている。この?に値を格納するには、SQLiteDatabase#compileStatementの戻り値であるSQLiteStatementのbindXxxメソッドを使用する。bindXxxメソッドには以下の種類がある。indexは1から始まり、適切にエスケープされる。

メソッド 説明
bindNull(int index) indexの場所をnullで置き換える。
bindLong(int index, long value) indexの場所をvalueで置き換える。
bindDouble(int index, long doube) indexの場所をvalueで置き換える。
bindString(int index, String value) indexの場所をvalueで置き換える。
bindBlob(int index, byte[] value) indexの場所をvalueで置き換える。

最後に、SQLiteStatement#executeInsertメソッドを確認する。定義は以下となる。

public long executeInsert()

引数はなく、INSERT文が実行される。executeメソッドだとINSERTだけでなく、UPDATE、DELETEも実行できる。executeInsertを使用すると戻り値で自動生成されたシーケンス値が取得できる。ログにキー項目を出力したり、同一キーで関連テーブルを更新する場合等に使用できる。