mirror of
https://github.com/jlengrand/exposed-wiki.git
synced 2026-03-10 08:11:18 +00:00
Schema + Sequence docs + minor improvements.
This commit is contained in:
55
DAO.md
55
DAO.md
@@ -17,18 +17,16 @@
|
||||
* [Entities mapping](#entities-mapping)
|
||||
* [Fields transformation](#fields-transformation)
|
||||
***
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
The DAO (Data Access Object) API of Exposed, is similar to ORM frameworks like Hibernate with a Kotlin-specific API.
|
||||
A DB table is represented by an `object` inherited from `org.jetbrains.exposed.sql.Table` like this:
|
||||
```kotlin
|
||||
object StarWarsFilms : Table() {
|
||||
val id: Column<Int> = integer("id").autoIncrement().primaryKey()
|
||||
val id: Column<Int> = integer("id").autoIncrement()
|
||||
val sequelId: Column<Int> = integer("sequel_id").uniqueIndex()
|
||||
val name: Column<String> = varchar("name", 50)
|
||||
val director: Column<String> = varchar("director", 50)
|
||||
override val primaryKey = PrimaryKey(id, name = "PK_StarWarsFilms_Id") // PK_StarWarsFilms_Id is optional here
|
||||
}
|
||||
```
|
||||
Tables that contain an `Int` id with the name `id` can be declared like this:
|
||||
@@ -51,7 +49,6 @@ An entity instance or a row in the table is defined as a class instance:
|
||||
```kotlin
|
||||
class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
|
||||
companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
|
||||
|
||||
var sequelId by StarWarsFilms.sequelId
|
||||
var name by StarWarsFilms.name
|
||||
var director by StarWarsFilms.director
|
||||
@@ -74,7 +71,6 @@ val movies = StarWarsFilm.find { StarWarsFilms.sequelId eq 8 }
|
||||
val movie = StarWarsFilm.findById(5)
|
||||
```
|
||||
* For a list of available predicates see [DSL Where expression](https://github.com/JetBrains/Exposed/wiki/DSL#where-expression).
|
||||
|
||||
Read a value from a property similar to any property in a Kotlin class:
|
||||
```kotlin
|
||||
val name = movie.name
|
||||
@@ -98,7 +94,6 @@ movie.name = "Episode VIII – The Last Jedi"
|
||||
```kotlin
|
||||
movie.delete()
|
||||
```
|
||||
|
||||
## Referencing
|
||||
### many-to-one reference
|
||||
Let's say you have this table:
|
||||
@@ -106,10 +101,8 @@ Let's say you have this table:
|
||||
object Users: IntIdTable() {
|
||||
val name = varchar("name", 50)
|
||||
}
|
||||
|
||||
class User(id: EntityID<Int>): IntEntity(id) {
|
||||
companion object : IntEntityClass<User>(Users)
|
||||
|
||||
var name by Users.name
|
||||
}
|
||||
```
|
||||
@@ -120,10 +113,8 @@ object UserRatings: IntIdTable() {
|
||||
val film = reference("film", StarWarsFilms)
|
||||
val user = reference("user", Users)
|
||||
}
|
||||
|
||||
class UserRating(id: EntityID<Int>): IntEntity(id) {
|
||||
companion object : IntEntityClass<UserRating>(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
|
||||
@@ -137,7 +128,6 @@ Now if you wanted to get all the ratings for a film, you could do that by using
|
||||
```kotlin
|
||||
class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
|
||||
companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
|
||||
|
||||
...
|
||||
val ratings by UserRating referrersOn UserRatings.film // make sure to use val and referrersOn
|
||||
...
|
||||
@@ -155,10 +145,8 @@ object UserRatings: IntIdTable() {
|
||||
val secondUser = reference("second_user", Users).nullable() // this reference is nullable!
|
||||
...
|
||||
}
|
||||
|
||||
class UserRating(id: EntityID<Int>): IntEntity(id) {
|
||||
companion object : IntEntityClass<UserRating>(UserRatings)
|
||||
|
||||
...
|
||||
var secondUser by User optionalReferencedOn UserRatings.secondUser // use optionalReferencedOn for nullable references
|
||||
...
|
||||
@@ -166,7 +154,6 @@ class UserRating(id: EntityID<Int>): IntEntity(id) {
|
||||
```
|
||||
Now `secondUser` will be a nullable field.
|
||||
Of course, you can still use `referrersOn`.
|
||||
|
||||
### many-to-many reference
|
||||
In some cases, a many-to-many reference may be required.
|
||||
Let's assume you want to add a reference to the following Actors table to the StarWarsFilm class:
|
||||
@@ -175,10 +162,8 @@ object Actors: IntIdTable() {
|
||||
val firstname = varchar("firstname", 50)
|
||||
val lastname = varchar("lastname", 50)
|
||||
}
|
||||
|
||||
class Actor(id: EntityID<Int>): IntEntity(id) {
|
||||
companion object : IntEntityClass<Actor>(Actors)
|
||||
|
||||
var firstname by Actors.firstname
|
||||
var lastname by Actors.lastname
|
||||
}
|
||||
@@ -186,15 +171,15 @@ class Actor(id: EntityID<Int>): IntEntity(id) {
|
||||
Create an additional intermediate table to store the references:
|
||||
```kotlin
|
||||
object StarWarsFilmActors : Table() {
|
||||
val starWarsFilm = reference("starWarsFilm", StarWarsFilms).primaryKey(0)
|
||||
val actor = reference("actor", Actors).primaryKey(1)
|
||||
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
|
||||
}
|
||||
```
|
||||
Add a reference to `StarWarsFilm`:
|
||||
```kotlin
|
||||
class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
|
||||
companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
|
||||
|
||||
...
|
||||
var actors by Actor via StarWarsFilmActors
|
||||
...
|
||||
@@ -221,7 +206,6 @@ val film = transaction {
|
||||
director = "Rian Johnson"
|
||||
}
|
||||
}
|
||||
|
||||
//create actor
|
||||
val actor = transaction {
|
||||
Actor.new {
|
||||
@@ -229,7 +213,6 @@ val actor = transaction {
|
||||
lastname = "Ridley"
|
||||
}
|
||||
}
|
||||
|
||||
//add reference
|
||||
transaction {
|
||||
film.actors = SizedCollection(listOf(actor))
|
||||
@@ -248,13 +231,11 @@ object NodeToNodes : Table() {
|
||||
}
|
||||
class Node(id: EntityID<Int>) : IntEntity(id) {
|
||||
companion object : IntEntityClass<Node>(NodeTable)
|
||||
|
||||
var name by NodeTable.name
|
||||
var parents by Node.via(NodeToNodes.child, NodeToNodes.parent)
|
||||
var children by Node.via(NodeToNodes.parent, NodeToNodes.child)
|
||||
}
|
||||
```
|
||||
|
||||
As you can see `NodeToNodes` columns target only `NodeTable` and another version of `via` function were used.
|
||||
Now you can create a hierarchy of nodes.
|
||||
```kotlin
|
||||
@@ -263,43 +244,26 @@ 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
|
||||
```
|
||||
Beware that you can't setup references inside a `new` block as an entity is not created yet and id is not defined to be referenced to.
|
||||
|
||||
### Eager Loading
|
||||
**Available since 0.13.1**.
|
||||
References in Exposed are lazily loaded, meaning queries to fetch the data for the reference are made at the moment the reference is first utilised. For scenarios wherefore you know you will require references ahead of time, Exposed can eager load them at the time of the parent query, this is prevents the classic "N+1" problem as references can be aggregated and loaded in a single query.
|
||||
|
||||
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, for example if Actors had a rating reference you could:
|
||||
|
||||
```kotlin
|
||||
|
||||
StarWarsFilm.findById(1).load(StarWarsFilm::actors, Actor::rating)
|
||||
|
||||
```
|
||||
|
||||
Similarly you can eager load references on Collections of DAO's such as Lists and SizedIterables, for collections you can use the with function in the same fashion as before, passing the DAO's references as KProperty's.
|
||||
|
||||
```kotlin
|
||||
|
||||
StarWarsFilm.all().with(StarWarsFilm::actors)
|
||||
|
||||
```
|
||||
|
||||
NOTE: References that are eagerly loaded are stored inside the Transaction Cache, this means that they are not available in other transactions and thus must be loaded and referenced inside the same transaction.
|
||||
|
||||
|
||||
## Advanced CRUD operations
|
||||
### Read entity with a join to another table
|
||||
Let's imagine that you want to find all users who rated second SW film with more than 5.
|
||||
@@ -315,10 +279,8 @@ After that all we have to do is to "wrap" a result with User entity:
|
||||
```kotlin
|
||||
val users = User.wrapRows(query).toList()
|
||||
```
|
||||
|
||||
### Auto-fill created and updated columns on entity change
|
||||
See example by @PaulMuriithi [here](https://github.com/PaulMuriithi/ExposedDatesAutoFill/blob/master/src/main/kotlin/app/Models.kt).
|
||||
|
||||
### Use queries as expressions
|
||||
Imagine that you want to sort cities by how many users each city has. In order to do so, you can write a sub-query which counts users in each city and order by that number. Though in order to do so you'll have to convert `Query` to `Expression`. This can be done using `wrapAsExpression` function:
|
||||
```kotlin
|
||||
@@ -327,25 +289,20 @@ val expression = wrapAsExpression<Int>(Users
|
||||
.select {
|
||||
Cities.id eq Users.cityId
|
||||
})
|
||||
|
||||
val cities = Cities
|
||||
.selectAll()
|
||||
.orderBy(expression, SortOrder.DESC)
|
||||
.toList()
|
||||
```
|
||||
|
||||
## Entities mapping
|
||||
|
||||
### Fields transformation
|
||||
As databases could store only basic types like integers and strings it's not always conveniently to keep the same simplicity on DAO level.
|
||||
Sometimes you may want to make some transformations like parsing json from a varchar column or get some value from a cache based on value from a database.
|
||||
|
||||
In that case the preferred way is to use column transformations. Assume that we want to define unsigned integer field on Entity, but Exposed doesn't have such column type yet.
|
||||
```kotlin
|
||||
object TableWithUnsignedInteger : IntIdTable() {
|
||||
val uint = integer("uint")
|
||||
}
|
||||
|
||||
class EntityWithUInt : IntEntity() {
|
||||
var uint: UInt by TableWithUnsignedInteger.uint.transform({ it.toInt() }, { it.toUInt() })
|
||||
|
||||
@@ -353,8 +310,6 @@ class EntityWithUInt : IntEntity() {
|
||||
}
|
||||
```
|
||||
`transform` function accept two lambdas to convert values to and from an original column type.
|
||||
|
||||
After that in your code you'll be able to put only `UInt` instances into `uint` field.
|
||||
It still possible to insert/update values with negative integers via DAO, but your business code becomes much cleaner.
|
||||
|
||||
Please keep in mind what such transformations will aqure on every access to a field what means that you should avoid heavy transformations here.
|
||||
88
DSL.md
88
DSL.md
@@ -12,24 +12,21 @@
|
||||
* [Limit](#limit)
|
||||
* [Join](#join)
|
||||
* [Alias](#alias)
|
||||
* [Schema](#schema)
|
||||
* [Sequence](#sequence)
|
||||
* [Batch Insert](#batch-insert)
|
||||
* [Insert From Select](#insert-from-select)
|
||||
|
||||
|
||||
|
||||
***
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
The DSL (Domain Specific Language) API of Exposed, is similar to actual SQL statements with type safety that Kotlin offers.
|
||||
A DB table is represented by an `object` inherited from `org.jetbrains.exposed.sql.Table` like this:
|
||||
```kotlin
|
||||
object StarWarsFilms : Table() {
|
||||
val id: Column<Int> = integer("id").autoIncrement().primaryKey()
|
||||
val id: Column<Int> = integer("id").autoIncrement()
|
||||
val sequelId: Column<Int> = integer("sequel_id").uniqueIndex()
|
||||
val name: Column<String> = varchar("name", 50)
|
||||
val director: Column<String> = varchar("director", 50)
|
||||
override val primaryKey = PrimaryKey(id, name = "PK_StarWarsFilms_Id") // PK_StarWarsFilms_Id is optional here
|
||||
}
|
||||
```
|
||||
Tables that contains `Int` id with the name `id` can be declared like this:
|
||||
@@ -67,7 +64,6 @@ val filmAndDirector = StarWarsFilms.
|
||||
it[StarWarsFilms.name] to it[StarWarsFilms.director]
|
||||
}
|
||||
```
|
||||
|
||||
If you want to select only distinct value then use `withDistinct()` function:
|
||||
```kotlin
|
||||
val directors = StarWarsFilms.
|
||||
@@ -77,14 +73,12 @@ val directors = StarWarsFilms.
|
||||
it[StarWarsFilms.director]
|
||||
}
|
||||
```
|
||||
|
||||
### Update
|
||||
```kotlin
|
||||
StarWarsFilms.update ({ StarWarsFilms.sequelId eq 8 }) {
|
||||
it[StarWarsFilms.name] = "Episode VIII – The Last Jedi"
|
||||
}
|
||||
```
|
||||
|
||||
If you want to update column value with some expression like increment use `update` function or setter:
|
||||
```kotlin
|
||||
StarWarsFilms.update({ StarWarsFilms.sequelId eq 8 }) {
|
||||
@@ -95,12 +89,10 @@ StarWarsFilms.update({ StarWarsFilms.sequelId eq 8 }) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Delete
|
||||
```kotlin
|
||||
StarWarsFilms.deleteWhere { StarWarsFilms.sequelId eq 8 }
|
||||
```
|
||||
|
||||
## Where expression
|
||||
Query expression (where) expects a boolean operator (ie: `Op<Boolean>`).
|
||||
Allowed conditions are:
|
||||
@@ -144,7 +136,6 @@ val condition = when {
|
||||
Op.build { StarWarsFilms.sequelId eq sequelId }
|
||||
else -> null
|
||||
}
|
||||
|
||||
val query = condition?.let { StarWarsFilms.select(condition) } ?: StarWarsFilms.selectAll()
|
||||
```
|
||||
or
|
||||
@@ -160,19 +151,15 @@ val query = when {
|
||||
}
|
||||
```
|
||||
This is a very primitive example, but you should get the main idea about the problem.
|
||||
|
||||
Now let's try to write the same query in a more simple way (`andWhere` function available since 0.10.5):
|
||||
```Kotlin
|
||||
val query = StarWarsFilms.selectAll()
|
||||
|
||||
directorName?.let {
|
||||
query.andWhere { StarWarsFilms.director eq it }
|
||||
}
|
||||
|
||||
sequelId?.let {
|
||||
query.andWhere { StarWarsFilms.sequelId eq it }
|
||||
}
|
||||
|
||||
```
|
||||
But what if we have conditionaly select from another table and want to join it only when condition is true?
|
||||
You have to use `adjustColumnSet` and `adjustSlice` functions (available since 0.8.1) which allows to extend and modify `join` and `slice` parts of a query (see kdoc on that functions):
|
||||
@@ -225,7 +212,6 @@ object StarWarsFilms : IntIdTable() {
|
||||
val name: Column<String> = varchar("name", 50)
|
||||
val director: Column<String> = varchar("director", 50)
|
||||
}
|
||||
|
||||
object Players : Table() {
|
||||
val sequelId: Column<Int> = integer("sequel_id").uniqueIndex()
|
||||
val name: Column<String> = varchar("name", 50)
|
||||
@@ -239,14 +225,12 @@ Join to count how many players play in each movie:
|
||||
.groupBy(StarWarsFilms.name)
|
||||
```
|
||||
* In case there is a foreign key it is possible to replace `select{}` with `selectAll()`
|
||||
|
||||
Same example using the full syntax:
|
||||
```kotlin
|
||||
Players.join(StarWarsFilms, JoinType.INNER, additionalConstraint = {StarWarsFilms.sequelId eq Players.sequelId})
|
||||
.slice(Players.name.count(), StarWarsFilms.name)
|
||||
.groupBy(StarWarsFilms.name)
|
||||
```
|
||||
|
||||
## Alias
|
||||
Aliases allow preventing ambiguity between field names and table names.
|
||||
Use the aliased var instead of original one:
|
||||
@@ -254,7 +238,6 @@ Use the aliased var instead of original one:
|
||||
val filmTable1 = StarWarsFilms.alias("ft1")
|
||||
filmTable1.selectAll() // can be used in joins etc'
|
||||
```
|
||||
|
||||
Also, aliases allow you to use the same table in a join multiple times:
|
||||
```Kotlin
|
||||
val sequelTable = StarWarsFilms.alias("sql")
|
||||
@@ -264,23 +247,75 @@ val originalAndSequelNames = StarWarsFilms
|
||||
.selectAll()
|
||||
.map { it[StarWarsFilms.name] to it[sequelTable[StarWarsFilms.name]] }
|
||||
```
|
||||
|
||||
And they can be used when selecting from sub-queries:
|
||||
```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
|
||||
You can create a schema or drop an existing one:
|
||||
```Kotlin
|
||||
val schema = Schema("my_schema") // my_schema is the schema name.
|
||||
// Creates a Schema
|
||||
SchemaUtils.createSchema(schema)
|
||||
// Drops a Schema
|
||||
SchemaUtils.dropSchema(schema)
|
||||
```
|
||||
Also, you can specify the schema owner like this (some databases require the explicit owner) :
|
||||
```Kotlin
|
||||
val schema = Schema("my_schema", authorization = "owner")
|
||||
```
|
||||
If you have many schemas and you want to set a default one, you can use:
|
||||
```Kotlin
|
||||
SchemaUtils.setSchema(schema)
|
||||
```
|
||||
## Sequence
|
||||
If you want to use Sequence, Exposed allows you to:
|
||||
### Define a Sequence
|
||||
```Kotlin
|
||||
val myseq = Sequence("my_sequence") // my_sequence is the sequence name.
|
||||
```
|
||||
Several parameters can be specified to control the properties of the sequence:
|
||||
```Kotlin
|
||||
private val myseq = Sequence(
|
||||
name = "my_sequence",
|
||||
startWith = 4,
|
||||
incrementBy = 2,
|
||||
minValue = 1,
|
||||
maxValue = 10,
|
||||
cycle = true,
|
||||
cache = 20
|
||||
)
|
||||
```
|
||||
### Create and Drop a Sequence
|
||||
```Kotlin
|
||||
// Creates a sequence
|
||||
SchemaUtils.createSequence(myseq)
|
||||
// Drops a sequence
|
||||
SchemaUtils.dropSequence(myseq)
|
||||
```
|
||||
### Use the NextVal function
|
||||
You can use the nextVal function like this:
|
||||
```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
|
||||
Batch Insert allow mapping a list of entities into DB raws in one sql statement. It is more efficient than inserting one by one as it initiates only one statement. Here is an example:
|
||||
```kotlin
|
||||
@@ -290,10 +325,7 @@ val allCitiesID = cities.batchInsert(cityNames) { name ->
|
||||
}
|
||||
```
|
||||
*NOTE:* The `batchInsert` function will still create multiple `INSERT` statements when interacting with your database. You most likely want to couple this with the `rewriteBatchedInserts=true` (or `rewriteBatchedStatements=true` option of your relevant JDBC driver, which will convert those into a single bulkInsert.
|
||||
|
||||
You can find the documentation for this option for MySQL [here](https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-configuration-properties.html) and PostgreSQL [here](https://jdbc.postgresql.org/documentation/94/connect.html).
|
||||
|
||||
|
||||
## Insert From Select
|
||||
If you want to use `INSERT INTO ... SELECT ` SQL clause try Exposed analog `Table.insert(Query)`.
|
||||
```kotlin
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
### Working with DataBase and DataSource
|
||||
Every database access using Exposed is starting by obtaining a connection and creating a transaction.
|
||||
|
||||
To get a connection:
|
||||
|
||||
```kotlin
|
||||
val db = Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
|
||||
```
|
||||
|
||||
It is also possible to provide `javax.sql.DataSource` for advanced behaviors such as connection pooling:
|
||||
```kotlin
|
||||
val db = Database.connect(dataSource)
|
||||
```
|
||||
|
||||
* Note: Starting Exposed 0.10 executing this code more than once per db will create leaks in your application, hence it is recommended to store it for later use.
|
||||
|
||||
For example:
|
||||
|
||||
```kotlin
|
||||
object DbSettings {
|
||||
val db by lazy {
|
||||
@@ -23,9 +17,7 @@ object DbSettings {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DataSource
|
||||
|
||||
* PostgreSQL
|
||||
```kotlin
|
||||
Database.connect("jdbc:postgresql://localhost:12346/test", driver = "org.postgresql.Driver",
|
||||
@@ -33,6 +25,13 @@ Database.connect("jdbc:postgresql://localhost:12346/test", driver = "org.postgre
|
||||
//Gradle
|
||||
compile("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
|
||||
compile("com.impossibl.pgjdbc-ng", "pgjdbc-ng", "0.8.3")
|
||||
```
|
||||
* MySQL
|
||||
```kotlin
|
||||
Database.connect("jdbc:mysql://localhost:3306/test", driver = "com.mysql.jdbc.Driver",
|
||||
@@ -71,4 +70,4 @@ Database.connect("jdbc:sqlserver://localhost:32768;databaseName=test", "com.micr
|
||||
user = "root", password = "your_pwd")
|
||||
//Gradle
|
||||
compile("com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre7")
|
||||
```
|
||||
```
|
||||
Reference in New Issue
Block a user