Berkeley DB JEに飛び込みます。 DPL APIの概要

はじめに



主題について少し。 BerkeleyDBは、さまざまなプログラミング言語のライブラリとして提供される高性能な組み込みDBMSです。 このソリューションには、キーと値のペアの保存が含まれ、複数の値を1つのキーに割り当てる機能もサポートしています。 BerkeleyDBは、マルチスレッド、レプリケーションなどをサポートしています。 この記事の注意は、主にひげを生やした90年代にSleepycat Softwareによって提供されたライブラリの使用に向けられます。 この記事では、DPL(Direct Persistence Layer)APIを使用する主な側面について説明します。



:この記事のすべての例は、Kotlin言語で提供されます。



エンティティの説明



開始するには、エンティティを記述する方法を理解してください。 幸いなことに、JPAと非常によく似ています。 すべてのエンティティは、アノテーション@Persistent



および@Entity



持つクラスの形式に反映されます。それぞれのアノテーションにより、記述されたエンティティのバージョンを明示的に指定できます。 この記事のフレームワークでは、 @Entity



アノテーションのみを使用します。 @Entity



アノテーションでは、 @Entity



に光を@Persitent







単純なエンティティの例
 @Entity(version = SampleDBO.schema) class SampleDBO private constructor() { companion object { const val schema = 1 } @PrimaryKey lateinit var id: String private set @SecondaryKey(relate = Relationship.MANY_TO_ONE) lateinit var name: String private set constructor(id: String, name: String): this() { this.id = id this.name = name } }
      
      





:タイプjava.lang.Long



@PrimaryKey



アノテーションを持つキーの場合、シーケンスパラメーターを指定することもできます。これにより、エンティティの識別子を生成するための個別のシーケンスが作成されます。 残念ながら、Kotlinでは機能しません。





別に注意する必要があります:最初に、すべてのエンティティで、ライブラリが正しく機能するためにデフォルトでプライベートコンストラクターを残す必要があり、2番目に、将来インデックスを作成するエンティティのすべてのフィールドに@SecondaryKey



アノテーションが存在する@SecondaryKey



があります。 この場合、これは名前フィールドです。



制約を使用する



エンティティで制約を使用するために、作成者は内部アクセサーを検証するという非常に簡単な方法を提供します。 わかりやすくするために上記の例を変更します。



制約付きの例
 @Entity(version = SampleDBO.schema) class SampleDBO private constructor() { companion object { const val schema = 1 } @PrimaryKey lateinit var id: String private set @SecondaryKey(relate = Relationship.MANY_TO_ONE) var name: String? = null private set(value) { if(value == null) { throw IllegalArgumentException("Illegal name passed: ${value}. Non-null constraint failed") } if(value.length < 4 || value.length > 16) { throw IllegalArgumentException("Illegal name passed: ${value}. Expected length in 4..16, but was: ${value.length}") } } constructor(id: String, name: String): this() { this.id = id this.name = name } }
      
      







エンティティ間の関係



BerkeleyDB JEは、すべての関係タイプをサポートしています。





3つの追加パラメーターを持つ同じ@SecondaryKey



を使用して、エンティティ間の関係を記述します。





プリミティブストアの例を使用したエンティティ間の関係
 @Entity(version = CustomerDBO.schema) class CustomerDBO private constructor() { companion object { const val schema = 1 } @PrimaryKey() var id: String? = null private set @SecondaryKey(relate = Relationship.ONE_TO_ONE) lateinit var email: String private set var balance: Long = 0L constructor(email: String, balance: Long): this() { this.email = email this.balance = balance } constructor(id: String, email: String, balance: Long): this(email, balance) { this.id = id } override fun toString(): String { return "CustomerDBO(id=$id, email=$email, balance=$balance)" } }
      
      





 @Entity(version = ProductDBO.schema) class ProductDBO { companion object { const val schema = 1 } @PrimaryKey() var id: String? = null private set @SecondaryKey(relate = Relationship.MANY_TO_ONE) lateinit var name: String private set var price: Long = 0L var amount: Long = 0L private constructor(): super() constructor(name: String, price: Long, amount: Long): this() { this.name = name this.price = price this.amount = amount } constructor(id: String, name: String, price: Long, amount: Long): this(name, price, amount) { this.id = id } override fun toString(): String { return "ProductDBO(id=$id, name=$name, price=$price, amount=$amount)" } }
      
      





 @Entity(version = ProductChunkDBO.schema) class ProductChunkDBO { companion object { const val schema = 1 } @PrimaryKey() var id: String? = null private set @SecondaryKey(relate = Relationship.MANY_TO_ONE, relatedEntity = OrderDBO::class, onRelatedEntityDelete = DeleteAction.CASCADE) var orderId: String? = null private set @SecondaryKey(relate = Relationship.MANY_TO_ONE, relatedEntity = ProductDBO::class, onRelatedEntityDelete = DeleteAction.CASCADE) var itemId: String? = null private set var amount: Long = 0L private constructor() constructor(orderId: String, itemId: String, amount: Long): this() { this.orderId = orderId this.itemId = itemId this.amount = amount } constructor(id: String, orderId: String, itemId: String, amount: Long): this(orderId, itemId, amount) { this.id = id } override fun toString(): String { return "ProductChunkDBO(id=$id, orderId=$orderId, itemId=$itemId, amount=$amount)" } }
      
      





 @Entity(version = OrderDBO.schema) class OrderDBO { companion object { const val schema = 1 } @PrimaryKey() var id: String? = null private set @SecondaryKey(relate = Relationship.MANY_TO_ONE, relatedEntity = CustomerDBO::class, onRelatedEntityDelete = DeleteAction.CASCADE) var customerId: String? = null private set @SecondaryKey(relate = Relationship.ONE_TO_MANY, relatedEntity = ProductChunkDBO::class, onRelatedEntityDelete = DeleteAction.NULLIFY) var itemChunkIds: MutableSet<String> = HashSet() private set var isExecuted: Boolean = false private set private constructor() constructor(customerId: String, itemChunkIds: List<String> = emptyList()): this() { this.customerId = customerId this.itemChunkIds.addAll(itemChunkIds) } constructor(id: String, customerId: String, itemChunkIds: List<String> = emptyList()): this(customerId, itemChunkIds) { this.id = id } fun setExecuted() { this.isExecuted = true } override fun toString(): String { return "OrderDBO(id=$id, customerId=$customerId, itemChunkIds=$itemChunkIds, isExecuted=$isExecuted)" } }
      
      







構成



BerkeleyDB JEは、広範な構成オプションを提供します。 この記事では、クライアントアプリケーションを作成するために最低限必要な設定について説明します。将来的には、可能な限り、より高度な機能に光を当てます。



まず、データベースで動作するコンポーネントへのエントリポイントを検討します。 私たちの場合、これらはEnvironment



EntityStore



EntityStore



ます。 それらのそれぞれは、さまざまなオプションの印象的なリストを提供します。



環境



環境での作業のセットアップには、標準パラメーターの定義が含まれます。 最も単純なバージョンでは、次のようなものが出てきます。



  val environment by lazy { Environment(dir, EnvironmentConfig().apply { transactional = true allowCreate = true nodeName = "SampleNode_1" cacheSize = Runtime.getRuntime().maxMemory() / 8 offHeapCacheSize = dir.freeSpace / 8 }) }
      
      







EntityStore



アプリケーションがDPL APIを使用する場合、データベースを操作するためのメインクラスはEntityStoreになります。 標準構成は次のとおりです。



  val store by lazy { EntityStore(environment, name, StoreConfig().apply { transactional = true allowCreate = true }) }
      
      





インデックス、データアクセス



インデックスの機能を理解するには、次のSQLクエリを検討するのが最も簡単です。



 SELECT * FROM customers ORDER BY email;
      
      





BerkeleyDB JEでは、このクエリは次のように実装できます。最初に必要なことは、実際には2つのインデックスを作成することです。 最初のものが主要なもので、私たちの本質の@PrimaryKey



対応しなければなりません。 2番目は、順序が実行されるフィールドに対応するセカンダリです( -前述のように、フィールドには@SecondaryKey



として注釈を付ける必要があり@SecondaryKey



)。



  val primaryIndex: PrimaryIndex<String, CustomerDBO> by lazy { entityStore.getPrimaryIndex(String::class.java, CustomerDBO::class.java) } val emailIndex: SecondaryIndex<String, String, CustomerDBO> by lazy { entityStore.getSecondaryIndex(primaryIndex, String::class.java, "email") }
      
      





データのサンプリングは通常の方法で実行されます-カーソルインターフェイス(この場合はEntityCursor



)を使用して



  fun read(): List<CustomerDBO> = emailIndex.entities().use { cursor -> mutableListOf<CustomerDBO>().apply { var currentPosition = 0 val count = cursor.count() add(cursor.first() ?: return@apply) currentPosition++ while(currentPosition < count) { add(cursor.next() ?: return@apply) currentPosition++ } } }
      
      





関係と条件



一般的なタスクは、テーブル間の関係を使用してエンティティを取得することです。 次のSQLクエリを例として使用して、この質問を検討してください。



 SELECT * FROM orders WHERE customer_id = ?;
      
      





そしてバークレーでの彼のプレゼンテーション:



  fun readByCustomerId(customerId: String): List<OrderDBO> = customerIdIndex.subIndex(customerId).entities().use { cursor -> mutableListOf<OrderDBO>().apply { var currentPosition = 0 val count = cursor.count() add(cursor.first() ?: return@apply) currentPosition++ while(currentPosition < count) { add(cursor.next() ?: return@apply) currentPosition++ } } }
      
      





残念ながら、このオプションは1つの条件下でのみ可能です。 複数の条件でクエリを作成するには、より複雑なデザインを使用する必要があります。



変更された購入者
 @Entity(version = CustomerDBO.schema) class CustomerDBO private constructor() { companion object { const val schema = 1 } @PrimaryKey() var id: String? = null private set @SecondaryKey(relate = Relationship.ONE_TO_ONE) lateinit var email: String private set @SecondaryKey(relate = Relationship.MANY_TO_ONE) lateinit var country: String private set @SecondaryKey(relate = Relationship.MANY_TO_ONE) lateinit var city: String private set var balance: Long = 0L constructor(email: String, country: String, city: String, balance: Long): this() { this.email = email this.country = country this.city = city this.balance = balance } constructor(id: String, email: String, country: String, city: String, balance: Long): this(email, country, city, balance) { this.id = id } }
      
      







新しいインデックス
  val countryIndex: SecondaryIndex<String, String, CustomerDBO> by lazy { entityStore.getSecondaryIndex(primaryIndex, String::class.java, "country") } val cityIndex: SecondaryIndex<String, String, CustomerDBO> by lazy { entityStore.getSecondaryIndex(primaryIndex, String::class.java, "city") }
      
      







2つの条件を持つクエリの例(SQL)
 SELECT * FROM customers WHERE country = ? AND city = ?;
      
      







2つの条件を持つクエリの例
  fun readByCountryAndCity(country: String, city: String): List<CustomerDBO> { val join = EntityJoin<String, CustomerDBO>(primaryIndex) join.addCondition(countryIndex, country) join.addCondition(cityIndex, city) return join.entities().use { cursor -> mutableListOf<CustomerDBO>().apply { var currentPosition = 0 val count = cursor.count() add(cursor.first() ?: return@apply) currentPosition++ while(currentPosition < count) { add(cursor.next() ?: return@apply) currentPosition++ } } } }
      
      







例からわかるように-かなり退屈な構文ですが、生きることはかなり可能です。



範囲クエリ



このタイプのクエリではすべてが透過的で、インデックスにはfun <E> entities(fromKey: K, fromInclusive: Boolean, toKey: K, toInclusive: Boolean):

EntityCursor<E>




関数のオーバーロードがありますfun <E> entities(fromKey: K, fromInclusive: Boolean, toKey: K, toInclusive: Boolean):

EntityCursor<E>




fun <E> entities(fromKey: K, fromInclusive: Boolean, toKey: K, toInclusive: Boolean):

EntityCursor<E>




fun <E> entities(fromKey: K, fromInclusive: Boolean, toKey: K, toInclusive: Boolean):

EntityCursor<E>




は、目的のデータ選択を反復するカーソルを使用する機能を提供します。 インデックスが使用されるため、この方法は非常に迅速に機能します。比較的便利であり、私の意見では、個別のコメントは必要ありません。



結論の代わりに



これは、計画されているBerkeleyDBシリーズの最初の記事です。 主な目的は、読者にJava Editionライブラリの操作の基本を理解させ、日常的なアクションに必要な主な機能を検討することです。 記事が誰かに興味がある場合は、後続の記事でこのライブラリを操作するより興味深い詳細を取り上げます。



私はバークレーの経験がほとんどないので、どこかに欠陥があった場合はコメントの批判と訂正に感謝します。



(2017年12月10日)UPD1:Berkeley JEと互換性のある最新のAndroidバージョンのトランザクションは正しく機能しないため、キャッシュを変更した後に手動でチェックポイントを作成できることを確認する必要があります。 これを行うには、Environment :: sync関数を使用できます。これはブロッキング(sic!)であり、非常に高価です(sic!)^ 2は現在のキャッシュの変更をディスクに書き込みます。



(2017年12月10日)UPD2:データベースと競合するとき(個人的な経験から判断すると)、ログクリーナーを無効にすることをお勧めします。 これは、環境設定を通じて行われます。 この設定は、データベースを操作する際に追加の変更なしで変更できますが、データは失われません。



All Articles