{"id":13428650,"url":"https://github.com/kotlin-orm/ktorm","last_synced_at":"2025-05-13T20:21:57.197Z","repository":{"id":38389230,"uuid":"159785192","full_name":"kotlin-orm/ktorm","owner":"kotlin-orm","description":"A lightweight ORM framework for Kotlin with strong-typed SQL DSL and sequence APIs.","archived":false,"fork":false,"pushed_at":"2025-01-19T15:18:54.000Z","size":100591,"stargazers_count":2223,"open_issues_count":102,"forks_count":153,"subscribers_count":30,"default_branch":"master","last_synced_at":"2025-04-28T11:55:11.012Z","etag":null,"topics":["kotlin","ktorm","orm","sql"],"latest_commit_sha":null,"homepage":"https://www.ktorm.org","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kotlin-orm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["https://www.ktorm.org/en/sponsor.html"]}},"created_at":"2018-11-30T07:28:36.000Z","updated_at":"2025-04-25T18:33:26.000Z","dependencies_parsed_at":"2024-05-05T04:19:07.651Z","dependency_job_id":"2db01c74-dc75-4461-8650-df0c7d0eb05c","html_url":"https://github.com/kotlin-orm/ktorm","commit_stats":{"total_commits":1343,"total_committers":36,"mean_commits":37.30555555555556,"dds":"0.12509307520476542","last_synced_commit":"601d9a11f45f33bb3d8f637162f6650236a84e89"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kotlin-orm%2Fktorm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kotlin-orm%2Fktorm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kotlin-orm%2Fktorm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kotlin-orm%2Fktorm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kotlin-orm","download_url":"https://codeload.github.com/kotlin-orm/ktorm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251311332,"owners_count":21569008,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","ktorm","orm","sql"],"created_at":"2024-07-31T01:01:02.239Z","updated_at":"2025-04-28T11:55:30.339Z","avatar_url":"https://github.com/kotlin-orm.png","language":"Kotlin","readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/kotlin-orm/ktorm-docs/master/source/images/logo-full.png\" alt=\"Ktorm\" width=\"300\" /\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/kotlin-orm/ktorm/actions/workflows/build.yml\"\u003e\n        \u003cimg src=\"https://github.com/kotlin-orm/ktorm/actions/workflows/build.yml/badge.svg\" alt=\"Build Status\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://search.maven.org/search?q=g:%22org.ktorm%22\"\u003e\n        \u003cimg src=\"https://img.shields.io/maven-central/v/org.ktorm/ktorm-core.svg?label=Maven%20Central\" alt=\"Maven Central\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"LICENSE\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/license-Apache%202-blue.svg?maxAge=2592000\" alt=\"Apache License 2\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/KotlinBy/awesome-kotlin\"\u003e\n        \u003cimg src=\"https://kotlin.link/awesome-kotlin.svg\" alt=\"Awesome Kotlin Badge\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\n# What's Ktorm?\n\nKtorm is a lightweight and efficient ORM Framework for Kotlin directly based on pure JDBC. It provides strong-typed and flexible SQL DSL and convenient sequence APIs to reduce our duplicated effort on database operations. All the SQL statements, of course, are generated automatically. Ktorm is open source and available under the Apache 2.0 license. Please leave a star if you've found this library helpful! \n\nFor more documentation, go to our site: [https://www.ktorm.org](https://www.ktorm.org).\n\n:us: English | :cn: [简体中文](README_cn.md) | :jp: [日本語](README_jp.md)\n\n# Features\n\n - No configuration files, no XML, no annotations, even no third-party dependencies, lightweight, easy to use.\n - Strong typed SQL DSL, exposing low-level bugs at compile time.\n - Flexible queries, fine-grained control over the generated SQLs as you wish.\n - Entity sequence APIs, writing queries via sequence functions such as `filter`, `map`, `sortedBy`, etc., just like using Kotlin's native collections and sequences. \n - Extensible design, write your own extensions to support more operators, data types, SQL functions, database dialects, etc.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/kotlin-orm/ktorm-docs/master/source/images/ktorm-example.png\"\u003e\n\u003c/p\u003e\n\n# Quick Start\n\nKtorm was deployed to maven central, so you just need to add a dependency to your `pom.xml` file if you are using maven: \n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.ktorm\u003c/groupId\u003e\n    \u003cartifactId\u003ektorm-core\u003c/artifactId\u003e\n    \u003cversion\u003e${ktorm.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nOr Gradle: \n\n```groovy\ncompile \"org.ktorm:ktorm-core:${ktorm.version}\"\n```\n\nFirstly, create Kotlin objects to [describe your table schemas](https://www.ktorm.org/en/schema-definition.html): \n\n```kotlin\nobject Departments : Table\u003cNothing\u003e(\"t_department\") {\n    val id = int(\"id\").primaryKey()\n    val name = varchar(\"name\")\n    val location = varchar(\"location\")\n}\n\nobject Employees : Table\u003cNothing\u003e(\"t_employee\") {\n    val id = int(\"id\").primaryKey()\n    val name = varchar(\"name\")\n    val job = varchar(\"job\")\n    val managerId = int(\"manager_id\")\n    val hireDate = date(\"hire_date\")\n    val salary = long(\"salary\")\n    val departmentId = int(\"department_id\")\n}\n```\n\nThen, connect to your database and write a simple query: \n\n```kotlin\nfun main() {\n    val database = Database.connect(\"jdbc:mysql://localhost:3306/ktorm\", user = \"root\", password = \"***\")\n\n    for (row in database.from(Employees).select()) {\n        println(row[Employees.name])\n    }\n}\n```\n\nNow you can run this program, Ktorm will generate a SQL `select * from t_employee`, selecting all employees in the table and printing their names. You can use the for-each loop here because the query object returned by the `select` function overloads the iteration operator. \n\n## SQL DSL\n\nLet's add some filter conditions to the query: \n\n```kotlin\ndatabase\n    .from(Employees)\n    .select(Employees.name)\n    .where { (Employees.departmentId eq 1) and (Employees.name like \"%vince%\") }\n    .forEach { row -\u003e \n        println(row[Employees.name])\n    }\n```\n\nGenerated SQL: \n\n```sql\nselect t_employee.name as t_employee_name \nfrom t_employee \nwhere (t_employee.department_id = ?) and (t_employee.name like ?) \n```\n\nThat's the magic of Kotlin, writing a query with Ktorm is easy and natural, the generated SQL is exactly corresponding to the origin Kotlin code. And moreover, it's strong-typed, the compiler will check your code before it runs, and you will be benefited from the IDE's intelligent sense and code completion.\n\nDynamic query that will apply different filter conditions in different situations: \n\n```kotlin\nval query = database\n    .from(Employees)\n    .select(Employees.name)\n    .whereWithConditions {\n        if (someCondition) {\n            it += Employees.managerId.isNull()\n        }\n        if (otherCondition) {\n            it += Employees.departmentId eq 1\n        }\n    }\n```\n\nAggregation: \n\n```kotlin\nval t = Employees.aliased(\"t\")\ndatabase\n    .from(t)\n    .select(t.departmentId, avg(t.salary))\n    .groupBy(t.departmentId)\n    .having { avg(t.salary) gt 100.0 }\n    .forEach { row -\u003e \n        println(\"${row.getInt(1)}:${row.getDouble(2)}\")\n    }\n```\n\nUnion: \n\n```kotlin\nval query = database\n    .from(Employees)\n    .select(Employees.id)\n    .unionAll(\n        database.from(Departments).select(Departments.id)\n    )\n    .unionAll(\n        database.from(Departments).select(Departments.id)\n    )\n    .orderBy(Employees.id.desc())\n```\n\nJoining: \n\n```kotlin\ndata class Names(val name: String?, val managerName: String?, val departmentName: String?)\n\nval emp = Employees.aliased(\"emp\")\nval mgr = Employees.aliased(\"mgr\")\nval dept = Departments.aliased(\"dept\")\n\nval results = database\n    .from(emp)\n    .leftJoin(dept, on = emp.departmentId eq dept.id)\n    .leftJoin(mgr, on = emp.managerId eq mgr.id)\n    .select(emp.name, mgr.name, dept.name)\n    .orderBy(emp.id.asc())\n    .map { row -\u003e \n        Names(\n            name = row[emp.name],\n            managerName = row[mgr.name],\n            departmentName = row[dept.name]\n        )\n    }\n```\n\nInsert: \n\n```kotlin\ndatabase.insert(Employees) {\n    set(it.name, \"jerry\")\n    set(it.job, \"trainee\")\n    set(it.managerId, 1)\n    set(it.hireDate, LocalDate.now())\n    set(it.salary, 50)\n    set(it.departmentId, 1)\n}\n```\n\nUpdate: \n\n```kotlin\ndatabase.update(Employees) {\n    set(it.job, \"engineer\")\n    set(it.managerId, null)\n    set(it.salary, 100)\n    where {\n        it.id eq 2\n    }\n}\n```\n\nDelete: \n\n```kotlin\ndatabase.delete(Employees) { it.id eq 4 }\n```\n\nRefer to [detailed documentation](https://www.ktorm.org/en/query.html) for more usages about SQL DSL.\n\n## Entities and Column Binding\n\nIn addition to SQL DSL, entity objects are also supported just like other ORM frameworks do. We need to define entity classes firstly and bind table objects to them. In Ktorm, entity classes are defined as interfaces extending from `Entity\u003cE\u003e`: \n\n```kotlin\ninterface Department : Entity\u003cDepartment\u003e {\n    companion object : Entity.Factory\u003cDepartment\u003e()\n    val id: Int\n    var name: String\n    var location: String\n}\n\ninterface Employee : Entity\u003cEmployee\u003e {\n    companion object : Entity.Factory\u003cEmployee\u003e()\n    val id: Int\n    var name: String\n    var job: String\n    var manager: Employee?\n    var hireDate: LocalDate\n    var salary: Long\n    var department: Department\n}\n```\n\nModify the table objects above, binding database columns to entity properties:  \n\n```kotlin\nobject Departments : Table\u003cDepartment\u003e(\"t_department\") {\n    val id = int(\"id\").primaryKey().bindTo { it.id }\n    val name = varchar(\"name\").bindTo { it.name }\n    val location = varchar(\"location\").bindTo { it.location }\n}\n\nobject Employees : Table\u003cEmployee\u003e(\"t_employee\") {\n    val id = int(\"id\").primaryKey().bindTo { it.id }\n    val name = varchar(\"name\").bindTo { it.name }\n    val job = varchar(\"job\").bindTo { it.job }\n    val managerId = int(\"manager_id\").bindTo { it.manager.id }\n    val hireDate = date(\"hire_date\").bindTo { it.hireDate }\n    val salary = long(\"salary\").bindTo { it.salary }\n    val departmentId = int(\"department_id\").references(Departments) { it.department }\n}\n```\n\n\u003e Naming Strategy: It's highly recommended to name your entity classes by singular nouns, name table objects by plurals (e.g. Employee/Employees, Department/Departments). \n\nNow that column bindings are configured, so we can use [sequence APIs](#Entity-Sequence-APIs) to perform many operations on entities. Let's add two extension properties for `Database` first. These properties return new created sequence objects via `sequenceOf` and they can help us improve the readability of the code: \n\n```kotlin\nval Database.departments get() = this.sequenceOf(Departments)\nval Database.employees get() = this.sequenceOf(Employees)\n```\n\nThe following code uses the `find` function to obtain an employee by its name: \n\n```kotlin\nval employee = database.employees.find { it.name eq \"vince\" }\n```\n\nWe can also filter the sequence by the function `filter`. For example, obtaining all the employees whose names are vince: \n\n```kotlin\nval employees = database.employees.filter { it.name eq \"vince\" }.toList()\n```\n\nThe `find` and `filter` functions both accept a lambda expression, generating a select sql with the condition returned by the lambda. The generated SQL auto left joins the referenced table `t_department`: \n\n```sql\nselect * \nfrom t_employee \nleft join t_department _ref0 on t_employee.department_id = _ref0.id \nwhere t_employee.name = ?\n```\n\nSave entities to database: \n\n```kotlin\nval employee = Employee {\n    name = \"jerry\"\n    job = \"trainee\"\n    hireDate = LocalDate.now()\n    salary = 50\n    department = database.departments.find { it.name eq \"tech\" }\n}\n\ndatabase.employees.add(employee)\n```\n\nFlush property changes in memory to database: \n\n```kotlin\nval employee = database.employees.find { it.id eq 2 } ?: return\nemployee.job = \"engineer\"\nemployee.salary = 100\nemployee.flushChanges()\n```\n\nDelete an entity from database: \n\n```kotlin\nval employee = database.employees.find { it.id eq 2 } ?: return\nemployee.delete()\n```\n\nDetailed usages of entity APIs can be found in the documentation of [column binding](https://www.ktorm.org/en/entities-and-column-binding.html) and [entity query](https://www.ktorm.org/en/entity-finding.html).\n\n## Entity Sequence APIs\n\nKtorm provides a set of APIs named *Entity Sequence*, which can be used to obtain entity objects from databases. As the name implies, its style and use pattern are highly similar to the sequence APIs in Kotlin standard lib, as it provides many extension functions with the same names, such as `filter`, `map`, `reduce`, etc.\n\nMost of the entity sequence APIs are provided as extension functions, which can be divided into two groups, they are intermediate operations and terminal operations. \n\n### Intermediate Operations\n\nThese functions don’t execute the internal queries but return new-created sequence objects applying some modifications. For example, the `filter` function creates a new sequence object with the filter condition given by its parameter. The following code obtains all the employees in department 1 by using `filter`:\n\n```kotlin\nval employees = database.employees.filter { it.departmentId eq 1 }.toList()\n```\n\nWe can see that the usage is almost the same as `kotlin.sequences`, the only difference is the `==` in the lambda is replaced by the `eq` function. The `filter` function can also be called continuously, as all the filter conditions are combined with the `and` operator. \n\n```kotlin\nval employees = database.employees\n    .filter { it.departmentId eq 1 }\n    .filter { it.managerId.isNotNull() }\n    .toList()\n```\n\nGenerated SQL: \n\n```sql\nselect * \nfrom t_employee \nleft join t_department _ref0 on t_employee.department_id = _ref0.id \nwhere (t_employee.department_id = ?) and (t_employee.manager_id is not null)\n```\n\nUse `sortedBy` or `soretdByDescending` to sort entities in a sequence: \n\n```kotlin\nval employees = database.employees.sortedBy { it.salary }.toList()\n```\n\nUse `drop` and `take` for pagination: \n\n```kotlin\nval employees = database.employees.drop(1).take(1).toList()\n```\n\n### Terminal Operations\n\nTerminal operations of entity sequences execute the queries right now, then obtain the query results and perform some calculations on them. The for-each loop is a typical terminal operation, and the following code uses it to print all employees in the sequence: \n\n```kotlin\nfor (employee in database.employees) {\n    println(employee)\n}\n```\n\nGenerated SQL: \n\n```sql\nselect * \nfrom t_employee \nleft join t_department _ref0 on t_employee.department_id = _ref0.id\n```\n\nThe `toCollection` functions (including `toList`, `toSet`, etc.) are used to collect all the elements into a collection: \n\n```kotlin\nval employees = database.employees.toCollection(ArrayList())\n```\n\nThe `mapColumns` function is used to obtain the results of a column: \n\n```kotlin\nval names = database.employees.mapColumns { it.name }\n```\n\nAdditionally, if we want to select two or more columns, we just need to wrap our selected columns by `tupleOf` in the closure, and the function’s return type becomes `List\u003cTupleN\u003cC1?, C2?, .. Cn?\u003e\u003e`. \n\n```kotlin\ndatabase.employees\n    .filter { it.departmentId eq 1 }\n    .mapColumns { tupleOf(it.id, it.name) }\n    .forEach { (id, name) -\u003e\n        println(\"$id:$name\")\n    }\n```\n\nGenerated SQL: \n\n```sql\nselect t_employee.id, t_employee.name\nfrom t_employee \nwhere t_employee.department_id = ?\n```\n\nOther familiar functions are also supported, such as `fold`, `reduce`, `forEach`, etc. The following code calculates the total salary of all employees: \n\n```kotlin\nval totalSalary = database.employees.fold(0L) { acc, employee -\u003e acc + employee.salary }\n```\n\n### Sequence Aggregation\n\nThe entity sequence APIs not only allow us to obtain entities from databases just like using `kotlin.sequences`, but they also provide rich support for aggregations, so we can conveniently count the columns, sum them, or calculate their averages, etc.\n\nThe following code obtains the max salary in department 1:\n\n```kotlin\nval max = database.employees\n    .filter { it.departmentId eq 1 }\n    .aggregateColumns { max(it.salary) }\n```\n\nAlso, if we want to aggregate two or more columns, we just need to wrap our aggregate expressions by `tupleOf` in the closure, and the function’s return type becomes `TupleN\u003cC1?, C2?, .. Cn?\u003e`. The example below obtains the average and the range of salaries in department 1: \n\n```kotlin\nval (avg, diff) = database.employees\n    .filter { it.departmentId eq 1 }\n    .aggregateColumns { tupleOf(avg(it.salary), max(it.salary) - min(it.salary)) }\n```\n\nGenerated SQL: \n\n```sql\nselect avg(t_employee.salary), max(t_employee.salary) - min(t_employee.salary) \nfrom t_employee \nwhere t_employee.department_id = ?\n```\n\nKtorm also provides many convenient helper functions implemented based on `aggregateColumns`, they are `count`, `any`, `none`, `all`, `sumBy`, `maxBy`, `minBy`, `averageBy`. \n\nThe following code obtains the max salary in department 1 using `maxBy` instead: \n\n```kotlin\nval max = database.employees\n    .filter { it.departmentId eq 1 }\n    .maxBy { it.salary }\n```\n\nAdditionally, grouping aggregations are also supported, we just need to call `groupingBy` before calling `aggregateColumns`. The following code obtains the average salaries for each department. Here, the result's type is `Map\u003cInt?, Double?\u003e`, in which the keys are departments' IDs, and the values are the average salaries of the departments. \n\n```kotlin\nval averageSalaries = database.employees\n    .groupingBy { it.departmentId }\n    .aggregateColumns { avg(it.salary) }\n```\n\nGenerated SQL: \n\n```sql\nselect t_employee.department_id, avg(t_employee.salary) \nfrom t_employee \ngroup by t_employee.department_id\n```\n\nKtorm also provides many convenient helper functions for grouping aggregations, they are `eachCount(To)`, `eachSumBy(To)`, `eachMaxBy(To)`, `eachMinBy(To)`, `eachAverageBy(To)`. With these functions, we can write the code below to obtain average salaries for each department: \n\n```kotlin\nval averageSalaries = database.employees\n    .groupingBy { it.departmentId }\n    .eachAverageBy { it.salary }\n```\n\nOther familiar functions are also supported, such as `aggregate`, `fold`, `reduce`, etc. They have the same names as the extension functions of `kotlin.collections.Grouping`, and the usages are totally the same. The following code calculates the total salaries for each department using `fold`:\n\n```kotlin\nval totalSalaries = database.employees\n    .groupingBy { it.departmentId }\n    .fold(0L) { acc, employee -\u003e \n        acc + employee.salary \n    }\n```\n\nDetailed usages of entity sequence APIs can be found in the documentation of [entity sequence](https://www.ktorm.org/en/entity-sequence.html) and [sequence aggregation](https://www.ktorm.org/en/sequence-aggregation.html). \n","funding_links":["https://www.ktorm.org/en/sponsor.html"],"categories":["Libraries","Kotlin","数据库开发"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkotlin-orm%2Fktorm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkotlin-orm%2Fktorm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkotlin-orm%2Fktorm/lists"}