0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQLの自由度を残したAndroid向けSQLite ORM「AndrORM」を作りました【Kotlin / KSP】

0
Last updated at Posted at 2026-08-28

AndroidでSQLiteを扱うとき、Roomは非常に有力な選択肢です。

一方で、

  • SQLの構造は自分で意識して組み立てたい
  • 動的な検索条件をKotlinコードで構築したい
  • JOIN、サブクエリ、HAVINGなどを柔軟に扱いたい
  • SQL文字列の連結やバインド値の管理は共通化したい
  • Entityの定義やCursorからの変換処理はできるだけ省力化したい

という開発スタイルもあると思います。

そこで、**SQLの自由度を残しながら、Kotlinのdata classとKSPでSQLite開発を支援するAndroid向けORM「AndrORM」**を開発しています。

この記事では、AndrORMがどのようなライブラリなのか、Roomと何が違うのか、どのように使うのかを紹介します。

AndrORMは現在アルファ版です。
この記事は0.1.5-alphaを前提としています。
今後、APIや仕様が変更される可能性があります。

AndrORMとは

AndrORMは、Android/Kotlin向けのSQLite ORMです。

SQLを隠蔽することを目的としたORMではありません。

開発者がSQLiteやSQLの構造を意識しながら、

  • SQLの組み立て
  • バインド値の管理
  • Entity定義
  • KSPによる用途別Entity生成
  • SQLiteテーブル作成
  • SELECT結果からEntityへの変換
  • データベース移行
  • トランザクション
  • SAVEPOINT
  • インメモリSQLiteデータベース
  • Detektによる静的検証

といった処理を支援します。

例えば、SELECTは次のようにKotlinコードで組み立てます。

val select = Select(ProductSelect::class)
    .where {
        ProductSelect::enabled eq true
    }
    .order {
        ProductSelect::productCode.asc
    }

生成されたSQLはbuild()で取得でき、バインド値も別途参照できます。

val sql = select.build()
val bindValues = select.bindValues

「SQLを見えなくする」のではなく、SQLを意識したまま、文字列連結や定型処理を減らすことを重視しています。

Roomとの違い

RoomとAndrORMは、どちらもAndroidでSQLite開発を支援するためのものですが、考え方が異なります。

項目 AndrORM Room
クエリの定義 KotlinによるSQLビルダー @QueryなどにSQLを記述
動的な検索条件 Kotlinコードとして段階的に組み立てる DAOやクエリを用途ごとに構成
SQLの扱い SQLiteの構造を意識して利用 DAOを中心に構成
Entity 手作業またはKSPで生成 アノテーションを付けて定義
用途別Entity KSPでSELECT・DML用途別に生成可能 EntityやDTOを個別に定義
DB移行 テーブル再構築とデータ移行を支援 Migrationを定義
SAVEPOINT 専用APIを提供 主にトランザクション単位
静的検証 AndrORM専用Detektルール Annotation Processor/KSPによる検証

AndrORMは、Roomそのものを置き換えることを目的としていません。

Roomの設計が適しているプロジェクトではRoomを使用し、SQLの構造やSQLite固有の動作をより直接的に管理したい場合の選択肢としてAndrORMを開発しています。

どんな開発者を想定しているか

例えば、次のような場合を想定しています。

  • SQLの知識を活かしてSQLiteを扱いたい
  • 動的な検索条件をKotlinコードで構築したい
  • JOINやサブクエリを柔軟に利用したい
  • SQL文字列とバインド値の管理を共通化したい
  • テーブル定義をKotlinのdata classへ集約したい
  • SELECT/INSERT/UPDATEなど、用途別にEntityを分けたい
  • DBのバージョンアップ時にテーブル再構築やデータ移行を行いたい
  • トランザクション内でSAVEPOINTによる部分ロールバックを利用したい
  • SQLビルダーの参照ミスを静的解析で検出したい

逆に、SQLをほとんど意識せず、DAO中心で開発したい場合はRoomの方が適していると思います。

主な機能

現在、主に次の機能を実装しています。

  • Kotlinのdata classによるテーブル定義
  • KSPによるSELECT/INSERT/UPDATEなどの用途別Entity生成
  • Entityの手書き定義にも対応
  • SELECT/INSERT/UPDATE/DELETE/UPSERT/ABSERT
  • INNER JOIN/LEFT JOIN/CROSS JOIN/NATURAL JOIN
  • WHERE/HAVING/ORDER BY
  • 集約関数またはhaving指定時のGROUP BY自動生成
  • LIMIT/OFFSET
  • サブクエリ
  • EXISTS/NOT EXISTS
  • 集約関数
  • スカラー関数
  • raw条件/raw式
  • SQLとバインド値の一元管理
  • Entity/Map/Cursor形式でのSELECT結果取得
  • Entity定義からSQLiteテーブルを作成
  • DB更新時のテーブル再構築とデータ移行
  • トランザクション
  • SAVEPOINT
  • 独自型とSQLite型の相互変換
  • AndrORM専用Detektルール
ABSERTはAndrORM独自の名称で、SQLiteのINSERT ... ON CONFLICT ... DO NOTHINGを生成します。「Insert if absent」の意味で使用しています。

5~10分で試す

ここからは、最低限の導入方法を紹介します。

1. KSPとDetektを追加

プロジェクトルートのbuild.gradle.ktsへ追加します。

plugins {
    id("com.google.devtools.ksp") version "1.9.24-1.0.20" apply false
    id("io.gitlab.arturbosch.detekt") version "1.23.6" apply false
}

対象モジュールではプラグインを有効にします。

plugins {
    id("com.google.devtools.ksp")
    id("io.gitlab.arturbosch.detekt")
}

2. AndrORMを追加

対象モジュールのbuild.gradle.ktsへ追加します。

dependencies {
    implementation(
        "io.github.kawanagare-git:androrm-runtime:0.1.5-alpha"
    )
    ksp(
        "io.github.kawanagare-git:androrm-generator-ksp:0.1.5-alpha"
    )
    detektPlugins(
        "io.github.kawanagare-git:androrm-detekt-rules:0.1.5-alpha"
    )
}

3. Entityを定義

例として商品テーブルを定義します。

@Projections(
    [
        Projection(
            entityNameExtend = "Select",
            properties = [
                ColumnProjection("productCode"),
                ColumnProjection("productName"),
                ColumnProjection("enabled"),
            ],
            commonInterface = [SELECT],
        ),
        Projection(
            entityNameExtend = "Insert",
            properties = [
                ColumnProjection("productCode"),
                ColumnProjection("productName"),
                ColumnProjection("enabled"),
            ],
            commonInterface = [INSERT],
        ),
    ],
)
@Table(
    name = "PRODUCT",
    alias = "P",
)
data class Product(
    @PrimaryKey
    @Column(name = "PRODUCT_CODE")
    val productCode: String,

    @Column(name = "PRODUCT_NAME")
    val productName: String,

    @Column(
        name = "ENABLED",
        default = "1",
    )
    val enabled: Boolean,
)

@Projectionを使うと、元のEntityから用途別EntityをKSPで生成できます。

上の例では、

Product
├─ ProductSelect
└─ ProductInsert

という用途別Entityを生成して使用できます。

KSPを使わず、必要なアノテーションとマーカーインターフェースを指定してEntityを手書きすることもできます。

4. KSPを実行

Windowsの場合は次のコマンドで生成できます。

.\gradlew.bat :app:kspDebugKotlin

Entity定義や@Projectionを変更し、生成結果を確実に更新したい場合は次のように再実行できます。

.\gradlew.bat :app:kspDebugKotlin --rerun-tasks

5. SELECTを組み立てる

val select = Select(ProductSelect::class)
    .where {
        ProductSelect::enabled eq true
    }
    .order {
        ProductSelect::productCode.asc
    }

SQLとバインド値はそれぞれ取得できます。

val sql = select.build()
val bindValues = select.bindValues

DBヘルパーを利用すると、結果をEntityのリストとして取得できます。

val products =
    helper.executeSelectAsEntityList(select)

MapやCursorで取得するAPIも用意しています。

動的な検索条件をKotlinで組み立てる

AndrORMでは、条件をDSLとして組み立てます。

val select = Select(ProductSelect::class)
    .where { // 以下の条件は AND 条件となります
        ProductSelect::enabled eq true
        ProductSelect::productName like "SAMPLE%"
    }
    .order {
        ProductSelect::productCode.asc
    }
    .limit(50)
    .offset(0)

条件に応じてKotlin側で処理を分けられるため、検索画面などで条件数が増減するケースでもSQLビルダーとして組み立てられます。

UPDATE/DELETEの全件操作は明示指定

誤操作防止のため、WHERE句なしでUPDATEやDELETEを実行する場合は、明示的に全件操作を指定します。

UPDATEの場合:

val update = Update(ProductUpdate::class)
    .set {
        ProductUpdate::enabled assign false
    }
    .updateAll()

DELETEの場合:

val delete = Delete(ProductDelete::class)
    .deleteAll()

意図せずWHERE句を付け忘れたSQLを生成しにくくするための仕様です。

インメモリSQLiteデータベース

0.1.5-alphaでは、AndrOrmDatabaseHelperdatabaseNamenullを指定することで、ファイルへ保存しないインメモリSQLiteデータベースも利用できます。

class AppMemoryDatabaseHelper(
    context: Context,
) : AndrOrmDatabaseHelper(
    context = context,
    databaseName = null,
    version = 1,
    entities = listOf(
        Product::class,
    ),
)

一時的なマスターデータや、アプリ実行中だけ保持したいDBをAndrORMのSQLビルダーやEntity変換と同じ形で扱えます。

SAVEPOINTによる部分ロールバック

AndrORMでは、トランザクションの中でSAVEPOINTを使用できます。

helper.transaction {
    executeDml(firstQuery)

    val secondResult = savepoint("second_process") {
        executeDml(secondQuery)
    }

    if (secondResult.isSuccess) {
        // SAVEPOINT内の処理成功
    } else {
        // secondQueryはSAVEPOINTまでロールバック済み
    }

    executeDml(thirdQuery)
}

SAVEPOINT内の通常の処理例外では、

処理失敗
↓
ROLLBACK TO SAVEPOINT
↓
RELEASE SAVEPOINT
↓
SavepointResultを返す

という流れになります。

トランザクション全体ではなく、一部分だけをロールバックしたい処理を想定しています。

Detektによる静的検証

AndrORMには専用のDetektルールも含めています。

現在、主に次のような問題を検出します。

  • 定義用エンティティでのテーブル名の重複〔@Table(name=......)〕
  • FROM元またはJOIN先に存在しないEntityプロパティの参照
  • FROM元とJOIN先で同一Entityを重複指定しているケース

SQLを実行してから気付くのではなく、できる限り開発時に検出することを狙っています。

サンプルアプリ

AndrORMを実際のAndroidアプリへ組み込んだ在庫管理サンプルも公開しています。

AndrORMSample

サンプルでは、AndrORMだけでなく次の構成も確認できます。

  • Jetpack Compose
  • ViewModel
  • StateFlow
  • SharedFlow
  • Repository
  • AndrORM EntityからUIモデルへの変換
  • 商品/商品分類/入出庫履歴
  • SELECT/INSERT/UPDATE/UPSERT/ABSERT
  • トランザクションによる在庫数と入出庫履歴の整合性確保

「ライブラリ単体のAPI」だけでなく、AndroidアプリのRepository内部へAndrORMを組み込む例として作成しています。

対応環境

この記事で扱っている0.1.5-alphaの主な環境は次のとおりです。

項目 バージョン
Kotlin 1.9.24
Android Gradle Plugin 8.8.0
Gradle Wrapper 8.10.2
JDK/JVM Target 17
compileSdk 34
targetSdk 34
minSdk 24
KSP 1.9.24-1.0.20
Detekt 1.23.6

現在の位置づけ

AndrORMは現在アルファ版です。

実際に利用できる形で公開していますが、今後もAPIや仕様を変更する可能性があります。

Roomを置き換えるためのライブラリというより、

「SQLiteとSQLを自分で意識して扱いたいAndroid開発者に、もう一つの選択肢を提供する」

ことを目標にしています。

実際に触っていただき、

  • APIが分かりにくい
  • セットアップで迷う
  • このSQLが書きにくい
  • この機能が欲しい
  • Roomや他のライブラリと比べてここが気になる

といった点があれば、フィードバックをいただけると助かります。

リンク

ライセンスはMIT Licenseです。

本記事で紹介するAndrORMは、2010年代に公開され、2014年頃まで活動していた同名のAndroid向けORM「Androrm」とは別のプロジェクトです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?