Hai, Habr. Belum lama ini, untuk salah satu proyek saya, saya membutuhkan database tertanam yang akan menyimpan elemen nilai kunci, memberikan dukungan transaksi, dan, opsional, data terenkripsi. Setelah pencarian singkat, saya menemukan proyek Berkeley DB . Selain fitur yang saya butuhkan, database ini menyediakan antarmuka yang kompatibel dengan STL yang memungkinkan Anda untuk bekerja dengan database seperti dengan wadah STL biasa (hampir biasa). Sebenarnya, antarmuka ini akan dibahas di bawah ini.
Berkeley db
Berkeley DB adalah database open source yang tertanam, dapat diukur, berkinerja tinggi, dan tinggi. Ini tersedia secara gratis untuk digunakan dalam proyek-proyek sumber terbuka , tetapi untuk proyek-proyek eksklusif ada batasan yang signifikan. Fitur yang didukung:
- transaksi
- log gagal-depan untuk failover
- Enkripsi data AES
- replikasi
- indeks
- alat sinkronisasi untuk aplikasi multithreaded
- kebijakan akses - satu penulis, banyak pembaca
- caching
Serta banyak lainnya.
Ketika sistem diinisialisasi, pengguna dapat menentukan subsistem mana yang akan digunakan. Ini menghilangkan pemborosan sumber daya pada operasi seperti transaksi, logging, mengunci ketika mereka tidak diperlukan.
Pilihan struktur penyimpanan dan akses data tersedia:
- Btree - implementasi pohon seimbang yang disortir
- Implementasi hash - linear hash
- Heap - menggunakan file heap paged secara logis untuk penyimpanan. Setiap entri diidentifikasi oleh halaman dan offset di dalamnya. Penyimpanan diatur sedemikian rupa sehingga menghapus catatan tidak membutuhkan pemadatan. Ini memungkinkan Anda untuk menggunakannya dengan kekurangan ruang fisik.
- Antrian - antrian yang menyimpan catatan panjang tetap dengan nomor logis sebagai kunci. Ini dirancang untuk penyisipan cepat di akhir, dan mendukung operasi khusus yang menghapus dan mengembalikan entri dari kepala antrian dalam satu panggilan.
- Recno - memungkinkan Anda untuk menyimpan catatan panjang tetap dan variabel dengan nomor logis sebagai kunci. Menyediakan akses ke elemen dengan indeksnya.
Untuk menghindari ambiguitas, perlu untuk mendefinisikan beberapa konsep yang digunakan untuk menggambarkan karya Berkeley DB .
Basis data adalah penyimpanan data nilai-kunci. Analog database Berkeley DB di DBMS lain bisa berupa tabel.
Lingkungan basis data adalah pembungkus untuk satu atau lebih basis data . Menentukan pengaturan umum untuk semua basis data , seperti ukuran cache, jalur penyimpanan file, penggunaan dan konfigurasi pemblokiran, transaksi, pencatatan subsistem.
Dalam kasus penggunaan umum, suatu lingkungan dibuat dan dikonfigurasikan, dan memiliki satu atau lebih basis data .
Antarmuka STL
Berkeley DB adalah perpustakaan yang ditulis dalam C. Ini memiliki pengikat untuk bahasa seperti Perl , Java , PHP dan lainnya. Antarmuka untuk C ++ adalah pembungkus kode C dengan objek dan warisan. Untuk memungkinkannya mengakses basis data yang mirip dengan operasi dengan wadah STL , ada antarmuka STL sebagai tambahan pada C ++ . Dalam bentuk grafis, lapisan antarmuka terlihat seperti ini:

Jadi, antarmuka STL memungkinkan Anda untuk mendapatkan elemen dari database dengan kunci (untuk Btree atau Hash ) atau dengan indeks (untuk Recno ) mirip dengan std::map
atau std::vector
wadah std::vector
, temukan elemen dalam database melalui standar std::find_if
, beralih ke seluruh database melalui foreach
. Semua kelas dan fungsi antarmuka Berkeley DB STL berada dalam ruang nama dbstl , singkatnya, dbstl juga berarti antarmuka STL .
Instalasi
Basis data mendukung sebagian besar platform Linux , Windows , Android , Apple iOS , dll.
Untuk Ubuntu 18.04, cukup instal paket:
- libdb5.3-stl-dev
- libdb5.3 ++ - dev
Untuk membangun dari sumber Linux , Anda perlu menginstal autoconf dan libtool . Kode sumber terbaru dapat ditemukan di sini .
Misalnya, saya mengunduh arsip dengan versi 18.1.32 - db-18.1.32.zip. Anda perlu membuka zip arsip dan pergi ke folder sumber:
unzip db-18.1.32.zip cd db-18.1.32
Selanjutnya, kita pindah ke direktori build_unix dan menjalankan perakitan dan instalasi:
cd build_unix ../dist/configure --enable-stl --prefix=/home/user/libraries/berkeley-db make make install
Menambahkan ke proyek cmake
Proyek BerkeleyDBSamples digunakan untuk menggambarkan contoh dengan Berkeley DB .
Struktur proyek adalah sebagai berikut:
+-- CMakeLists.txt +-- sample-usage | +-- CMakeLists.txt | +-- sample-map-usage.cpp | +-- submodules | +-- cmake | | +-- FindBerkeleyDB
Root CMakeLists.txt menjelaskan parameter umum proyek. File sumber sampel dalam penggunaan sampel . sample-usage / CMakeLists.txt mencari pustaka, menentukan kumpulan contoh.
Dalam contoh, FindBerkeleyDB digunakan untuk menghubungkan perpustakaan ke proyek cmake . Itu ditambahkan sebagai git submodule di submodules / cmake . Selama perakitan, Anda mungkin perlu menentukan BerkeleyDB_ROOT_DIR
. Misalnya, untuk pustaka di atas yang diinstal dari sumber, Anda harus menentukan flag cmake -DBerkeleyDB_ROOT_DIR=/home/user/libraries/berkeley-db
.
Di file root CMakeLists.txt , tambahkan path ke modul FindBerkeleyDB ke CMAKE_MODULE_PATH :
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/submodules/cmake/FindBerkeleyDB")
Setelah itu, sample-use / CMakeLists.txt melakukan pencarian pustaka dengan cara standar:
find_package(BerkeleyDB REQUIRED)
Selanjutnya, tambahkan file yang dapat dieksekusi dan tautkan ke perpustakaan Oracle :: BerkeleyDB :
add_executable(sample-map-usage "sample-map-usage.cpp") target_link_libraries(sample-map-usage PRIVATE Oracle::BerkeleyDB ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
Contoh praktis
Untuk menunjukkan penggunaan dbstl, mari kita periksa contoh sederhana dari file sample-map-use.cpp . Aplikasi ini menunjukkan bekerja dengan dbstl::db_map
dalam program berulir tunggal. Wadah itu sendiri mirip dengan std::map
dan menyimpan data sebagai pasangan kunci / nilai. Struktur basis data yang mendasarinya bisa Btree atau Hash . Tidak seperti std::map
, untuk dbstl::db_map<std::string, TestElement>
tipe nilai aktualnya adalah dbstl::ElementRef<TestElement>
. Jenis ini dikembalikan, misalnya, untuk dbstl::db_map<std::string, TestElement>::operator[]
. Ini mendefinisikan metode untuk menyimpan objek tipe TestElement
dalam database. Salah satu metode tersebut adalah operator=
.
Dalam contoh, bekerja dengan database adalah sebagai berikut:
- aplikasi memanggil metode Berkeley DB untuk mengakses data
- metode ini mengakses cache untuk membaca atau menulis
- jika perlu, akses langsung ke file data
Secara grafis, proses ini ditunjukkan pada gambar:

Untuk mengurangi kompleksitas contoh, itu tidak menggunakan penanganan pengecualian. Beberapa metode wadah dbstl dapat melempar pengecualian ketika kesalahan terjadi.
Penguraian kode
Untuk bekerja dengan Berkeley DB, Anda harus menghubungkan dua file header:
#include <db_cxx.h> #include <dbstl_map.h>
Yang pertama menambahkan primitif antarmuka C ++ , dan yang kedua mendefinisikan kelas dan fungsi untuk bekerja dengan database, seperti dengan wadah asosiatif, serta banyak metode utilitas. Antarmuka STL terletak di namespace dbstl .
Untuk penyimpanan, struktur Btree digunakan , std::string
bertindak sebagai kunci, dan nilainya adalah struktur pengguna TestElement
:
struct TestElement{ std::string id; std::string name; };
Dalam fungsi main
, inisialisasi pustaka dengan memanggil dbstl::dbstl_startup()
. Itu harus ditempatkan sebelum penggunaan pertama primitif dari antarmuka STL .
Setelah itu, kami menginisialisasi dan membuka lingkungan basis data di direktori yang ditetapkan oleh variabel ENV_FOLDER
:
auto penv = dbstl::open_env(ENV_FOLDER, 0u, DB_INIT_MPOOL | DB_CREATE);
Bendera DB_INIT_MPOOL
bertanggung jawab untuk menginisialisasi subsistem caching, DB_CREATE
- untuk membuat semua file yang diperlukan untuk lingkungan. Tim juga mendaftarkan objek ini di manajer sumber daya. Ia bertanggung jawab untuk menutup semua objek terdaftar (objek database, kursor, transaksi, dll. Juga terdaftar di dalamnya) dan membersihkan memori dinamis. Jika Anda sudah memiliki objek lingkungan database dan Anda hanya perlu mendaftarkannya dengan manajer sumber daya, Anda dapat menggunakan fungsi dbstl::register_db_env
.
Operasi serupa dilakukan dengan database :
auto db = dbstl::open_db(penv, "sample-map-usage.db", DB_BTREE, DB_CREATE, 0u);
Data pada disk akan ditulis ke file sample-map-use.db , yang akan dibuat jika tidak ada (berkat flag DB_CREATE
) di direktori ENV_FOLDER
. Pohon digunakan untuk penyimpanan (parameter DB_BTREE
).
Di Berkeley DB, kunci dan nilai disimpan sebagai array byte. Untuk menggunakan jenis khusus (dalam TestElement
kasus kami), Anda harus menetapkan fungsi untuk:
- menerima jumlah byte untuk menyimpan objek;
- marshaling suatu objek menjadi array byte;
- unmarshaling.
Dalam contoh ini, fungsi ini dilakukan oleh metode statis dari kelas TestMarshaller
. Ini TestElement
objek TestElement
dalam memori sebagai berikut:
- panjang bidang
id
disalin ke awal buffer - byte berikutnya isi kolom
id
ditempatkan - setelah itu, ukuran bidang
name
disalin - maka konten itu sendiri ditempatkan dari bidang
name

Kami menjelaskan fungsi TestMarshaller
:
TestMarshaller::restore
- mengisi objek TestElement
dengan data dari bufferTestMarshaller::size
- mengembalikan ukuran buffer yang diperlukan untuk menyimpan objek yang ditentukan.TestMarshaller::store
- menyimpan objek di buffer.
Untuk mendaftarkan fungsi marshaling / dbstl::DbstlElemTraits
, gunakan dbstl::DbstlElemTraits
:
dbstl::DbstlElemTraits<TestElement>::instance()->set_size_function(&TestMarshaller::size); dbstl::DbstlElemTraits<TestElement>::instance()->set_copy_function(&TestMarshaller::store); dbstl::DbstlElemTraits<TestElement>::instance()->set_restore_function( &TestMarshaller::restore );
Inisialisasi wadah:
dbstl::db_map<std::string, TestElement> elementsMap(db, penv);
Beginilah cara menyalin elemen dari std::map
ke wadah yang dibuat terlihat seperti:
std::copy( std::cbegin(inputValues), std::cend(inputValues), std::inserter(elementsMap, elementsMap.begin()) );
Tetapi dengan cara ini Anda dapat mencetak isi database ke output standar:
std::transform( elementsMap.begin(dbstl::ReadModifyWriteOption::no_read_modify_write(), true), elementsMap.end(), std::ostream_iterator<std::string>(std::cout, "\n"), [](const auto data) -> std::string { return data.first + "=> { id: " + data.second.id + ", name: " + data.second.name + "}"; });
Memanggil metode begin
pada contoh di atas terlihat sedikit tidak biasa: elementsMap.begin(dbstl::ReadModifyWriteOption::no_read_modify_write(), true)
.
Desain ini digunakan untuk mendapatkan iterator read-only . dbstl tidak mendefinisikan metode cbegin
, melainkan parameter readonly
(yang kedua) dalam metode begin
digunakan. Anda juga dapat menggunakan referensi konstan ke wadah untuk mendapatkan iterator read-only . Iterator semacam itu hanya memungkinkan operasi membaca, saat menulis, itu akan membuang pengecualian.
Mengapa iterator read-only digunakan dalam kode di atas? Pertama, ia hanya melakukan operasi baca melalui iterator. Kedua, dokumentasi mengatakan bahwa ia memiliki kinerja yang lebih baik dibandingkan dengan versi reguler.
Menambahkan pasangan kunci / nilai baru, atau, jika kunci sudah ada, memperbarui nilai sesederhana di std::map
:
elementsMap["added key 1"] = {"added id 1", "added name 1"};
Seperti disebutkan di atas, instruksi elementMap elementsMap["added key 1"]
mengembalikan kelas pembungkus dengan operator=
didefinisikan ulang, panggilan selanjutnya yang langsung menyimpan objek dalam database.
Jika Anda perlu memasukkan item ke dalam wadah:
auto [iter, res] = elementsMap.insert( std::make_pair(std::string("added key 2"), TestElement{"added id 2", "added name 2"}) );
Panggilan ke elementsMap.insert
mengembalikan std::pair<, >
. Jika objek tidak dapat dimasukkan, bendera sukses akan salah . Jika tidak, flag sukses berisi true , dan iterator menunjuk ke objek yang dimasukkan.
Cara lain untuk menemukan nilai dengan kunci adalah dengan menggunakan metode dbstl::db_map::find
, mirip dengan std::map::find
:
auto findIter = elementsMap.find("test key 1");
Melalui iterator yang diperoleh, Anda dapat mengakses kunci - findIter->first
, ke bidang elemen findIter->second.id
- findIter->second.id
dan findIter->second.name
. Untuk mengekstrak pasangan kunci / nilai , operator dereference digunakan - auto iterPair = *findIter;
.
Ketika operator dereferencing ( * ) atau akses ke anggota kelas ( -> ) diterapkan ke iterator, database diakses dan data diekstraksi darinya. Selain itu, data yang diekstraksi sebelumnya, bahkan jika mereka diubah, dihapus. Ini berarti bahwa dalam contoh di bawah ini, perubahan yang dilakukan pada iterator akan dibuang, dan nilai yang disimpan dalam database akan ditampilkan pada konsol.
findIter->second.id = "skipped id"; findIter->second.name = "skipped name"; std::cout << "Found elem for key " << "test key 1" << ": id: " << findIter->second.id << ", name: " << findIter->second.name << std::endl;
Untuk menghindari ini, Anda perlu mendapatkan pembungkus objek yang disimpan dari iterator dengan memanggil findIter->second
dan menyimpannya dalam variabel. Selanjutnya, buat semua perubahan pada pembungkus ini, dan tulis hasilnya ke database dengan memanggil metode pembungkus _DB_STL_StoreElement
:
auto ref = findIter->second; ref.id = "new test id 1"; ref.name = "new test name 1"; ref._DB_STL_StoreElement();
Memperbarui data bisa lebih mudah - dapatkan pembungkusnya dengan instruksi findIter->second
dan tetapkan objek TestElement
diinginkan, seperti dalam contoh:
if(auto findIter = elementsMap.find("test key 2"); findIter != elementsMap.end()){ findIter->second = {"new test id 2", "new test name 2"}; }
Sebelum mengakhiri program, Anda harus memanggil dbstl::dbstl_exit();
untuk menutup dan menghapus semua objek yang terdaftar di manajer sumber daya.
Kesimpulannya
Artikel ini memberikan ikhtisar singkat tentang fitur utama wadah dbstl::db_map
menggunakan dbstl::db_map
sebagai dbstl::db_map
dalam program berulir tunggal sederhana. Ini hanya perkenalan kecil dan belum mencakup fitur-fitur seperti transaksionalitas, penguncian, manajemen sumber daya, penanganan pengecualian, dan eksekusi multithread.
Saya tidak bermaksud menjelaskan secara rinci metode dan parameternya, untuk ini lebih baik merujuk ke dokumentasi yang sesuai pada antarmuka C ++ dan pada antarmuka STL