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?
这对您意味着什么?
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都将其用于持久层定义。
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 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
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 the entityType
property for each class (which is used by the database engine).
companion
对象为每个类声明entityType
属性(数据库引擎使用该属性)。
Links are values, not variables; that is, you don’t set them with =
, but access them as a collection. (Pay attention to val books
versus var name
; I spent quite a bit of time trying to figure out why the compilation with var 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
references XdBook::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 {}
}
}
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
文件夹中初始化数据库存储。
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 = 1816
yearOfDeath = 1855
}
}
store.transactional {
XdBook.new {
title = "Jane Eyre"
year = 1847
genres.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 = 1892
yearOfDeath = 1973
}
}
store.transactional {
tolkien.books.add(XdBook.new {
title = "The Hobbit"
year = 1937
genres.add(XdGenre.FANTASY)
})
tolkien.books.add(XdBook.new {
title = "The Lord of the Rings"
year = 1955
genres.add(XdGenre.FANTASY)
})
}
3. Add an author with books:
3.为作者添加书籍:
store.transactional {
XdAuthor.new {
name = "George R. R. Martin"
countryOfBirth = "USA"
yearOfBirth = 1948
books.add(XdBook.new {
title = "A Game of Thrones"
year = 1996
genres.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
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个字符!
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数据库