Skip to content

Latest commit

 

History

History
496 lines (380 loc) · 18.7 KB

README_jp.md

File metadata and controls

496 lines (380 loc) · 18.7 KB

Ktorm

Build Status Maven Central Apache License 2 Awesome Kotlin Badge

What's Ktorm?

Ktormは純粋なJDBCをベースにしたKotlin用の軽量で効率的なORMフレームワークです。強力に型付けされた柔軟性の高い SQL DSL と便利なシーケンス API を提供し、データベース操作の重複作業を軽減してくれます。もちろん、すべてのSQL文は自動的に生成されます。Ktormはオープンソースで、Apache 2.0ライセンスで提供されています。このライブラリが役に立ったならば、Starをつけてください!

詳細なドキュメントについては、私たちのサイトを参照してください。: https://www.ktorm.org.

🇺🇸 English | 🇨🇳 简体中文 | 🇯🇵 日本語

特徴

  • 設定ファイル、XML、アノテーション、サードパーティの依存関係もなく、軽量で使いやすい。
  • 強力に型付けされた SQL DSL、コンパイル時に低レベルのバグを暴き出す。
  • 柔軟なクエリ、生成されたSQLを思いのままに細かく制御。
  • エンティティシーケンスAPI、filter, map, sortedBy などのシーケンス関数を使って、Kotlin のネイティブなコレクションやシーケンスを使うのと同じようにクエリを書くことができます。
  • 拡張性のある設計。独自の拡張機能を書いて、より多くの演算子、データ型、SQL関数、データベースの方言などをサポートすることができます。

クイックスタート

Ktormはmaven centralにデプロイされているので、mavenを使っている場合は pom.xml ファイルに依存関係を追加するだけです。

<dependency>
    <groupId>org.ktorm</groupId>
    <artifactId>ktorm-core</artifactId>
    <version>${ktorm.version}</version>
</dependency>

Gradleの場合:

compile "org.ktorm:ktorm-core:${ktorm.version}"

第一に、テーブルスキーマを記述するためのKotlinオブジェクトを作成します。

object Departments : Table<Nothing>("t_department") {
    val id = int("id").primaryKey()
    val name = varchar("name")
    val location = varchar("location")
}

object Employees : Table<Nothing>("t_employee") {
    val id = int("id").primaryKey()
    val name = varchar("name")
    val job = varchar("job")
    val managerId = int("manager_id")
    val hireDate = date("hire_date")
    val salary = long("salary")
    val departmentId = int("department_id")
}

そして、データベースに接続して、簡単なクエリを書きます。

fun main() {
    val database = Database.connect("jdbc:mysql://localhost:3306/ktorm", user = "root", password = "***")

    for (row in database.from(Employees).select()) {
        println(row[Employees.name])
    }
}

このプログラムを実行すると、Ktormは select * from t_employee というSQL文を生成し、テーブル内のすべての従業員を選択して名前を表示します。select 関数が返すクエリオブジェクトは反復演算子をオーバーロードしているので、for-eachループを使うことができます。

SQL DSL

クエリにいくつかフィルタ条件を追加してみましょう。

database
    .from(Employees)
    .select(Employees.name)
    .where { (Employees.departmentId eq 1) and (Employees.name like "%vince%") }
    .forEach { row -> 
        println(row[Employees.name])
    }

生成される SQL文:

select t_employee.name as t_employee_name 
from t_employee 
where (t_employee.department_id = ?) and (t_employee.name like ?) 

Ktormを使ってクエリを書くのは簡単で自然です。生成されたSQLは元のKotlinのコードに正確に対応しています。さらに、強力な型付けがされており、実行前にコンパイラがあなたのコードをチェックし、IDEのintelligent senseとコード補完の恩恵を受けることができます。

状況に応じて異なるフィルタ条件を適用する動的クエリ:

val query = database
    .from(Employees)
    .select(Employees.name)
    .whereWithConditions {
        if (someCondition) {
            it += Employees.managerId.isNull()
        }
        if (otherCondition) {
            it += Employees.departmentId eq 1
        }
    }

集計:

val t = Employees.aliased("t")
database
    .from(t)
    .select(t.departmentId, avg(t.salary))
    .groupBy(t.departmentId)
    .having { avg(t.salary) gt 100.0 }
    .forEach { row -> 
        println("${row.getInt(1)}:${row.getDouble(2)}")
    }

Union:

val query = database
    .from(Employees)
    .select(Employees.id)
    .unionAll(
        database.from(Departments).select(Departments.id)
    )
    .unionAll(
        database.from(Departments).select(Departments.id)
    )
    .orderBy(Employees.id.desc())

多数のテーブルを結合する:

data class Names(val name: String?, val managerName: String?, val departmentName: String?)

val emp = Employees.aliased("emp")
val mgr = Employees.aliased("mgr")
val dept = Departments.aliased("dept")

val results = database
    .from(emp)
    .leftJoin(dept, on = emp.departmentId eq dept.id)
    .leftJoin(mgr, on = emp.managerId eq mgr.id)
    .select(emp.name, mgr.name, dept.name)
    .orderBy(emp.id.asc())
    .map { row -> 
        Names(
            name = row[emp.name],
            managerName = row[mgr.name],
            departmentName = row[dept.name]
        )
    }

挿入:

database.insert(Employees) {
    set(it.name, "jerry")
    set(it.job, "trainee")
    set(it.managerId, 1)
    set(it.hireDate, LocalDate.now())
    set(it.salary, 50)
    set(it.departmentId, 1)
}

更新:

database.update(Employees) {
    set(it.job, "engineer")
    set(it.managerId, null)
    set(it.salary, 100)
    where {
        it.id eq 2
    }
}

削除:

database.delete(Employees) { it.id eq 4 }

SQL DSLの詳しい使い方については、詳細ドキュメントを参照してください。

エンティティと列のバインド

SQL DSL に加えて、他の ORM フレームワークと同様にエンティティオブジェクトもサポートされています。まず、エンティティクラスを定義し、それにテーブルオブジェクトをバインドする必要があります。Ktormでは、エンティティクラスは Entity<E> を拡張したインタフェースとして定義されています。

interface Department : Entity<Department> {
    companion object : Entity.Factory<Department>()
    val id: Int
    var name: String
    var location: String
}

interface Employee : Entity<Employee> {
    companion object : Entity.Factory<Employee>()
    val id: Int
    var name: String
    var job: String
    var manager: Employee?
    var hireDate: LocalDate
    var salary: Long
    var department: Department
}

上記のテーブルオブジェクトを変更し、データベースの列をエンティティのプロパティにバインドします。

object Departments : Table<Department>("t_department") {
    val id = int("id").primaryKey().bindTo { it.id }
    val name = varchar("name").bindTo { it.name }
    val location = varchar("location").bindTo { it.location }
}

object Employees : Table<Employee>("t_employee") {
    val id = int("id").primaryKey().bindTo { it.id }
    val name = varchar("name").bindTo { it.name }
    val job = varchar("job").bindTo { it.job }
    val managerId = int("manager_id").bindTo { it.manager.id }
    val hireDate = date("hire_date").bindTo { it.hireDate }
    val salary = long("salary").bindTo { it.salary }
    val departmentId = int("department_id").references(Departments) { it.department }
}

名前付けのコツ:エンティティクラスに単数名詞で名前を付け、テーブルオブジェクトに複数形で名前を付けることを強くお勧めします(例:Employee/Employees、Department/Departments)。

これで列バインディングが設定されたので、エンティティシーケンス APIを使ってエンティティに対して多くの操作を行うことができます。最初に、 Databaseの2つの拡張プロパティを定義します。これは、 sequenceOf関数を使用してシーケンスオブジェクトを作成して返します。 これらの2つの属性は、コードを読みやすくするのに役立ちます。

val Database.departments get() = this.sequenceOf(Departments)
val Database.employees get() = this.sequenceOf(Employees)

次のコードは、 find関数を使用して、名前からシーケンスからEmployeeオブジェクトを取得します。

val employee = database.employees.find { it.name eq "vince" }

また、filter関数でシーケンスをフィルタリングすることもできます。たとえば、名前がvinceであるすべての従業員を取得します。

val employees = database.employees.filter { it.name eq "vince" }.toList()

関数 findfilter はどちらもラムダ式を受け取り、ラムダによって返された条件を持つSELECT SQLを生成します。生成されたSQLは自動的に参照されたテーブル t_department に結合します。

select * 
from t_employee 
left join t_department _ref0 on t_employee.department_id = _ref0.id 
where t_employee.name = ?

エンティティをデータベースに保存します。:

val employee = Employee {
    name = "jerry"
    job = "trainee"
    hireDate = LocalDate.now()
    salary = 50
    department = database.departments.find { it.name eq "tech" }
}

database.employees.add(employee)

メモリ内のプロパティの変更をデータベースにフラッシュする

val employee = database.employees.find { it.id eq 2 } ?: return
employee.job = "engineer"
employee.salary = 100
employee.flushChanges()

データベースからエンティティを削除する:

val employee = database.employees.find { it.id eq 2 } ?: return
employee.delete()

エンティティAPIの詳しい使い方は、column bindingentity queryのドキュメントに記載されています。

エンティティシーケンス API

KtormはEntity Sequenceという名前のAPIセットを提供しており、これを使ってデータベースからエンティティオブジェクトを取得することができます。その名が示すように、そのスタイルや利用パターンはKotlin標準ライブラリのシーケンスAPIと非常によく似ており、filter, map, reduceなどの同じ名前の拡張関数が多数提供されています。

エンティティシーケンスAPIのほとんどは拡張関数として提供されており、それらは中間処理末端処理の2つのグループに分けることができます。

中間処理

これらの関数は,内部クエリを実行するのではなく,いくつかの変更を加えて新たに作成されたシーケンスオブジェクトを返します。たとえば、filter関数はパラメータで与えられたフィルタ条件を持つ新しいシーケンスオブジェクトを生成します。以下のコードは、filterを用いて部門IDが1である全従業員を取得します。

val employees = database.employees.filter { it.departmentId eq 1 }.toList()

使い方は kotlin.sequences とほぼ同じですが、唯一の違いはラムダの ==eq 関数に置き換えられていることです。フィルタ条件はすべて and 演算子と結合されるので、filter 関数は連続して呼び出すこともできます。

val employees = database.employees
    .filter { it.departmentId eq 1 }
    .filter { it.managerId.isNotNull() }
    .toList()

生成される SQL文:

select * 
from t_employee 
left join t_department _ref0 on t_employee.department_id = _ref0.id 
where (t_employee.department_id = ?) and (t_employee.manager_id is not null)

エンティティを順番に並べ替えるには、sortedBysoretdByDescending を使います。:

val employees = database.employees.sortedBy { it.salary }.toList()

ページネーションには droptake を使います。:

val employees = database.employees.drop(1).take(1).toList()

末端処理

エンティティシーケンスの端末操作は、今すぐにクエリを実行し、クエリの結果を取得し、それに対していくつかの計算を行います。for-eachループは典型的な端末操作であり、次のコードはこれを使用して、シーケンス内のすべての従業員を出力します。:

for (employee in database.employees) {
    println(employee)
}

生成される SQL文:

select * 
from t_employee 
left join t_department _ref0 on t_employee.department_id = _ref0.id

toCollection のような関数群( toList, toSet などを含む) は、すべての要素を指定したコレクションに集めるために用いられます。:

val employees = database.employees.toCollection(ArrayList())

カラムの結果を得るには、mapColumns関数を用います。:

val names = database.employees.mapColumns { it.name }

さらに、2つ以上の列を選択する場合は、選択した列をクロージャの tupleOf でラップするだけでよく、関数の戻り値の型は List<TupleN<C1?, C2?, .. Cn?>> になります:

database.employees
    .filter { it.departmentId eq 1 }
    .mapColumns { tupleOf(it.id, it.name) }
    .forEach { (id, name) ->
        println("$id:$name")
    }

生成される SQL文:

select t_employee.id, t_employee.name
from t_employee 
where t_employee.department_id = ?

他にも、fold, reduce, forEach などのおなじみの関数もサポートされています。以下のコードは全従業員の給与総額を計算します。:

val totalSalary = database.employees.fold(0L) { acc, employee -> acc + employee.salary }

シーケンスの集約

エンティティシーケンスAPIは、kotlin.sequenceを使うのと同じようにデータベースからエンティティを取得できるだけでなく、集約関数のサポートも充実しているので、列の数を数えたり、合計したり、平均値を計算したりするのに便利です。

以下のコードは、第1部門の最大給与を取得します。:

val max = database.employees
    .filter { it.departmentId eq 1 }
    .aggregateColumns { max(it.salary) }

また、2つ以上の列を集計する場合は、クロージャー内の tupleOf で集計式をラップするだけでよく、関数の戻り値の型は TupleN<C1?, C2?, .. Cn?> になります。 以下の例では、部門1の給与の平均と範囲を取得しています。

val (avg, diff) = database.employees
    .filter { it.departmentId eq 1 }
    .aggregateColumns { tupleOf(avg(it.salary), max(it.salary) - min(it.salary)) }

生成される SQL文:

select avg(t_employee.salary), max(t_employee.salary) - min(t_employee.salary) 
from t_employee 
where t_employee.department_id = ?

Ktormは、aggregateColumns に基づいて実装された多くの便利なヘルパー関数を提供しています。(count, any, none, all, sumBy, maxBy, minBy, averageBy

以下のコードでは、第1部門の給与の最大値を maxBy関数を用いて取得しています。:

val max = database.employees
    .filter { it.departmentId eq 1 }
    .maxBy { it.salary }

さらに、グループ化された集約もサポートされているので、aggregateColumns を呼び出す前に groupingBy を呼び出すだけで良いのです。以下のコードは、各部門の平均給与を取得するものです。ここでは、結果の型は Map<Int?, Double?> であり、キーは部門のID、値は部門の平均給与額です。:

val averageSalaries = database.employees
    .groupingBy { it.departmentId }
    .aggregateColumns { avg(it.salary) }

生成される SQL文:

select t_employee.department_id, avg(t_employee.salary) 
from t_employee 
group by t_employee.department_id

Ktormはまた、集計をグループ化するための便利なヘルパー関数を多数提供しています(eachCount(To), eachSumBy(To), eachMaxBy(To), eachMinBy(To), eachAverageBy(To))。これらの関数を使って、以下のコードを書けば、各部門の平均給与を求めることができます。:

val averageSalaries = database.employees
    .groupingBy { it.departmentId }
    .eachAverageBy { it.salary }

他にも aggregate, fold, reduce などのおなじみの関数がサポートされています。これらの関数は kotlin.collections.Grouping の拡張関数と同じ名前で、使い方も全く同じです。以下のコードは fold を用いて各部門の給与総額を計算しています。:

val totalSalaries = database.employees
    .groupingBy { it.departmentId }
    .fold(0L) { acc, employee -> 
        acc + employee.salary 
    }

エンティティシーケンスAPIの詳しい使い方は、entity sequencesequence aggregationのドキュメントに記載されています。