kotlin數據庫
I want to show you how to use one of my favorite database choices for Kotlin applications. Namely, Xodus. Why do I like using Xodus for Kotlin applications? Well, here are a couple of its selling points:
我想向您展示如何在Kotlin應用程序中使用我最喜歡的數據庫選擇之一。 即Xodus 。 為什么我喜歡在Kotlin應用程序中使用Xodus? 好吧,這里有幾個賣點:
Transactional
交易性
Embedded
嵌入式的
Schema-less
無模式
Pure JVM-based
基于純JVM
Has an additional Kotlin DSL — Xodus-DNQ.
還有一個額外的Kotlin DSL — Xodus-DNQ 。
What does this mean to you?
這對您意味著什么?
- ACID on-board — all database operations are atomic, consistent, isolated, and durable. 板載ACID —所有數據庫操作都是原子的,一致的,隔離的且持久的。
- No need to manage an external database — everything is inside your application. 無需管理外部數據庫-一切都在應用程序內部。
- Painless refactorings — if you need to add a couple of properties you won’t have to then rebuild the tables. 無痛重構–如果您需要添加幾個屬性,則無需重新構建表。
- Cross-platform database — Xodus can run on any platform that can run a Java virtual machine. 跨平臺數據庫-Xodus可以在可以運行Java虛擬機的任何平臺上運行。
- Kotlin language benefits — take the best from using types, nullable values and delegates for properties declaration and constraints description. Kotlin語言的好處-充分利用類型,可空值和委托進行屬性聲明和約束描述。
Xodus is an open-source product from JetBrains. Originally it was developed for internal use, but it was subsequently released to the public back in July 2016. YouTrack issue tracker and Hub team tool use it as their data storage. If you are curious about the performance, you can check out the benchmarks. As for the real-life example, take a look at the JetBrains YouTrack installation: which at the time of writing has over 1,6 million issues, and that is not even taking into account all the comments and time tracking entries all stored there.
Xodus是JetBrains的開源產品。 它最初是為內部使用而開發的,但后來于2016年7月發布給公眾。YouTrack問題跟蹤程序和Hub團隊工具將其用作數據存儲。 如果您對性能感到好奇,可以查看基準測試 。 對于真實的示例,請看一下JetBrains YouTrack的安裝 :在撰寫本文時,它已發行了超過1,600萬個問題,并且甚至沒有考慮所有存儲在其中的注釋和時間跟蹤條目。
Xodus-DNQ is a Kotlin library that contains the data definition language and queries for Xodus. It was also developed first as a part of the product and then later released publicly. YouTrack and Hub both use it for persistent layer definition.
Xodus-DNQ是Kotlin庫,其中包含數據定義語言和Xodus查詢。 它也首先作為產品的一部分進行開發,然后再公開發布。 YouTrack和Hub都將其用于持久層定義。
建立 (Setup)
Let’s write a small application which stores books and their authors.
讓我們編寫一個存儲書及其作者的小應用程序。
I will use Gradle as a build tool, as it helps simplify all the dependencies management and project compilation stuff. If you have never worked with Gradle, I recommend taking a look at the official guides they have on installation and creating new builds.
我將Gradle用作構建工具,因為它有助于簡化所有依賴項管理和項目編譯的工作。 如果您從未使用過Gradle,建議您閱讀他們在安裝和創建新版本方面的官方指南。
So first, we need to start by creating a new directory for our example, and then run gradle init
there. This will initialize the project structure and add some directories and build scripts.
因此,首先,我們需要為示例創建一個新目錄,然后在gradle init
運行gradle init
。 這將初始化項目結構,并添加一些目錄和構建腳本。
Now, create a bookstore.kt
file in src/main/kotlin
directory. Fill it with the never-going-out-of-fashion classics:
現在,在src/main/kotlin
目錄中創建一個bookstore.kt
文件。 用永不過時的經典裝滿它:
fun main() {println("Hello World")
}
Then, update the build.gradle
file using code similar to this:
然后,使用類似于以下代碼的代碼更新build.gradle
文件:
plugins {id 'application'id 'org.jetbrains.kotlin.jvm' version '1.3.21'
}
group 'mariyadavydova'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {kotlinOptions {jvmTarget = "1.8"}
}
repositories {mavenCentral()
}
dependencies {implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.21'implementation 'org.jetbrains.xodus:dnq:1.2.420'
}
mainClassName = 'BookstoreKt'
There are a few things that are happening here:
這里發生了一些事情:
- We add the Kotlin plugin and claim that the compilation output is targeted for JVM 1.8. 我們添加了Kotlin插件,并聲稱編譯輸出針對JVM 1.8。
- We add dependencies to the Kotlin standard library and Xodus-DNQ. 我們向Kotlin標準庫和Xodus-DNQ添加依賴項。
We also add the application plugin and define the main class. In the case of the Kotlin application, we do not have a class with a static method main, like in Java. Instead, we have to define a standalone function
main
. However, under the hood, Kotlin still makes a class containing this function, and the name of the class is generated from the name of the file. For example,‘bookstore.kt’
makes‘BookstoreKt’
.我們還添加了應用程序插件并定義了主類。 在Kotlin應用程序的情況下,我們沒有像Java這樣的帶有靜態方法main的類。 相反,我們必須定義一個獨立的函數
main
。 但是,在幕后,Kotlin仍然制作了一個包含此功能的類,并且該類的名稱是從文件名生成的。 例如,'bookstore.kt'
成為'BookstoreKt'
。
We can actually safely remove settings.gradle
, as we don’t need it in this example.
實際上,我們可以安全地刪除settings.gradle
,因為在此示例中不需要它。
Now, execute ./gradlew run
; you should see “Hello World” in your console:
現在,執行./gradlew run
; 您應該在控制臺中看到“ Hello World”:
> Task :run
Hello World
資料定義 (Data definition)
Xodus provides three different ways to deal with data, namely Environments, Entity Stores and the Virtual File System. However, Xodus-DNQ supports only the Entity Stores, which describe a data model as a set of typed entities with named properties (attributes) and named entity links (relations). It is similar to rows in the SQL database table.
Xodus提供了三種不同的數據處理方式,即環境 , 實體存儲和虛擬文件系統 。 但是,Xodus-DNQ僅支持實體存儲,它們將數據模型描述為一組具有命名屬性(屬性)和命名實體鏈接(關系)的類型化實體。 它類似于SQL數據庫表中的行。
As my goal is to demonstrate how simple it is to operate Xodus via Kotlin DSL, I’ll stick to the entity types API for this story.
因為我的目標是演示通過Kotlin DSL操作Xodus多么簡單,所以我將堅持本故事的實體類型API。
Let’s start with an XdAuthor
:
讓我們從XdAuthor
開始:
class XdAuthor(entity: Entity) : XdEntity(entity) {companion object : XdNaturalEntityType<XdAuthor>()
var name by xdRequiredStringProp()var countryOfBirth by xdStringProp()var yearOfBirth by xdRequiredIntProp()var yearOfDeath by xdNullableIntProp()val books by xdLink0_N(XdBook::authors)
}
From my point of view, this declaration looks pretty natural: we say that our authors always have names and year of birth, may have country of birth and year of death (the latter is irrelevant for the currently living authors); also, there could be any number of books from each author in our bookstore.
從我的角度來看,這種說法看起來很自然:我們說我們的作者總是有名字和出生年份,可能有出生國家和死亡年份(后者與當前在世的作者無關); 此外,我們書店中的每位作者都有多少本書籍。
There are several things worth mentioning in this code snippet:
此代碼段中有幾件事值得一提:
The
companion
object declares theentityType
property for each class (which is used by the database engine).companion
對象為每個類聲明entityType
屬性(數據庫引擎使用該屬性)。- The data fields are declared with the help of the delegates, which encapsulate the types, properties, and constraints for these fields. 數據字段是在委托的幫助下聲明的,這些委托封裝了這些字段的類型,屬性和約束。
Links are values, not variables; that is, you don’t set them with
=
, but access them as a collection. (Pay attention toval books
versusvar name
; I spent quite a bit of time trying to figure out why the compilation withvar books
kept failing.)鏈接是值,而不是變量; 也就是說,您不必將其設置為
=
,而是將其作為集合進行訪問。 (請注意val books
而不是var name
;我花了很多時間試圖弄清楚為什么使用var books
的編譯總是失敗。)
The second type is an XdBook
:
第二種是XdBook
:
class XdBook(entity: Entity) : XdEntity(entity) {companion object : XdNaturalEntityType<XdBook>()
var title by xdRequiredStringProp()var year by xdNullableIntProp()val genres by xdLink1_N(XdGenre)val authors : XdMutableQuery<XdAuthor> by xdLink1_N(XdAuthor::books)
}
The thing to pay attention to here is the declaration of the authors
’ field:
這里要注意的是authors
字段的聲明:
Notice that we write down the type explicitly (
XdMutableQuery<XdAuth
or>). For the bidirectional link, we have to help the compiler to resolve the types by leaving a hint on one of the link ends.請注意,我們明確記錄了類型(
XdMutableQuery<XdAuth
或>)。 對于雙向鏈接,我們必須通過在鏈接端之一上留下提示來幫助編譯器解析類型。Also, notice that
XdAuthor::books
referencesXdBook::authors
and vice versa. We have to add these references if we want the link to be bidirectional; so if you add an author to the book, the book will appear in the list of the books of this author, and vice versa.另外,請注意
XdAuthor::books
引用了XdBook::authors
,反之亦然。 如果我們希望鏈接是雙向的,則必須添加這些引用。 因此,如果您將作者添加到書中,則該書將出現在該作者的書列表中,反之亦然。
The third entity type is an XdGenre
enumeration, which is pretty trivial:
第三種實體類型是XdGenre
枚舉,這很簡單:
class XdGenre(entity: Entity) : XdEnumEntity(entity) {companion object : XdEnumEntityType<XdGenre>() {val FANTASY by enumField {}val ROMANCE by enumField {}}
}
數據庫初始化 (Database initialization)
Now, when we have declared the entity types, we have to initialize the database:
現在,當我們聲明實體類型時,我們必須初始化數據庫:
fun initXodus(): TransientEntityStore {XdModel.registerNodes(XdAuthor,XdBook,XdGenre)val databaseHome = File(System.getProperty("user.home"), "bookstore")val store = StaticStoreContainer.init(dbFolder = databaseHome,environmentName = "db")initMetaData(XdModel.hierarchy, store)return store
}
fun main() {val store = initXodus()
}
This code shows the most basic setup:
此代碼顯示了最基本的設置:
We define the data model. Here we list all entity types manually, but it is possible to auto scan the classpath as well.
我們定義數據模型。 在這里,我們手動列出了所有實體類型,但是也可以自動掃描類路徑 。
We initialize the database store in
{user.home}/bookstore
folder.我們在
{user.home}/bookstore
文件夾中初始化數據庫存儲。- We link the metadata with the store. 我們將元數據與商店鏈接。
填寫數據 (Filling the data in)
Now that we have initialized the database, it’s time to put something inside. Before doing this, let’s add toString
methods to our entity classes. Their only purpose is to allow us to output the database content in a human-readable format.
現在我們已經初始化了數據庫,是時候將一些東西放到里面了。 在執行此操作之前,讓我們將toString
方法添加到我們的實體類中。 它們的唯一目的是允許我們以人類可讀的格式輸出數據庫內容。
class XdAuthor(entity: Entity) : XdEntity(entity) {...override fun toString(): String {val bibliography = books.asSequence().joinToString("\n")return "$name ($yearOfBirth-${yearOfDeath ?: "???"}):\n$bibliography"}
}
class XdBook(entity: Entity) : XdEntity(entity) {...override fun toString(): String {val genres = genres.asSequence().joinToString(", ")return "$title (${year ?: "Unknown"}) - $genres"}
}
class XdGenre(entity: Entity) : XdEnumEntity(entity) {...override fun toString(): String {return this.name.toLowerCase().capitalize()}
}
Notice books.asSequence().joinToString("\n")
and genres.asSequence().joinToString(", ")
instructions: here we use asSequence()
method to convert an XdQuery
to a Kotlin collection.
請注意books.asSequence().joinToString("\n")
和genres.asSequence().joinToString(", ")
指令:在這里,我們使用asSequence()
方法將XdQuery
轉換為Kotlin集合。
Right, let’s now add several books from our collection inside the main function. All database operations (creating, reading, updating and removing entities) we do inside transactions — atomic database modifications, which guarantees to preserve the consistency.
正確,現在讓我們在主函數中添加我們收藏中的幾本書。 我們在事務內部執行所有數據庫操作(創建,讀取,更新和刪除實體)-原子數據庫修改,這保證了保持一致性。
In the case of our bookstore, there are plenty of ways to fill it with stuff:
就我們的書店而言,有很多方法可以填充其中的內容:
1. Add an author and a book separately:
1.分別添加作者和書籍:
val bronte = store.transactional {XdAuthor.new {name = "Charlotte Bront?"countryOfBirth = "England"yearOfBirth = 1816yearOfDeath = 1855} }store.transactional {XdBook.new {title = "Jane Eyre"year = 1847genres.add(XdGenre.ROMANCE)authors.add(bronte)}}
2. Add an author and put several books in their list:
2.添加一位作者,并在列表中放入幾本書:
val tolkien = store.transactional {XdAuthor.new {name = "J. R. R. Tolkien"countryOfBirth = "England"yearOfBirth = 1892yearOfDeath = 1973}}store.transactional {tolkien.books.add(XdBook.new {title = "The Hobbit"year = 1937genres.add(XdGenre.FANTASY)})tolkien.books.add(XdBook.new {title = "The Lord of the Rings"year = 1955genres.add(XdGenre.FANTASY)})}
3. Add an author with books:
3.為作者添加書籍:
store.transactional {XdAuthor.new {name = "George R. R. Martin"countryOfBirth = "USA"yearOfBirth = 1948books.add(XdBook.new {title = "A Game of Thrones"year = 1996genres.add(XdGenre.FANTASY)})}}
To check that everything is created, all we need to do is to print the content of our database:
要檢查所有內容是否已創建,我們所需要做的就是打印數據庫的內容:
store.transactional(readonly = true) { println(XdAuthor.all().asSequence().joinToString("\n***\n"))}
Now, if you execute ./gradlew run
, you should see the following output:
現在,如果執行./gradlew run
,應該會看到以下輸出:
Charlotte Bront? (1816-1855):
Jane Eyre (1847) - Romance
***
J. R. R. Tolkien (1892-1973):
The Hobbit (1937) - Fantasy
The Lord of the Rings (1955) - Fantasy
***
George R. R. Martin (1948-???):
A Game of Thrones (1996) - Fantasy
約束條件 (Constraints)
As mentioned, the transactions guarantee data consistency. One of the operations which Xodus does before saving the changes is checking the constraints. In the DNQ, some of them are encoded in the name of the delegate which provides a property of a given type. For example, xdRequiredIntProp
has to always be set to some value, whereas xdNullableIntProp
can remain empty.
如前所述,事務保證了數據的一致性。 Xodus在保存更改之前所做的一項操作是檢查約束。 在DNQ中,其中一些編碼為委托人的名稱,該委托人提供給定類型的屬性。 例如, xdRequiredIntProp
必須始終設置為某個值,而xdNullableIntProp
可以保持為空。
Despite this, Xodus-DNQ allows defining more complex constraints which are described in the official documentation. I have added several examples to the XdAuthor
entity type:
盡管如此,Xodus-DNQ允許定義更復雜的約束,這些約束在官方文檔中進行了介紹 。 我向XdAuthor
實體類型添加了幾個示例:
var name by xdRequiredStringProp { containsNone("?!") }var country by xdStringProp {length(min = 3, max = 56)regex(Regex("[A-Za-z.,]+"))}var yearOfBirth by xdRequiredIntProp { max(2019) }var yearOfDeath by xdNullableIntProp { max(2019) }
You may be wondering why I have limited the countryOfBirth
property length to 56 characters. Well, the longest official country name which I found is “The United Kingdom of Great Britain and Northern Ireland” — precisely 56 characters!
您可能想知道為什么我將countryOfBirth
屬性的長度限制為56個字符。 好吧,我發現的最長的官方國家名稱是“大不列顛及北愛爾蘭聯合王國”,正好是56個字符!
查詢 (Queries)
We have already used database queries above. Do you remember? We printed the list of authors using XdAuthor.all().asSequence()
. As you may guess, the all()
method returns all the entries of a given entity type.
上面我們已經使用過數據庫查詢。 你還記得嗎? 我們使用XdAuthor.all().asSequence()
打印了作者列表。 您可能會猜到, all()
方法返回給定實體類型的所有條目。
More often than not though, we will prefer filtering data. Here are some examples:
通常,我們會更喜歡過濾數據。 這里有些例子:
store.transactional(readonly = true) {val fantasyBooks = XdBook.filter { it.genres contains XdGenre.FANTASY }val booksOf20thCentury = XdBook.filter { (it.year ge 1900) and (it.year lt 1999) }val authorsFromEngland = XdAuthor.filter { it.countryOfBirth eq "England" }val booksSortedByYear = XdBook.all().sortedBy(XdBook::year)val allGenres = XdBook.all().flatMapDistinct(XdBook::genres)
}
Again, there are plenty of options for building data queries, so I strongly recommend taking a look at the documentation.
同樣,構建數據查詢有很多選擇,因此我強烈建議您閱讀文檔 。
I hope this story is as useful for you as it was for me when I wrote it :) Any feedback is highly appreciated!
我希望這個故事對您和我寫這篇文章時一樣有用:)任何反饋都非常感謝!
You can find the source code for this tutorial here.
您可以在此處找到本教程的源代碼 。
翻譯自: https://www.freecodecamp.org/news/how-to-use-the-xodus-database-in-kotlin-applications-3f899896b9df/
kotlin數據庫