diff --git a/zh_CN/DAO.md b/zh_CN/DAO.md new file mode 100644 index 0000000..d4fb870 --- /dev/null +++ b/zh_CN/DAO.md @@ -0,0 +1,337 @@ +* [概览](#概览) +* [基本的增删改查操作](#基本的增删改查操作) + * [新增](#新增) + * [查询](#查询) + * [排序](#排序sort-order-by) + * [修改](#修改) + * [删除](#删除) +* [关联Referencing](#关联Referencing) + * [多对一many-to-one reference](#多对一many-to-one-reference) + * [Optional reference](#optional-reference) + * [多对多many-to-many reference](#多对多many-to-many-reference) + * [Parent-Child reference](#parent-child-reference) + * [Eager Loading](#eager-loading) +* [高级的增删改查操作](#高级的增删改查操作) + * [Read entity with a join to another table](#read-entity-with-a-join-to-another-table) + * [Auto-fill created and updated columns on entity change](#auto-fill-created-and-updated-columns-on-entity-change) +* [实体映射mapping](#实体映射mapping) + * [Fields transformation](#fields-transformation) +*** +## 概览 +Exposed 的 DAO(数据访问对象)API,类似于带有 Kotlin 特定 API 的 Hibernate 等 ORM 框架. +一个数据库table表, 由继承自 `org.jetbrains.exposed.sql.Table` 的一个 `object`表示, 如下所示: +```kotlin +object StarWarsFilms : Table() { + val id: Column = integer("id").autoIncrement() + val sequelId: Column = integer("sequel_id").uniqueIndex() + val name: Column = varchar("name", 50) + val director: Column = varchar("director", 50) + override val primaryKey = PrimaryKey(id, name = "PK_StarWarsFilms_Id") // PK_StarWarsFilms_Id is optional here +} +``` +包含 `Int` 类型 id 字段的表, 可以这样声明: +```kotlin +object StarWarsFilms : IntIdTable() { + val sequelId: Column = integer("sequel_id").uniqueIndex() + val name: Column = varchar("name", 50) + val director: Column = varchar("director", 50) +} +``` +请注意,以下这些 Column 类型将自动定义,因此您也可以去掉对它们的定义。产生与上面示例相同的结果: +```kotlin +object StarWarsFilms : IntIdTable() { + val sequelId = integer("sequel_id").uniqueIndex() + val name = varchar("name", 50) + val director = varchar("director", 50) +} +``` +一个实体实例或表中的一行row被定义为一个类实例: + ```kotlin +class StarWarsFilm(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(StarWarsFilms) + var sequelId by StarWarsFilms.sequelId + var name by StarWarsFilms.name + var director by StarWarsFilms.director +} +``` +## 基本的增删改查操作 +### 新增 +```kotlin +val movie = StarWarsFilm.new { + name = "The Last Jedi" + sequelId = 8 + director = "Rian Johnson" +} +``` +### 查询 +要获取实体entities,请使用以下方法之一 +```kotlin +val movies = StarWarsFilm.all() +val movies = StarWarsFilm.find { StarWarsFilms.sequelId eq 8 } +val movie = StarWarsFilm.findById(5) +``` +* 有关可用谓词的列表,请参见 [DSL Where expression](https://github.com/JetBrains/Exposed/wiki/DSL#where-expression). + 从类似于 Kotlin 类中的任何属性的属性中读取值: +```kotlin +val name = movie.name +``` +#### 排序sort-order-by +升序: +```kotlin +val movies = StarWarsFilm.all().sortedBy { it.sequelId } +``` +降序: +```kotlin +val movies = StarWarsFilm.all().sortedByDescending{ it.sequelId } +``` +### 修改 +更新类似于 Kotlin 类中任何属性的属性值: +```kotlin +movie.name = "Episode VIII – The Last Jedi" +``` +* 注意:当您为 Entity 设置新值时,Exposed 不会立即更新,它只是将其存储在内部映射中。向数据库“刷新Flushing”值发生在事务结束时或从数据库中的下一个“select *”之前. +### 删除 +```kotlin +movie.delete() +``` +## 关联Referencing +### 多对一many-to-one reference +假如您有一张表: +```kotlin +object Users: IntIdTable() { + val name = varchar("name", 50) +} +class User(id: EntityID): IntEntity(id) { + companion object : IntEntityClass(Users) + var name by Users.name +} +``` +现在您要添加一个reference 来引用当前表(和其他表!): +```kotlin +object UserRatings: IntIdTable() { + val value = long("value") + val film = reference("film", StarWarsFilms) + val user = reference("user", Users) +} +class UserRating(id: EntityID): IntEntity(id) { + companion object : IntEntityClass(UserRatings) + var value by UserRatings.value + var film by StarWarsFilm referencedOn UserRatings.film // use referencedOn for normal references + var user by User referencedOn UserRatings.user +} +``` +现在您可以像获取其他字段属性一样获取电影的评分rating: +```kotlin +filmRating.film // returns a StarWarsFilm object +``` +现在如果您想获取一部电影的所有评分rating, 您可以使用 `FilmRating.find` 函数来实现, 但是更简单的是可以在 StarWarsFilm类中添加一个 `referrersOn` 字段属性: +```kotlin +class StarWarsFilm(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(StarWarsFilms) + ... + val ratings by UserRating referrersOn UserRatings.film // make sure to use val and referrersOn + ... +} +``` +你可以这样调用: +```kotlin +movie.ratings // returns all UserRating objects with this movie as film +``` +### Optional reference +您也可以添加一个 optional reference: +```kotlin +object UserRatings: IntIdTable() { + ... + val secondUser = reference("second_user", Users).nullable() // this reference is nullable! + ... +} +class UserRating(id: EntityID): IntEntity(id) { + companion object : IntEntityClass(UserRatings) + ... + var secondUser by User optionalReferencedOn UserRatings.secondUser // use optionalReferencedOn for nullable references + ... +} +``` +现在 `secondUser` 将是一个可空字段, 你应该使用 `optionalReferrersOn` 而不是 `referrersOn` 来获取 `secondUser` 的所有评分rating. + +```kotlin +class User(id: EntityID): IntEntity(id) { + companion object : IntEntityClass(Users) + ... + val secondRatings by UserRating optionalReferrersOn UserRatings.secondUser // make sure to use val and optionalReferrersOn + ... +} +``` + +### 多对多many-to-many reference +在某些情况下,可能需要多对多引用 many-to-many reference. +假设您想向 StarWarsFilm 类添加对以下 Actors 表的引用: +```kotlin +object Actors: IntIdTable() { + val firstname = varchar("firstname", 50) + val lastname = varchar("lastname", 50) +} +class Actor(id: EntityID): IntEntity(id) { + companion object : IntEntityClass(Actors) + var firstname by Actors.firstname + var lastname by Actors.lastname +} +``` +创建一个额外的中间表来存储引用: +```kotlin +object StarWarsFilmActors : Table() { + val starWarsFilm = reference("starWarsFilm", StarWarsFilms) + val actor = reference("actor", Actors) + override val primaryKey = PrimaryKey(starWarsFilm, actor, name = "PK_StarWarsFilmActors_swf_act") // PK_StarWarsFilmActors_swf_act is optional here +} +``` +添加对 `StarWarsFilm` 的引用: +```kotlin +class StarWarsFilm(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(StarWarsFilms) + ... + var actors by Actor via StarWarsFilmActors + ... +} +``` +注意: 仅当您手动设置 id 列时,才能在同一个`事务transaction`中创建实体和引用。如果你正在使用 `UUIDTables` 和 `UUIDEntity` 你可以这样做: +```kotlin +transaction { + //only works with UUIDTable and UUIDEntity + StarWarsFilm.new (UUID.randomUUID()){ + ... + actors = SizedCollection(listOf(actor)) + } +} +``` +如果您不想手动设置 id 列,则必须在其自己的`事务transaction`中创建实体,然后在另一个`事务transaction`中设置关系: +```kotlin +// create film +val film = transaction { + StarWarsFilm.new { + name = "The Last Jedi" + sequelId = 8 + director = "Rian Johnson" + } +} +//create actor +val actor = transaction { + Actor.new { + firstname = "Daisy" + lastname = "Ridley" + } +} +//add reference +transaction { + film.actors = SizedCollection(listOf(actor)) +} +``` +### Parent-Child reference +Parent-child reference父子引用与多对多many-to-many版本非常相似,但中间表包含对同一个表的两个引用. +假设您要构建一个具有 parents 和 children 的分层实体. 我们的表和实体映射看起来会是这样 +```kotlin +object NodeTable : IntIdTable() { + val name = varchar("name", 50) +} +object NodeToNodes : Table() { + val parent = reference("parent_node_id", NodeTable) + val child = reference("child_user_id", NodeTable) +} +class Node(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(NodeTable) + var name by NodeTable.name + var parents by Node.via(NodeToNodes.child, NodeToNodes.parent) + var children by Node.via(NodeToNodes.parent, NodeToNodes.child) +} +``` +如您所见,`NodeToNodes` 列仅针对 `NodeTable`,并且使用了另一个版本的 `via` 函数. +现在您可以创建一个有层次结构的nodes. +```kotlin +val root = Node.new { name = "root" } +val child1 = Node.new { + name = "child1" +} +child1.parents = SizedCollection(root) // assign parent +val child2 = Node.new { name = "child2" } +root.children = SizedCollection(listOf(child1, child2)) // assign children +``` +请注意,您不能在 `new` block块内设置引用,因为尚未创建实体并且未定义要引用的 id. +### Eager Loading +**自 0.13.1 起可用**. +Exposed 中的引用是延迟加载的,这意味着获取引用数据的查询是在首次使用引用时进行的。对于您知道提前需要引用的场景,Exposed 可以在父查询时预先加载它们,这可以防止经典的“N+1”问题,因为引用可以在单个查询中聚合和加载. +To eager load a reference you can call the "load" function and pass the DAO's reference as a KProperty: +```kotlin +StarWarsFilm.findById(1).load(StarWarsFilm::actors) +``` +This works for references of references also, 例如, 如果 Actors 有一个 评分rating 的 reference 引用, 你可以: +```kotlin +StarWarsFilm.findById(1).load(StarWarsFilm::actors, Actor::rating) +``` +同样, 您可以在DAO集合(Lists、SizedIterables)上使用 eager load references, 对于集合,您可以像以前一样使用 with 函数, 将 DAO's reference 作为 KProperty 的引用传递. +```kotlin +StarWarsFilm.all().with(StarWarsFilm::actors) +``` +注意:通过eager loaded 的引用存储在事务缓存中, 这意味着它们在其他事务中不可用,因此必须在同一事务中加载和引用. + +#### Eager loading for Text Fields +某些数据库驱动程序不会立即加载文本内容(出于性能和内存原因),这意味着您只能在打开的事务中获取列值. + +如果您希望在事务之外使得内容可用,您可以在定义数据库表时使用 eagerLoading 参数. +> If you desire to make content available outside the transaction, you can use the eagerLoading param when defining the DB Table. +```kotlin +object StarWarsFilms : Table() { + ... + val description = text("name", eagerLoading=true) +} +``` +## 高级的增删改查操作 +### Read entity with a join to another table +假设您要查找对第二部 SW 电影评分超过 5 的所有用户. +首先,我们应该使用 Exposed DSL 编写查询语句. +```kotlin +val query = Users.innerJoin(UserRatings).innerJoin(StarWarsFilm) + .slice(Users.columns) + .select { + StarWarsFilms.sequelId eq 2 and (UserRatings.value gt 5) + }.withDistinct() +``` +之后我们要做的就是用User entity “包装”一个结果: +```kotlin +val users = User.wrapRows(query).toList() +``` +### Auto-fill created and updated columns on entity change +参见示例 @PaulMuriithi [这里](https://github.com/PaulMuriithi/ExposedDatesAutoFill/blob/master/src/main/kotlin/app/Models.kt). +### Use queries as expressions +想象一下,您想根据每个city拥有的user数量对city进行排序。为此,您可以编写一个子查询来计算每个city的用户user并按该数字排序。虽然为了这样做,您必须将 `Query` 转换为 `Expression`。这可以使用 `wrapAsExpression` 函数来完成: +```kotlin +val expression = wrapAsExpression(Users + .slice(Users.id.count()) + .select { + Cities.id eq Users.cityId + }) +val cities = Cities + .selectAll() + .orderBy(expression, SortOrder.DESC) + .toList() +``` +## 实体映射mapping +### Fields transformation +由于数据库只能存储整数和字符串等基本类型,因此在 DAO 级别保持相同的简单性并不总是很方便. +有时您可能想要进行一些转换,例如从 varchar 列解析 json 或根据数据库中的值从缓存中获取一些值. +在这种情况下,首选方法是使用列转换。假设我们要在 Entity 上定义无符号整数字段,但是 Exposed 还没有这样的列类型. +```kotlin +object TableWithUnsignedInteger : IntIdTable() { + val uint = integer("uint") +} +class EntityWithUInt : IntEntity() { + var uint: UInt by TableWithUnsignedInteger.uint.transform({ it.toInt() }, { it.toUInt() }) + + companion object : IntEntityClass() +} +``` +`transform` 函数接受两个 lambdas 来将值与原始列类型进行转换. +之后,在您的代码中,您将只能将 `UInt` 实例放入 `uint` 字 +仍然可以通过 DAO 插入带有负整数的更新值,但是您的业务代码会变得更加简洁. +请记住,每次对字段的访问都会产生什么这样的转换,这意味着您应该在此处避免大量转换. + +或者... 返回 [主页](Home.md) \ No newline at end of file diff --git a/zh_CN/DSL.md b/zh_CN/DSL.md new file mode 100644 index 0000000..4016c7c --- /dev/null +++ b/zh_CN/DSL.md @@ -0,0 +1,365 @@ +* [概览](#概览) +* [基本的增删改查操作](#基本的增删改查操作) + * [新增](#新增) + * [查询](#查询) + * [修改](#修改) + * [删除](#删除) +* [Where 条件表达式](#Where条件表达式) +* [有条件的 where](#有条件的where) +* [统计Count](#统计count) +* [排序Order-by](#排序order-by) +* [分组Group-by](#分组group-by) +* [限制Limit](#限制limit) +* [Join](#join) +* [Union](#union) +* [别名Alias](#别名alias) +* [Schema](#schema) +* [序列Sequence](#序列sequence) +* [批量插入Batch Insert](#批量插入Batch-Insert) +* [查询新增Insert From Select](#查询插入Insert-From-Select) +*** +## 概览 +Exposed 的 DSL(Domain Specific Language)API , 类似 Kotlin 提供的具有类型安全性的实际 SQL 语句. +一个数据库table表, 由继承自 `org.jetbrains.exposed.sql.Table` 的一个 `object`表示,如下所示: +```kotlin +object StarWarsFilms : Table() { + val id: Column = integer("id").autoIncrement() + val sequelId: Column = integer("sequel_id").uniqueIndex() + val name: Column = varchar("name", 50) + val director: Column = varchar("director", 50) + override val primaryKey = PrimaryKey(id, name = "PK_StarWarsFilms_Id") // PK_StarWarsFilms_Id is optional here +} +``` +包含 `Int` 类型 id 字段的表, 可以这样声明: +```kotlin +object StarWarsFilms : IntIdTable() { + val sequelId: Column = integer("sequel_id").uniqueIndex() + val name: Column = varchar("name", 50) + val director: Column = varchar("director", 50) +} +``` +## 基本的增删改查操作 +### 新增 +```kotlin +val id = StarWarsFilms.insertAndGetId { + it[name] = "The Last Jedi" + it[sequelId] = 8 + it[director] = "Rian Johnson" +} +``` +### 查询 +```kotlin +val query: Query = StarWarsFilms.select { StarWarsFilms.sequelId eq 8 } +``` +`Query` 继承 `Iterable`, 因此可以使用 map/foreach 等遍历它. 例如: +```kotlin +StarWarsFilms.select { StarWarsFilms.sequelId eq 8 }.forEach { + println(it[StarWarsFilms.name]) +} +``` +`slice` 函数可让您选择特定的列或/和表达式. +```kotlin +val filmAndDirector = StarWarsFilms. + slice(StarWarsFilms.name, StarWarsFilms.director). + selectAll().map { + it[StarWarsFilms.name] to it[StarWarsFilms.director] + } +``` +如果您只想选择不同的值,请使用 `withDistinct()` 函数: +```kotlin +val directors = StarWarsFilms. + slice(StarWarsFilms.director). + select { StarWarsFilms.sequelId less 5 }. + withDistinct().map { + it[StarWarsFilms.director] + } +``` +### 修改 +```kotlin +StarWarsFilms.update ({ StarWarsFilms.sequelId eq 8 }) { + it[StarWarsFilms.name] = "Episode VIII – The Last Jedi" +} +``` +如果您想使用表达式来更新列值(例如增量increment),请使用 `update` 函数或setter: +```kotlin +StarWarsFilms.update({ StarWarsFilms.sequelId eq 8 }) { + with(SqlExpressionBuilder) { + it.update(StarWarsFilms.sequelId, StarWarsFilms.sequelId + 1) + // or + it[StarWarsFilms.sequelId] = StarWarsFilms.sequelId + 1 + } +} +``` +### 删除 +```kotlin +StarWarsFilms.deleteWhere { StarWarsFilms.sequelId eq 8 } +``` +## Where条件表达式 +查询表达式(where)需要一个布尔运算符(即:`Op`). +以下是允许的条件(conditions): +``` +eq - (==) +neq - (!=) +isNull() +isNotNull() +less - (<) +lessEq - (<=) +greater - (>) +greaterEq - (>=) +like - (=~) +notLike - (!~) +exists +notExists +regexp +notRegexp +inList +notInList +between +match (MySQL MATCH AGAINST) +``` +以下是允许的逻辑条件(logical conditions): +``` +not +and +or +``` +## 有条件的where +It rather common case when your query's `where` condition depends on some other code conditions. Moreover, it could be independent or nested conditions what make it more complicated to prepare such `where`. +Let's imagine that we have a form on a website where a user can optionally filter "Star Wars" films by a director and/or a sequel. +In Exposed version before 0.8.1 you had to code it like: +一种常见的情况是, 您的查询的 `where` 条件取决于其他一些代码条件时. 此外, 可能是独立的或嵌套的条件, 这使得准备这样的 `where` 变得更加复杂。假设我们在网站上有一个表单,用户可以在该表单中选择过滤导演或续集的“星球大战”电影。在 0.8.1 之前的 Exposed 版本中,您必须将其编码为: +```Kotlin +val condition = when { + directorName != null && sequelId != null -> + Op.build { StarWarsFilms.director eq directorName and (StarWarsFilms.sequelId eq sequelId) } + directorName != null -> + Op.build { StarWarsFilms.director eq directorName } + sequelId != null -> + Op.build { StarWarsFilms.sequelId eq sequelId } + else -> null +} +val query = condition?.let { StarWarsFilms.select(condition) } ?: StarWarsFilms.selectAll() +``` +或者 +```Kotlin +val query = when { + directorName != null && sequelId != null -> + StarWarsFilms.select { StarWarsFilms.director eq directorName and (StarWarsFilms.sequelId eq sequelId) } + directorName != null -> + StarWarsFilms.select { StarWarsFilms.director eq directorName } + sequelId != null -> + StarWarsFilms.select { StarWarsFilms.sequelId eq sequelId } + else -> StarWarsFilms.selectAll() +} +``` +这是一个非常原始的示例,但是您应该了解该问题的主要思想. +现在, 让我们尝试以更简单的方式编写相同的查询(`andWhere` 函数从 0.10.5 开始可用): +```Kotlin +val query = StarWarsFilms.selectAll() +directorName?.let { + query.andWhere { StarWarsFilms.director eq it } +} +sequelId?.let { + query.andWhere { StarWarsFilms.sequelId eq it } +} +``` +但是,如果我们有条件地从另一个表中选择并且只想在条件为true时加入它怎么办? +您必须使用 `adjustColumnSet` 和 `adjustSlice` 函数(自 0.8.1 起可用),这些函数允许扩展和修改查询的 `join` 和 `slice` 部分(请参阅有关该函数的 kdoc): +```Kotlin +playerName?.let { + query.adjustColumnSet { innerJoin(Players, {StarWarsFilms.sequelId}, {Players.sequelId}) } + .adjustSlice { slice(fields + Players.columns) } + .andWhere { Players.name eq playerName } +} +``` +## 统计count +`count()` 是 `Query` 的一个方法,如下例所示: +```kotlin +val count = StarWarsFilms.select { StarWarsFilms.sequelId eq 8 }.count() +``` +## 排序order-by +Order-by 接受一个list(元素为columns到布尔指示的映射),指示排序应该是升序还是降序. +例子: +```kotlin +StarWarsFilms.selectAll().orderBy(StarWarsFilms.sequelId to SortOrder.ASC) +``` +## 分组group-by +在 group-by 中,通过 `slice()` 方法定义字段及其函数(例如 `count`). +```kotlin +StarWarsFilms + .slice(StarWarsFilms.sequelId.count(), StarWarsFilms.director) + .selectAll() + .groupBy(StarWarsFilms.director) +``` +可用的函数如下: +``` +count +sum +max +min +average +... +``` +## 限制limit +You can use limit function to prevent loading large data sets or use it for pagination with second `offset` parameter. +您可以使用 limit 函数来防止加载大型数据集或使用它带有第二个`offset`偏移参数的进行分页操作. +```kotlin +// 获取第 1 部电影之后的 2 部电影 +StarWarsFilms.select { StarWarsFilms.sequelId eq Players.sequelId }.limit(2, offset = 1) +``` +## Join +join 示例考虑如下 tables 表: +```kotlin +object StarWarsFilms : IntIdTable() { + val sequelId: Column = integer("sequel_id").uniqueIndex() + val name: Column = varchar("name", 50) + val director: Column = varchar("director", 50) +} +object Players : Table() { + val sequelId: Column = integer("sequel_id").uniqueIndex() + val name: Column = varchar("name", 50) +} +``` +连接(join)查询统计每一部电影有多少player玩家参演: +```kotlin +(Players innerJoin StarWarsFilms) + .slice(Players.name.count(), StarWarsFilms.name) + .select { StarWarsFilms.sequelId eq Players.sequelId } + .groupBy(StarWarsFilms.name) +``` +* 如果有外键,可以将 `select{}` 替换为 `selectAll()` + 使用完整语法的相同示例: +```kotlin +Players.join(StarWarsFilms, JoinType.INNER, additionalConstraint = {StarWarsFilms.sequelId eq Players.sequelId}) + .slice(Players.name.count(), StarWarsFilms.name) + .selectAll() + .groupBy(StarWarsFilms.name) +``` +## Union +您可以使用 `.union(...)` 组合多个查询的结果集. +根据 SQL 规范, 查询必须具有相同数量的列, 并且不标记为更新. +当数据库支持时, 可以合并子查询. +```kotlin +val lucasDirectedQuery = StarWarsFilms.slice(StarWarsFilms.name).select { StarWarsFilms.director eq "George Lucas" } +val abramsDirectedQuery = StarWarsFilms.slice(StarWarsFilms.name).select { StarWarsFilms.director eq "J.J. Abrams" } +val filmNames = lucasDirectedQuery.union(abramsDirectedQuery).map { it[StarWarsFilms.name] } +``` +默认情况下仅返回唯一的 rows 行数据. 可以使用 `.unionAll()` 返回重复的 rows 行数据. +```kotlin +val lucasDirectedQuery = StarWarsFilms.slice(StarWarsFilms.name).select { StarWarsFilms.director eq "George Lucas" } +val originalTrilogyQuery = StarWarsFilms.slice(StarWarsFilms.name).select { StarWarsFilms.sequelId inList (3..5) } +val filmNames = lucasDirectedQuery.unionAll(originalTrilogyQuery).map { it[StarWarsFilms.name] } +``` +## 别名alias +alias 别名可以防止混淆字段名和表名. +使用别名变量而不是原始变量: +```Kotlin +val filmTable1 = StarWarsFilms.alias("ft1") +filmTable1.selectAll() // can be used in joins etc' +``` +此外,alias 别名允许您在连接中多次使用同一个表: +```Kotlin +val sequelTable = StarWarsFilms.alias("sql") +val originalAndSequelNames = StarWarsFilms + .innerJoin(sequelTable, { StarWarsFilms.sequelId }, { sequelTable[StarWarsFilms.id] }) + .slice(StarWarsFilms.name, sequelTable[StarWarsFilms.name]) + .selectAll() + .map { it[StarWarsFilms.name] to it[sequelTable[StarWarsFilms.name]] } +``` +当使用selecting from 子查询时, 可以使用它们: +```kotlin +val starWarsFilms = StarWarsFilms + .slice(StarWarsFilms.id, StarWarsFilms.name) + .selectAll() + .alias("swf") +val id = starWarsFilms[StarWarsFilms.id] +val name = starWarsFilms[StarWarsFilms.name] +starWarsFilms + .slice(id, name) + .selectAll() + .map { it[id] to it[name] } +``` +## Schema +你可以创建一个schema 或 删除一个已经存在的: +```Kotlin +val schema = Schema("my_schema") // my_schema is the schema name. +// Creates a Schema +SchemaUtils.createSchema(schema) +// Drops a Schema +SchemaUtils.dropSchema(schema) +``` +此外,您可以像这样指定 schema 所有者(某些数据库需要显式所有者): +```Kotlin +val schema = Schema("my_schema", authorization = "owner") +``` +如果你有很多 schema 并且你想设置一个默认 schema ,你可以使用: +```Kotlin +SchemaUtils.setSchema(schema) +``` +## 序列sequence +如果你想使用 Sequence,Exposed 是允许的: +### 定义一个 Sequence +```Kotlin +val myseq = Sequence("my_sequence") // my_sequence is the sequence name. +``` +可以指定几个参数来控制 sequence 的属性: +```Kotlin +private val myseq = Sequence( + name = "my_sequence", + startWith = 4, + incrementBy = 2, + minValue = 1, + maxValue = 10, + cycle = true, + cache = 20 + ) +``` +### 创建或删除一个 Sequence +```Kotlin +// Creates a sequence +SchemaUtils.createSequence(myseq) +// Drops a sequence +SchemaUtils.dropSequence(myseq) +``` +### 使用 NextVal 函数 +您可以这样使用 nextVal 函数: +```Kotlin +val nextVal = myseq.nextVal() +val id = StarWarsFilms.insertAndGetId { + it[id] = nextVal + it[name] = "The Last Jedi" + it[sequelId] = 8 + it[director] = "Rian Johnson" +} +``` +```Kotlin +val firstValue = StarWarsFilms.slice(nextVal).selectAll().single()[nextVal] +``` +## 批量插入Batch-Insert +批量插入允许在一个 sql 语句中将实体列表映射到数据库原始数据。它比一个一个插入更有效,因为它只初始化一个statement语句。这是一个例子: +```kotlin +val cityNames = listOf("Paris", "Moscow", "Helsinki") +val allCitiesID = cities.batchInsert(cityNames) { name -> + this[cities.name] = name +} +``` +*注意:* `batchInsert` 函数在与数据库交互时仍会创建多个 `INSERT` 语句。您很可能希望将此与相关 JDBC 驱动程序的 `rewriteBatchedInserts=true`(或 `rewriteBatchedStatements=true`)选项结合使用,这会将它们转换为单个 bulkInsert. +您可以在 MySQL 中找到此选项的文档 [这里](https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-configuration-properties.html) 和 PostgreSQL [这里](https://jdbc.postgresql.org/documentation/94/connect.html). + +如果您不需要获取新生成的值(例如:自动递增的 ID),请将 `shouldReturnGeneratedValues` 参数设置为 false,这样可以通过将它们分批来提高批量插入的性能,而不是一直等待数据库同步新插入的对象状态. + +如果要检查 `rewriteBatchedInserts` + `batchInsert` 是否正常工作,请检查如何为您的驱动程序启用 JDBC 日志记录,因为 Exposed 将始终显示未重写的多个插入。您可以找到有关如何在 PostgreSQL 中启用日志记录的文档 [这里](https://jdbc.postgresql.org/documentation/head/logging.html). +## 查询插入Insert-From-Select +如果您想使用 `INSERT INTO ... SELECT ` SQL 语句, 可以尝试 Exposed 的 `Table.insert(Query)`. +```kotlin +val substring = users.name.substring(1, 2) +cities.insert(users.slice(substring).selectAll().orderBy(users.id).limit(2)) +``` +默认情况下,它将尝试按照它们在 Table 实例中定义的顺序插入 non auto-increment “表”中的所有列。如果要指定某些列或更改顺序,请提供第二个参数columns(传入一个columns的list列表): +> By default it will try to insert into all non auto-increment `Table` columns in order they defined in Table instance. If you want to specify columns or change the order, provide list of columns as second parameter: +```kotlin +val userCount = users.selectAll().count() +users.insert(users.slice(stringParam("Foo"), Random().castTo(VarCharColumnType()).substring(1, 10)).selectAll(), columns = listOf(users.name, users.id)) +``` + +或者... 返回 [主页](Home.md) \ No newline at end of file diff --git a/zh_CN/DataBase-and-DataSource.md b/zh_CN/DataBase-and-DataSource.md new file mode 100644 index 0000000..6ea595b --- /dev/null +++ b/zh_CN/DataBase-and-DataSource.md @@ -0,0 +1,102 @@ +### 使用数据库和数据源 datasource +使用 Exposed 的每个数据库访问都是通过获取连接和创建事务来启动的. +首先,您必须告诉 Exposed 如何使用 `Database.connect` 函数连接到数据库. +它不会创建真正的数据库连接,而只会提供一个描述符descriptor以供将来使用. + +将通过调用事务 `transaction` lambda函数, 稍后来实例化一个真正的数据库连接(详情请阅 [事务](https://github.com/JetBrains/Exposed/wiki/Transactions)). + +通过简单提供连接参数, 来获取数据库实例: +```kotlin +val db = Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") +``` +也可以通过 `javax.sql.DataSource` 来提供连接池等高级行为: +```kotlin +val db = Database.connect(dataSource) +``` +* 注意:从 Exposed 0.10 开始,在您的应用程序中, 每个数据库多次执行此代码会造成泄漏,因此建议将其存储起来以备后用. +例如: +```kotlin +object DbSettings { + val db by lazy { + Database.connect(/* setup connection */) + } +} +``` +### 数据源 DataSource +* PostgreSQL +```kotlin +Database.connect("jdbc:postgresql://localhost:12346/test", driver = "org.postgresql.Driver", + user = "root", password = "your_pwd") +//Gradle +implementation("org.postgresql:postgresql:42.2.2") +``` +* PostgreSQL using the pgjdbc-ng JDBC driver +```kotlin +Database.connect("jdbc:pgsql://localhost:12346/test", driver = "com.impossibl.postgres.jdbc.PGDriver", + user = "root", password = "your_pwd") +//Gradle +implementation("com.impossibl.pgjdbc-ng", "pgjdbc-ng", "0.8.3") +``` +* MySQL/MariaDB +```kotlin +Database.connect("jdbc:mysql://localhost:3306/test", driver = "com.mysql.cj.jdbc.Driver", + user = "root", password = "your_pwd") +//Gradle +implementation("mysql:mysql-connector-java:8.0.2") +``` +* MySQL/MariaDB with latest JDBC driver + Hikari pooling +```kotlin +val config = HikariConfig().apply { + jdbcUrl = "jdbc:mysql://localhost/dbname" + driverClassName = "com.mysql.cj.jdbc.Driver" + username = "username" + password = "secret" + maximumPoolSize = 10 +} +val dataSource = HikariDataSource(config) +Database.connect(dataSource) +// Gradle +implementation "mysql:mysql-connector-java:8.0.19" +implementation "com.zaxxer:HikariCP:3.4.2" +``` +* Oracle +```kotlin +Database.connect("jdbc:oracle:thin:@//localhost:1521/test", driver = "oracle.jdbc.OracleDriver", + user = "root", password = "your_pwd") +//Gradle +// Oracle jdbc-driver should be obtained from Oracle maven repo: https://blogs.oracle.com/dev2dev/get-oracle-jdbc-drivers-and-ucp-from-oracle-maven-repository-without-ides +``` ++ SQLite +```kotlin +// In file +Database.connect("jdbc:sqlite:/data/data.db", "org.sqlite.JDBC") +// In memory +Database.connect("jdbc:sqlite:file:test?mode=memory&cache=shared", "org.sqlite.JDBC") +// For both: set SQLite compatible isolation level, see +// https://github.com/JetBrains/Exposed/wiki/FAQ +TransactionManager.manager.defaultIsolationLevel = + Connection.TRANSACTION_SERIALIZABLE + // or Connection.TRANSACTION_READ_UNCOMMITTED +//Gradle +implementation("org.xerial:sqlite-jdbc:3.30.1") +``` +* H2 +```kotlin +// Database in file, needs full path or relative path starting with ./ +Database.connect("jdbc:h2:./myh2file", "org.h2.Driver") +// In memory +Database.connect("jdbc:h2:mem:regular", "org.h2.Driver") +// In memory / keep alive between connections/transactions +Database.connect("jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;", "org.h2.Driver") +//Gradle +implementation("com.h2database:h2:1.4.199") +``` +* SQL Server +```kotlin +Database.connect("jdbc:sqlserver://localhost:32768;databaseName=test", "com.microsoft.sqlserver.jdbc.SQLServerDriver", + user = "root", password = "your_pwd") +//Gradle +implementation("com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre7") +``` + +或者... 返回 [主页](Home.md) \ No newline at end of file diff --git a/zh_CN/DataTypes.md b/zh_CN/DataTypes.md new file mode 100644 index 0000000..36c3f83 --- /dev/null +++ b/zh_CN/DataTypes.md @@ -0,0 +1,74 @@ +目前 Exposed 在表table的定义中支持以下数据类型: +* `integer` - 转换成数据库的 `INT` +* `long` - `BIGINT` +* `float` - `FLOAT` +* `decimal` - `DECIMAL` 具有规模和精度 +* `bool` - `BOOLEAN` +* `char` - `CHAR` +* `varchar` - `VARCHAR` 有长度 +* `text` - `TEXT` +* `enumeration` - `INT` 序数值(ordinal value) +* `enumerationByName` - `VARCHAR` +* `customEnumeration` - 详见 [附加部分](#如何使用数据库枚举类型) +* `blob` - `BLOB` +* `binary` - `VARBINARY` 有长度 +* `uuid` - `BINARY(16)` +* `reference` - 外键 + +其中 `exposed-java-time` 扩展模块 (`org.jetbrains.exposed:exposed-java-time:$exposed_version`) 提供了额外的类型: + +* `date` - `DATETIME` +* `time` - `TIME` +* `datetime` - `DATETIME` +* `timestamp` - `TIMESTAMP` +* `duration` - `DURATION` + +注意:某些类型对于特定的数据库方言是不同的. + +## 如何使用数据库枚举类型 +一些数据库(例如 MySQL、PostgreSQL、H2)支持显式 ENUM 类型。因为仅使用 jdbc 元数据使这些columns列与 kotlin enumerations枚举保持同步, 可能是一个巨大的挑战, 所以 Exposed 不提供以自动方式管理这些columns列的可能性,但这并不意味着您不能使用这些columns列类型. +您有两个选项可以使用 ENUM 数据库类型: +1. 使用表中现有的 ENUM column列 +2. 通过提供原始定义 SQL 从 Exposed 创建column列 + 在这两种情况下,您都应该使用 `customEnumeration` 函数(从 0.10.3 版开始可用) + +由于jdbc-driver 提供了一个 provide/expect 特殊类给 Enum 类型, 您必须在定义一个 `customEnumeration`时为它提供 from/to 转换函数. + +例如这样的枚举类 `private enum class Foo { Bar, Baz }` 您可以使用如下为数据库提供的代码: + +**H2** +```Kotlin +val existingEnumColumn = customEnumeration("enumColumn", { Foo.values()[it as Int] }, { it.name }) +val newEnumColumn = customEnumeration("enumColumn", "ENUM('Bar', 'Baz')", { Foo.values()[it as Int] }, { it.name }) +``` + +**MySQL** +```Kotlin +val existingEnumColumn = customEnumeration("enumColumn", { value -> Foo.valueOf(value as String) }, { it.name }) +val newEnumColumn = customEnumeration("enumColumn", "ENUM('Bar', 'Baz')", { value -> Foo.valueOf(value as String) }, { it.name }) +``` + +**PostgreSQL** + +PostgreSQL 要求将 ENUM 定义为单独的类型,因此您必须在创建表之前创建它。 postgresql jdbc 驱动程序也会为这些值返回 PGobject 实例。下面提供了完整的工作示例. +```Kotlin +class PGEnum>(enumTypeName: String, enumValue: T?) : PGobject() { + init { + value = enumValue?.name + type = enumTypeName + } +} + +object EnumTable : Table() { + val enumColumn = customEnumeration("enumColumn", "FooEnum", {value -> Foo.valueOf(value as String)}, { PGEnum("FooEnum", it) } +} +... +transaction { + exec("CREATE TYPE FooEnum AS ENUM ('Bar', 'Baz');") + SchemaUtils.create(EnumTable) + ... +} +``` + + +或者... 返回 [主页](Home.md) \ No newline at end of file diff --git a/zh_CN/FAQ.md b/zh_CN/FAQ.md new file mode 100644 index 0000000..0d87e36 --- /dev/null +++ b/zh_CN/FAQ.md @@ -0,0 +1,135 @@ +### Q: [Squash](https://github.com/orangy/squash) 与 Exposed 类似. 它们的区别在哪? +A: [Ilya Ryzhenkov](https://github.com/orangy/) (Squash 维护者) 回答: +> Squash is an attempt to refactor Exposed (long time ago) to fix DSL issues, extensibility on dialect side, support graph fetching and avoid TLS-stored transactions. Unfortunately, I didn’t have enough time to finish the work, but I still hope to return to it some day. We are talking with Exposed maintainer [@tapac](https://github.com/orangy/) about coordinating efforts and eventually joining forces. Note, that none of these libs are “official” JetBrains Kotlin SQL libs, they are both side projects of their respective authors. + +### Q: 我可以使用多个数据库连接吗? + +A: 可以. 相见 [[Working with a multiple databases|Transactions#working-with-a-multiple-databases]] + +### Q: 是否支持 `Array` column列类型? + +A: 目前不行. 更多信息在这里: https://github.com/JetBrains/Exposed/issues/150 +可在此处找到支持的数据类型的完整列表: [[Data Types|DataTypes]]. + +### Q: 是否支持 `upsert` 操作? + +A: Upsert is an instruction to the Database to insert a new row or update existing row based on a table key. It is not supported as part of the library but it is possible to implement on top of it. See this issue: https://github.com/JetBrains/Exposed/issues/167 and example here: https://medium.com/@OhadShai/first-steps-with-kotlin-exposed-cb361a9bf5ac + +### Q: 是否支持 `json` 类型? + +A: 目前不行. issue问题在这里: https://github.com/JetBrains/Exposed/issues/127 +可在此处找到支持的数据类型的完整列表: [[Data Types|DataTypes]]. + +### Q: 如何获取将执行的普通 SQL 查询? + +A: +```kotlin +val plainSQL = FooTable.select {}.prepareSQL(QueryBuilder(false)) +``` +使用QueryBuilder,带有 `false` - 如果你想获取内联语句参数; +带有`true` - 可以查看到带有 '?' 的查询语句. + +### Q: 是否可以使用原生sql sql 作为字符串? + +A: 作为库的一部分它是不支持, 但可以在它之上实现, 像这样使用: +```kotlin +fun String.execAndMap(transform : (ResultSet) -> T) : List { + val result = arrayListOf() + TransactionManager.current().exec(this) { rs -> + while (rs.next()) { + result += transform(rs) + } + } + return result +} + +"select u.name, c.name from user u inner join city c where blah blah".execAndMap { rs -> + rs.getString("u.name") to rs.getString("c.name") +} +``` +更多信息查看这个issue问题: https://github.com/JetBrains/Exposed/issues/118 + +### Q: 是否可以相对于当前字段值更新字段? + +A: 可以. 在此处查看示例: https://github.com/JetBrains/Exposed/wiki/DSL#update + +### Q: How can I add another type of Database? + +A: Implement `DatabaseDialect` interface and register it with `Database.registerDialect()`. +If the implementation adds a lot of value consider contributing it as a PR to Exposed. + +### Q: Is it possible to create tables with cross / cyclic reference? + +A: Yes, it's possible since Exposed 0.11.1 version + +### Q: How can I implement nested queries? + +A: See example here: https://github.com/JetBrains/Exposed/issues/248 + +### Q: How can I use SAVEPOINT? +A: It possible only through using a raw connection. See example [here](https://github.com/JetBrains/Exposed/issues/320#issuecomment-394825415). + +### Q: How to prepare query like: `SELECT * FROM table WHERE (x,y) IN ((1, 2), (3, 4), (5, 6))` +A: It possible with custom function. See [example](https://github.com/JetBrains/Exposed/issues/373#issuecomment-414123325). + +### Q: Where can I find snapshot builds of Exposed +A: You could use jitpack.io service for that. + +Add jitpack.io to repositories: +``` +repositories { + maven { url 'https://jitpack.io' } +} +``` +Then add Exposed dependency as stated below: +``` +dependencies { + implementation 'com.github.JetBrains:Exposed:-SNAPSHOT' +} +``` + +### Q: How can I specify a primary key column type e.g StringIdTable? +A: You need to define your own! See examples: +[#855](https://github.com/JetBrains/Exposed/issues/855) +https://stackoverflow.com/a/61940820/1155026 + +### Q: 如何创建自定义column列类型 +A: 只要实现 [IColumnType](https://github.com/JetBrains/Exposed/blob/76a671e57a0105d6aed79e256c088690bd4a56b6/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt#L25) + 并使用 [registerColumn](https://github.com/JetBrains/Exposed/blob/76a671e57a0105d6aed79e256c088690bd4a56b6/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt#L387) + 来 [扩展](https://kotlinlang.org/docs/extensions.html) 一个 [Table](https://github.com/JetBrains/Exposed/blob/76a671e57a0105d6aed79e256c088690bd4a56b6/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt#L326) + + +例如: **创建一个自定义 UUID 类型 (灵感来自 [@pjagielski 文章](https://medium.com/@pjagielski/how-we-use-kotlin-with-exposed-at-touk-eacaae4565b5#e4e4))** +```kotlin +abstract class TypedId(open val id: UUID): Serializable, Comparable { + override fun compareTo(other: TypedId) = this.id.compareTo(other.id) +} + +class TypedUUIDColumnType(val constructor: (UUID) -> T, private val uuidColType: UUIDColumnType = UUIDColumnType()): IColumnType by uuidColType { + override fun valueFromDB(value: Any) = constructor(uuidColType.valueFromDB(value)) + override fun notNullValueToDB(value: Any): Any = uuidColType.notNullValueToDB(valueUnwrap(value)) + override fun nonNullValueToString(value: Any): String = uuidColType.nonNullValueToString(valueUnwrap(value)) + private fun valueUnwrap(value: Any) = (value as? TypedId)?.id ?: value +} + +fun Table.typedUuid(name: String, constructor: (UUID) -> T) = registerColumn(name, TypedUUIDColumnType(constructor)) +fun Column.autoGenerate(constructor: (UUID) -> T): Column = clientDefault { constructor(UUID.randomUUID()) } + + +class StarWarsFilmId(id: UUID): TypedId(id) + +object StarWarsFilms : Table() { + val id = typedUuid("id") { StarWarsFilmId(it) }.autoGenerate{ StarWarsFilmId(it) } + val name: Column = varchar("name", 50) + val director: Column = varchar("director", 50) + final override val primaryKey = PrimaryKey(id) +} +``` + + +参考: [#149](https://github.com/JetBrains/Exposed/issues/149) + +### 更多问题在 Stack Overflow: +https://stackoverflow.com/questions/tagged/kotlin-exposed + +或者... 返回 [主页](Home.md) \ No newline at end of file diff --git a/zh_CN/Functions.md b/zh_CN/Functions.md new file mode 100644 index 0000000..0d5d1ec --- /dev/null +++ b/zh_CN/Functions.md @@ -0,0 +1,72 @@ +* [如何使用函数](#如何使用函数) +* [字符串函数](#字符串函数) +* [聚合函数](#聚合函数) +* [自定义函数](#自定义函数) + +## 如何使用函数 +如果您想从查询中检索出函数的结果,则必须将函数声明为变量,例如: +```kotlin +val lowerCasedName = FooTable.name.lowerCase() +val lowerCasedNames = FooTable.slice(lowerCasedName).selecAll().map { it[lowerCasedName] } + +``` +此外,功能可以链接和组合: +```kotlin +val trimmedAndLoweredFullName = Concat(FooTable.firstName, stringLiteral(" "), FooTable.lastName).trim().lowerCase() +val fullNames = FooTable.slice(trimmedAndLoweredFullName).selecAll().map { it[trimmedAndLoweredFullName] } + +``` + +## 字符串函数 +### 大小写转换 +返回小写大写字符串值. +```kotlin +val lowerCasedName = FooTable.name.lowerCase() +val lowerCasedNames = FooTable.slice(lowerCasedName).selecAll().map { it[lowerCasedName] } + +``` + + +## 聚合函数 +这些函数应该在查询中使用 [[分组groupBy|DSL#分组group-by]]. +### 最小值/最大值/平均值 +返回 最小/最大/平均值, 可以应用于任何可比较的表达式 +```kotlin +val minId = FooTable.id.min() +val maxId = FooTable.id.max() +val averageId = FooTable.id.avg() +val (min, max, avg) = FooTable.slice(minId, maxId, averageId).selecAll().map { + Triple(it[minId], it[maxId], it[averageId]) +} + +``` + +## 自定义函数 +如果您在数据库中找不到您最喜欢的函数(因为 Exposed 仅对经典 SQL 函数提供基本支持),您可以定义自己的函数. + +自 Exposed 0.15.1 以来,通过有多个选项来定义自定义函数: +1. 无参数函数: +```kotlin +val sqrt = FooTable.id.function("SQRT") +``` +在 SQL 表示中,它将是 `SQRT(FooTable.id)` + +2. 带有附加参数的函数: +```kotlin +val replacedName = CustomFunction("REPLACE", VarCharColumnType(), FooTable.name, stringParam("foo"), stringParam("bar")) + +``` +`CustomFunction` 类接收函数名作为第一个参数,结果列类型作为第二个参数,之后您可以提供任意数量的参数,并用逗号分隔. + +还有一些快捷方式,针对string, long and datetime类型的函数: +* `CustomStringFunction` +* `CustomLongFunction` +* `CustomDateTimeFunction` + +上面的代码可以简化为: +```kotlin +val replacedName = CustomStringFunction("REPLACE", FooTable.name, stringParam("foo"), stringParam("bar")) + +``` + +或者... 返回 [主页](Home.md) diff --git a/zh_CN/Getting-Started.md b/zh_CN/Getting-Started.md new file mode 100644 index 0000000..37e19c2 --- /dev/null +++ b/zh_CN/Getting-Started.md @@ -0,0 +1,198 @@ +## Download + +### Maven + +```xml + + + + mavenCentral + mavenCentral + https://repo1.maven.org/maven2/ + + + + + + + jcenter + jcenter + https://jcenter.bintray.com + + + + + + + org.jetbrains.exposed + exposed-core + 0.37.3 + + + org.jetbrains.exposed + exposed-dao + 0.37.3 + + + org.jetbrains.exposed + exposed-jdbc + 0.37.3 + + + +``` + +### Gradle Kotlin Script + +如果您使用的是旧版本的 Gradle,请将以下内容添加到您的 `build.gradle` 文件中. + +``` +repositories { + // Versions after 0.30.1 + mavenCentral() + + // Versions before 0.30.1 + jcenter() +} +dependencies { + implementation("org.jetbrains.exposed", "exposed-core", "0.37.3") + implementation("org.jetbrains.exposed", "exposed-dao", "0.37.3") + implementation("org.jetbrains.exposed", "exposed-jdbc", "0.37.3") +} +``` + +如果您使用的是较新版本的 Gradle,您可以将以下内容添加到您的 `build.gradle.kts`. + +``` +val exposedVersion: String by project +dependencies { + implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") +} +``` + +以及在您的 `gradle.properties` 文件中添加版本号 + +``` +exposedVersion=0.37.3 +``` + +- 注意: 还有一些其他模块。详细信息位于[[Modules 模块文档|LibDocumentation]]部分. + +## 入门 + +### 开启一个事务 + +使用 Exposed 的每个数据库访问都是通过获取连接和创建事务来启动的。 + +获取连接: + +```kotlin +Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") +``` + +也可以通过 `javax.sql.DataSource` 来提供连接池等高级行为 + +```kotlin +Database.connect(dataSource) +``` + +详情见 [[数据库和数据源|DataBase-and-DataSource]] + +获得连接后,所有 SQL 语句都应该放在一个事务中: + +```kotlin +transaction { + // Statements here +} +``` + +想要查看实际的数据库调用,请添加一个记录器logger: + +```kotlin +transaction { + // print sql to std-out + addLogger(StdOutSqlLogger) +} +``` + +### DSL & DAO + +Exposed 有两种形式:DSL(领域特定语言)和 DAO(数据访问对象). +On a high level, DSL means type-safe syntax that is similar to SQL whereas DAO means doing CRUD operations on entities. +在上层,DSL 意味着类似于 SQL 的类型安全语法,而 DAO 意味着对实体进行 CRUD 操作. +观察以下示例,并前往每个 API 的特定部分了解更多详细信息. + +### Your first Exposed DSL + +```kotlin + + +fun main(args: Array) { + // 一个连接 H2 数据的示例 + Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") + + transaction { + // 将 sql 打印到标准输出 + addLogger(StdOutSqlLogger) + + SchemaUtils.create (Cities) + + // insert new city. SQL: INSERT INTO Cities (name) VALUES ('St. Petersburg') + val stPeteId = Cities.insert { + it[name] = "St. Petersburg" + } get Cities.id + + // 'select *' SQL: SELECT Cities.id, Cities.name FROM Cities + println("Cities: ${Cities.selectAll()}") + } +} + +object Cities: IntIdTable() { + val name = varchar("name", 50) +} + +``` + +详见 [DSL API](DSL.md) + +### Your first Exposed DAO + +```kotlin + + +fun main(args: Array) { + // 一个连接 H2 数据的示例 + Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") + + transaction { + // 将 sql 打印到标准输出 + addLogger(StdOutSqlLogger) + + SchemaUtils.create (Cities) + + // insert new city. SQL: INSERT INTO Cities (name) VALUES ('St. Petersburg') + val stPete = City.new { + name = "St. Petersburg" + } + + // 'select *' SQL: SELECT Cities.id, Cities.name FROM Cities + println("Cities: ${City.all()}") + } +} + +object Cities: IntIdTable() { + val name = varchar("name", 50) +} + +class City(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(Cities) + + var name by Cities.name +} +``` + +详见 [DAO API](DAO.md) + +或者... 返回 [主页](Home.md) diff --git a/zh_CN/Home.md b/zh_CN/Home.md new file mode 100644 index 0000000..bf21b32 --- /dev/null +++ b/zh_CN/Home.md @@ -0,0 +1,29 @@ +欢迎来到 Exposed wiki维基! (仍在建设中) + +Exposed 是一个基于 JDBC 驱动的 Kotlin 语言轻量级 SQL 库. +Exposed 有两种类型的数据库访问:类型安全的 SQL wrapping DSL 和 轻量级数据访问对象 (DAO) + +该 wiki维基 包含以下页面:: + + * [介绍](Home.md) - 当前页 + * [入门](Getting-Started.md) + * [数据库和数据源](DataBase-and-DataSource.md) + * [事务](Transactions.md) + * [DSL API](DSL.md) + * [DAO API](DAO.md) + * [模块文档](LibDocumentation.md) + * [支持的数据类型](DataTypes.md) + * [内置的SQL函数](Functions.md) + * [常见问题](FAQ.md) + * [迁移指南](Migration-Guide.md) + + +目前支持的数据库方言有: + +* PostgreSQL +* MySQL +* Oracle +* SQLite +* H2 +* SQL Server +* MariaDB diff --git a/zh_CN/LibDocumentation.md b/zh_CN/LibDocumentation.md new file mode 100644 index 0000000..3f90cb5 --- /dev/null +++ b/zh_CN/LibDocumentation.md @@ -0,0 +1,155 @@ +## 依赖Dependencies +Exposed 模块可从 Maven Central(旧版本的 JFrog Bintray 和 JCenter)存储库中获得. +要使用它们,您必须在repositories存储库映射中添加适当的依赖项. + +#### Maven +```xml + + + + mavenCentral + mavenCentral + https://repo1.maven.org/maven2/ + + + + + + + jcenter + jcenter + https://jcenter.bintray.com + + +``` + +#### Gradle Groovy and Kotlin DSL + +```kotlin +repositories { + // 0.30.1 之后的版本 + mavenCentral() + + // 0.30.1 之前的版本 + jcenter() +} +``` + +## 基础模块 +### Exposed 0.17.x 以及较低版本 +Exposed 0.18.1 之前的版本, 只有一个基本模块`exposed`,其中包含您可能需要的所有内容,包括 JodaTime 作为date-time库. +你必须使用如下方式添加该版本的`Exposed`框架: + +#### Maven +```xml + + + org.jetbrains.exposed + exposed + 0.17.7 + + + +``` + +#### Gradle Groovy +```groovy +dependencies { + implementation 'org.jetbrains.exposed:exposed:0.17.7' +} +``` +#### Gradle Kotlin DSL +```kotlin +dependencies { + implementation("org.jetbrains.exposed", "exposed", "0.17.7") +} +``` + +### Exposed 0.18.1 以及较高版本 +为了向前推进和支持 Java 8 Time、异步驱动程序等特性,决定将 Exposed 拆分为更具体的模块。它将允许您使用您需要的唯一模块,并将在未来增加灵活. + +目前 `Exposed` 提供的共存模块如下 +* exposed-core - 基本模块, 其中包含 DSL api 和 mapping映射 +* exposed-dao - DAO api +* exposed-jdbc - 基于 Java JDBC API 的传输层实现 +* exposed-jodatime - 基于 JodaTime 库的 date-time 扩展功能 +* exposed-java-time - 基于 Java8 Time API的 date-time 扩展功能 +* exposed-kotlin-datetime - 基于 kotlinx-datetime的 date-time 扩展功能 +* exposed-money - 从`javax.money:money-api` 获取对 MonetaryAmount 的扩展支持 + +下面列出的依赖关系映射与以前的版本相似(按功能): +#### Maven +```xml + + + + org.jetbrains.exposed + exposed-core + 0.37.3 + + + org.jetbrains.exposed + exposed-dao + 0.37.3 + + + org.jetbrains.exposed + exposed-jdbc + 0.37.3 + + + org.jetbrains.exposed + exposed-jodatime + 0.37.3 + + + org.jetbrains.exposed + exposed-java-time + 0.37.3 + + + +``` + +#### Gradle Groovy +```groovy +dependencies { + implementation 'org.jetbrains.exposed:exposed-core:0.37.3' + implementation 'org.jetbrains.exposed:exposed-dao:0.37.3' + implementation 'org.jetbrains.exposed:exposed-jdbc:0.37.3' + implementation 'org.jetbrains.exposed:exposed-jodatime:0.37.3' + // or + implementation 'org.jetbrains.exposed:exposed-java-time:0.37.3' +} +``` +#### Gradle Kotlin DSL +在 `build.gradle.kts` 中的内容: +```kotlin +val exposedVersion: String by project +dependencies { + implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-jodatime:$exposedVersion") + // or + implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion") +} +``` +在 `gradle.properties` 中的内容: +``` +exposedVersion=0.37.3 +``` + +### JDBC driver and logging +您还需要为正在使用的数据库系统提供 JDBC 驱动程序 (详见 [[数据库和数据源|数据库和数据源]]) 和 一个日志实现 `addLogger(StdOutSqlLogger)`. 例如 (Gradle 语法): +```kotlin +dependencies { + // for H2 + implementation("com.h2database:h2:1.4.199") + // for logging (StdOutSqlLogger), see + // http://www.slf4j.org/codes.html#StaticLoggerBinder + implementation("org.slf4j:slf4j-nop:1.7.30") +} +``` + +或者... 返回 [主页](Home.md) \ No newline at end of file diff --git a/zh_CN/Migration-Guide.md b/zh_CN/Migration-Guide.md new file mode 100644 index 0000000..077b056 --- /dev/null +++ b/zh_CN/Migration-Guide.md @@ -0,0 +1,68 @@ +## Migrating from Exposed 0.17.x and earlier to 0.18.+ +### Dependencies + +See [["Exposed 0.18.1 and higher" section|LibDocumentation#exposed-0181-and-higher]] in Modules Documentation + +### Code changes: +Some functions and extensions had changed their places, another became extension functions instead of member functions. +In both cases it could require re-import to be possible to compile your code again. + +#### JDBC related classes +Those classes were replaced with Exposed alternatives. Only part of original interface functions reused in new interfaces. + +`java.sql.Connection` -> `org.jetbrains.exposed.sql.statements.api.ExposedConnection` + +`java.sql.Savepoint` -> `org.jetbrains.exposed.sql.statements.api.ExposedSavepoint` + +`java.sql.Blob` and `javax.sql.rowset.serial.SerialBlob` -> `org.jetbrains.exposed.sql.statements.api.ExposedBlob` + +`java.sql.PreparedStatement` -> `org.jetbrains.exposed.sql.statements.api.PreparedStatementApi` + +`java.sql.DatabaseMetadata` -> `org.jetbrains.exposed.sql.statements.api.ExposedDatabaseMetadata` (access it with `Database.metadata { }` function) + +If you need to get original jdbc connection (and you use `exposed-jdbc` as a dependency) you should cast `ExposedConnection` instance to `JdbcConnectionImpl` and get value from a `connection` field. +```kotlin +val jdbcConnection: java.sql.Conneciton = (transaction.connection as JdbcConnectionImpl).connection +``` + +The same goes for `java.sql.PreparedStatement` and `PreparedStatementApi` but you should cast it to `JdbcPreparedStatementImpl` +```kotlin +val exposedStatement : PreparedStatementApi = // e.g. received from a StatementInterceptor +val preparedStatement: java.sql.PreparedStatement = (exposedStatement as JdbcPreparedStatementImpl).statement +``` +### JodaTime and Java Time Api +Since 0.18.1 datetime functions have their own modules for both JodaTime and Java Time implementations. +If you already use JodaTime just re-import date related functions as they become extensions. +But if you want to switch from JodaTime to Java Time you have to: +1. Use `exposed-java-time` instead of `exposed-jodatime` +2. Fix your code according to a fact what `date` column will return `java.time.LocalDate` and `datetime` -> `java.time.LocalDateTime` + +## Migrating to 0.19.+ +To allow Exposed to work with Java 9 module system some classes in a `exposed-core` modules were changed their packages. +Also, `exposed-jodatime` functions/classes were moved to a new package. + +Affected classes: + +`org.jetbrains.exposed.dao.EntityID` -> `org.jetbrains.exposed.dao.id.EntityID` + +`org.jetbrains.exposed.dao.IdTable` -> `org.jetbrains.exposed.dao.id.IdTable` + +`org.jetbrains.exposed.dao.IntIdTable` -> `org.jetbrains.exposed.dao.id.IntIdTable` + +`org.jetbrains.exposed.dao.LongIdTable` -> `org.jetbrains.exposed.dao.id.LongIdTable` + +`org.jetbrains.exposed.dao.UUIDTable` -> `org.jetbrains.exposed.dao.id.UUIDTable` + +As this change could hardly affect large projects we move existing classes with their original package to `exposed-dao` module, deprecate them and prepare migration steps with IntelliJ IDEA: +1. Find any of deprecated tables in your project and use `Alt+Enter` quick-fix with "Replace in the whole project". Repeat for all *IdTables. +2. Run "Edit > Find > Replace in Path..." +3. Enter `import org.jetbrains.exposed.dao.*` in a search field +4. Enter in a replace field: +``` +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.* +``` +5. Run "Replace" in the whole project. +6. Search for `DateColumnType`, `date()`, `datetime()` and re-import them manually. +7. Run "Optimize imports" on all files in a current change list. +8. Run "Build" and fix the rest problems. diff --git a/zh_CN/Transactions.md b/zh_CN/Transactions.md new file mode 100644 index 0000000..69e60a1 --- /dev/null +++ b/zh_CN/Transactions.md @@ -0,0 +1,165 @@ +## 概述 + +Exposed 中的 CRUD 操作必须在一个事务 _transaction_ 中调用. 事务封装了一组 DSL 操作. 要使用默认参数创建和执行事务,只需将函数代码块(function block)传递给事务函数(`transaction` function): +```kotlin +transaction { + // DSL/DAO operations go here +} +``` +多个事务在当前线程上同步执行, 所以他们会 _阻塞_ 你的应用程序的其他部分! 如果您需要异步执行事务,请考虑在单独的 _线程_ (Thread)上运行它. + +### 访问返回值 + +尽管您可以在事务块(transaction block)中修改代码中的变量,但 `事务transaction` 支持直接返回值,从而实现不变性. + +```kotlin +val jamesList = transaction { + Users.select { Users.firstName eq "James" }.toList() +} +// jamesList is now a List containing Users data +``` +*注意:* 如果您不直接加载 `Blob` 和 `text` 字段, 那么它们在事务之外将不可用. 对与 `text` 字段, 在定义表的时,您可以使用 `eagerLoading` 参数, 来使得 `text`字段在事务之外可用. +```kotlin +// without eagerLoading +val idsAndContent = transaction { + Documents.selectAll().limit(10).map { it[Documents.id] to it[Documents.content] } +} + +// with eagerLoading for text fields +object Documents : Table() { + ... + val content = text("content", eagerLoading = true) +} + +val documentsWithContent = transaction { + Documents.selectAll().limit(10) +} +``` + +### 使用多个数据库 +_从 0.10.1 版本开始支持此功能_ + +当您想使用多个不同的数据库时, 您必须存储 `Database.connect()` 返回的数据库引用(reference)并将其作为第一个参数提供给 `事务transaction` 函数. +```kotlin +val db1 = connect("jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;", "org.h2.Driver", "root", "") +val db2 = connect("jdbc:h2:mem:db2;DB_CLOSE_DELAY=-1;", "org.h2.Driver", "root", "") +transaction(db1) { + ... + val result = transaction(db2) { + Table1.select{ }.map { it[Table1.name] } + } + + val count = Table2.select { Table2.name inList result }.count() +} +``` + +实体 (见 [[DAO API|DAO]] 页面) `stick` 加载该实体的事务. 这意味着所有更改都保留在同一个数据库中和禁止哪些跨数据库引用并会抛出异常. + +### 设置默认数据库 +不带参数的 `事务块(transaction block)` 将使用默认数据库. +与 0.10.1 之前一样,这将是最新 _已连接的_ 数据库. +也可以显式设置默认数据库. +```kotlin +val db = Database.connect() +TransactionManager.defaultDatabase = db +``` + +### 使用嵌套事务 +从 Exposed 0.16.1 开始,可以使用嵌套事务。要启用此功能,您应该将所需数据库实例上的 `useNestedTransactions` 设置为 `true`. + +任何发生在 `事务块transaction block` 中的异常将不会回滚这个事务, 而只会回滚当前事务中的代码. +Exposed 使用 SQL `SAVEPOINT` 功能, 在 `事务块transaction block` 开始时标记当前事务,并在退出时释放它. + +使用 savepoint 可能会影响性能,因此请阅读有关您使用的 DBMS 的文档以获取更多详细信息. + +```kotlin +val db = Database.connect() +db.useNestedTransactions = true + +transaction { + FooTable.insert{ it[id] = 1 } + + var idToInsert = 0 + transaction { // nested transaction + idToInsert++ + // On the first insert it will fail with unique constraint exception and will rollback to the `nested transaction` and then insert a new record with id = 2 + FooTable.insert{ it[id] = idToInsert } + } +} +``` + +### 使用协程 Coroutines +如今, 非阻塞和异步代码很流行.Kotlin 的协程[Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)为您提供了一种命令式的异步代码编写方式。大多数 Kotlin 框架(如 [ktor](https://ktor.io))都内置了对 Coroutines 的支持,而 Exposed 主要是阻塞的. + +为什么呢? + +因为 Exposed 使用 JDBC-api 与在阻塞 api 时代设计的数据库进行交互. 更重要的是,Exposed 将一些值存储在线程局部变量(thread-local)中,而协程可以(并且将)在不同的线程中执行. + +从 Exposed 0.15.1 开始,有一些桥接函数可以让您安全地在`suspend` blocks 中与 Exposed 进行交互: `newSuspendedTransaction/Transaction.suspendedTransaction` 具有与阻塞`事务transaction`函数相同的参数,但允许您提供 `CoroutineDispatcher` 函数将在其中执行. 如果未提供上下文,您的代码将在当前 `coroutineContext` 中执行. + +示例用法看起来如下: +```kotlin +runBlocking { + transaction { + SchemaUtils.create(FooTable) // Table表将在当前线程被创建 + + newSuspendedTransaction(Dispatchers.Default) { + FooTable.insert { it[id] = 1 } // 此插入操作,将在默认调度程序(Default dispatcher)的线程之一中执行 + + suspendedTransaction { + val id = FooTable.select { FooTable.id eq 1 }.single()()[FooTable.id] // 此查询操作也将使用相同事务,在默认调度程序(Default dispatcher)的某个线程上执行 + } + } + + val result = newSuspendedTransaction(Dispatchers.IO) { + FooTable.select { FooTable.id eq 1 }.single()[H2Tests.Testing.id] // 此查询操作将使用同一事务,在 IO 调度程序(IO dispatcher)的某个线程上执行 + } + } +} + +``` + +请注意这样的代码仍然是阻塞的(因为它仍然使用 JDBC)并且您不应该尝试在多个线程之间共享事务,因为它会导致不明确的行为. + +如果您希望异步执行某些代码并稍后在代码中使用结果,请查看 `suspendedTransactionAsync` 函数。 + +```kotlin +val launchResult = suspendedTransactionAsync(Dispatchers.IO, db = db) { + FooTable.insert{} + + FooTable.select { FooTable.id eq 1 }.singleOrNull()?.getOrNull(Testing.id) +} + +println("Result: " + (launchResult.await() ?: -1)) + +``` + +此函数将接收与上面的 `newSuspendedTransaction` 相同的参数,但返回 `Deferred`,您可以 `等待await` 以获取您想要的结果. + +有时候 `CoroutineDispatcher` 可以更改查询执行顺序,`suspendedTransactionAsync` 始终在新事务中执行,以防止并发问题。 + +### 高级参数及其用法 + +对于特定功能,可以使用以下附加参数创建事务: `transactionIsolation`, `repetitionAttempts` and `db`: + +```kotlin +transaction (Connection.TRANSACTION_SERIALIZABLE, 2) { + // DSL/DAO operations go here +} +``` +**事务隔离Transaction Isolation:** 此参数是 SQL 标准中定义的参数,指定当多个事务在数据库上同时执行时应该发生什么. 该值不直接影响 Exposed 操作,而是发送到数据库,期望遵守. `java.sql.Connection` 定义了允许的值, 如下: +* **TRANSACTION_NONE**: 不支持事务. +* **TRANSACTION_READ_UNCOMMITTED**: 最宽松的设置。允许来自一个事务的未提交更改影响另一个事务中的读取(“脏读”). +* **TRANSACTION_READ_COMMITTED**: 此设置可防止发生脏读,但仍允许发生不可重复读。不可重复读取是指一个事务(“事务 A”)从数据库中读取一行,另一个事务(“事务 B”)更改了该行,而事务 A 再次读取该行,导致不一致. +* **TRANSACTION_REPEATABLE_READ**: 公开事务的默认设置。防止脏读和不可重复读,但仍然允许幻读。幻读是指事务(“事务 A”)通过 WHERE 子句选择行列表,另一个事务(“事务 B”)对满足事务 A 的 WHERE 子句的行执行 INSERT 或 DELETE,事务 A 选择再次使用相同的 WHERE 子句,导致不一致. +* **TRANSACTION_SERIALIZABLE**: 最严格的设置。防止脏读、不可重复读和幻读. + +> 不可重复读和幻读到底有什么区别呢? +> (1) 不可重复读是读取了其他事务更改的数据,针对update操作 +> 解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。 +> (2) 幻读是读取了其他事务新增的数据,针对insert和delete操作 +> 解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。 + +**数据库db** 参数是可选的,用于选择应该结算事务的数据库(见上文). + +或者... 返回 [主页](Home.md) \ No newline at end of file