DatabaseClient – bu R2DBC asosiy paketidagi markaziy sinf. Bu resurslarni yaratish va bo'shatishni boshqaradi, shu bilan birga ulanishni yopishni unutish kabi keng tarqalgan xatolardan qochishga yordam beradi. Bu R2DBC asosiy ish jarayonining asosiy vazifalarini bajaradi (masalan, bayonotlarni yaratish va bajarish), dastur kodini SQL yetkazish va natijalarni olish uchun qoldiradi. DatabaseClient sinf quyidagi vazifalarni bajaradi:
-
SQL so'rovlarini bajaradi
-
Saqlangan protsedurani chaqiruvchi va yangilovchi ko'rsatmalarni yangilaydi
-
Resultnusxalarini aylanib o'tadi -
R2DBC istisnolarini ushlaydi va ularni
org.springframework.daopaketida aniqlangan tipizatsiyalangan, ko'proq ma'lumot beruvchi istisnolar ierarxiyasiga o'zgartiradi. (Izohli istisnolar ierarxiyasini ko'ring)
Klient reaktiv turlardan foydalanib deklarativ komponentlar yaratishni ta'minlovchi funktsional, erkin API interfeysiga ega.
Agar siz DatabaseClient-dan o'z kodingiz uchun foydalansangiz, faqat java.util.function interfeyslarini amalga oshirishingiz kerak bo'ladi va ularga aniq belgilangan kontraktni berishingiz kerak bo'ladi. DatabaseClient sinfi tomonidan taqdim etilgan Connectionni hisobga olgan holda, Function qayta chaqiruvi Publisherni yaratadi. Bu Rowdan natijani olish uchun xaritalash funktsiyalari uchun ham amal qiladi.
DatabaseClientni DAO amalga oshirilishi ichida to'g'ridan-to'g'ri ConnectionFactoryga ishora qiluvchi nusxani yaratish orqali ishlatishingiz mumkin yoki uni Spring IoC konteynerida sozlash va DAO ob'ektlariga bog'lovchi sifatida uzatishingiz mumkin.
DatabaseClient ob'ektini yaratishning eng oddiy usuli quyida ko'rsatilgandek statik fabrik metodidan foydalanishdir:
DatabaseClient client = DatabaseClient.create(connectionFactory);
val client = DatabaseClient.create(connectionFactory)
ConnectionFactory har doim Spring IoC konteynerida bean sifatida sozlanishi kerak.
Oldingi metod DatabaseClientni standart sozlamalar bilan yaratadi.
Shuningdek, DatabaseClient.builder()dan Builder nusxasini olish mumkin. Quyidagi metodlarni chaqirish orqali clientni sozlashingiz mumkin:
-
….bindMarkers(…): Ma'lumotlar bazasi uchun o'rnatilgan markerlarni almashtirish uchun nomlangan parametrni sozlash uchunBindMarkersFactoryni aniqlang. -
….executeFunction(…):Statementob'ektlari bajariladi, deb kutayotganExecuteFunctionni o'rnating. -
….namedParameters(false): Nomlangan parametrlarni kengaytirishni o'chiring. Standart holda yoqilgan.
BindMarkersFactoryResolver tomonidan aniqlanadi va
ConnectionFactorydan kelib chiqadi, odatda
ConnectionFactoryMetadatani tekshirish orqali. Spring sizning
BindMarkersFactoryni avtomatik ravishda aniqlashiga ruxsat berishingiz mumkin,
org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProviderni
META-INF/spring.factories orqali ro'yxatga olgan holda.
BindMarkersFactoryResolver classpathda joylashgan ulanish markeri provayderlarini
SpringFactoriesLoader yordamida aniqlaydi.
Quyidagi ma'lumotlar bazalariga hozirda qo'llab-quvvatlanadi:
-
H2
-
MariaDB
-
Microsoft SQL Server
-
MySQL
-
Postgres
Bu sinf tomonidan chiqariladigan barcha SQL so'rovlari DEBUG darajasida mijoz nusxasi sinfining to'liq nomi bilan mos keladigan kategoriya ostida qayd etiladi (odatda DefaultDatabaseClient). Bundan tashqari, har bir bajarish debuggani osonlashtirish uchun reaktiv ketma-ketlikda nazorat nuqtasini qayd etadi.
Quyidagi bo'limlarda DatabaseClientdan foydalanishning ba'zi bir misollari keltirilgan. Bu misollar DatabaseClient tomonidan taqdim etilgan barcha funksionalliklarning to'liq ro'yxatini emas, balki namoyish etadi. javadocni ko'ring.
Bayonotlarni bajarish
DatabaseClient bayonotni bajarish uchun asosiy funksionallikni ta'minlaydi. Quyidagi misolda minimal, lekin to'liq funksional kodda yangi jadval qanday yaratilishi ko'rsatilgan:
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.then();
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.await()
DatabaseClient qulay, erkin foydalanish uchun mo'ljallangan. Har bir bajarish spetsifikatsiyasi bosqichida oraliq, davomli va yakuniy metodlarni ochib beradi. Oldingi misolda then() foydalanilgan, u so'rovlardan (yoki SQL so'rovi bir nechta bayonotlarni o'z ichiga olsa) biri tugallangach, yakunlanuvchi Publisher qaytaradi.
execute(…) SQL so'rovi qatorini yoki so'rovni
Supplier<String>ni qabul qiladi, so'rov yaratishni bajarishga qadar kechiktiradi.
So'rovlar yaratish (SELECT)
SQL so'rovlari Row ob'ektlari orqali qiymatlar qaytarishi yoki ta'sirlangan qatorlar sonini ko'rsatishi mumkin. DatabaseClient so'rovdan kelib chiquvchi yangilangan qatorlar sonini yoki qatorlarning o'zini qaytarishi mumkin.
Quyidagi so'rov id va name ustunlarini jadvaldan qaytaradi:
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
.fetch().first();
val first = client.sql("SELECT id, name FROM person")
.fetch().awaitSingle()
Keyingi so'rovda bog'lanish o'zgaruvchisi ishlatilgan:
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().first();
val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitSingle()
Yuqoridagi misolda fetch() ishlatilganini ko'rgan bo'lishingiz mumkin. fetch() – bu davomli operator, u qancha ma'lumot iste'mol qilinishi kerakligini belgilash imkonini beradi.
first() chaqiruvi natijadan birinchi qatordan qaytaradi va qolganlarini chiqarib tashlaydi. Ma'lumotlar quyidagi operatorlar yordamida iste'mol qilinishi mumkin:
-
first()natijadan birinchi qatordan qaytaradi. Uning Kotlin'dagi korutin versiyasiawaitSingle()deb ataladi, qiymatlar bo'sh qiymatni qabul qilmaydi, vaawaitSingleOrNull(),agar qiymat majburiy bo'lsa. -
one()aniq bir natijadan qaytaradi va natija ko'proq qatorlarni o'z ichiga olsa, xatolik tugaydi. Kotlin'dagi korutinlarawaitOne()deb ataladi. -
all()natijaning barcha qatorlarini qaytaradi. Kotlin'dagi korutinlarni ishlatishdaflow()dan foydalaning. -
rowsUpdated()ta'sirlangan qatorlar (ko'rsatmaINSERT/UPDATE/DELETEhisoblagich) sonini qaytaradi. Uning Kotlin'dagi korutin versiyasiawaitRowsUpdated()deb ataladi.
数据库客户端使用的类和方法名保持原样
Qo'shimcha xaritalash tafsilotlarini belgilamasdan so'rovlar natijalarni Map bo'yicha qaytaradi, bu erda kalitlar harf registriga e'tibor bermasdan ustun nomlaridir, ular ustun qiymatlari bilan mos keladi.
Natijalarni xaritalashni boshqarish uchun Function<Row, T> ni taqdim etish mumkin, bu har bir Row uchun chaqiriladi, shu bilan uni ixtiyoriy qiymatlar (yagona qiymatlar, to'plamlar, Map va ob'ektlar) qaytarishi mumkin bo'ladi.
Quyidagi misolda name ustunini olib tashlash va uning qiymatini chiqarish ko'rsatilgan:
Flux<String> names = client.sql("SELECT name FROM person")
.map(row -> row.get("name", String.class))
.all();
val names = client.sql("SELECT name FROM person")
.map{ row: Row -> row.get("name", String.class) }
.flow()
Yangilash (INSERT, UPDATE, va DELETE) DatabaseClient yordamida
Modifikatsiya qiluvchi ko'rsatmalarning yagona farqi shundaki, ular odatda jadval ma'lumotlarini qaytarmaydi, shuning uchun rowsUpdated() funksiyasidan natijalarni olish uchun foydalaniladi.
Quyidagi misolda yangilanadigan qatorlar sonini qaytaruvchi UPDATE ko'rsatmasi ko'rsatilgan:
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().rowsUpdated();
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitRowsUpdated()
So'rovlarga qiymatlarni bog'lash
Tipik dastur ma'lum kirish ma'lumotlariga muvofiq satrlarni tanlash yoki yangilash uchun parametrli SQL ko'rsatmalarini talab qiladi. Odatda bu WHERE ifodasi bilan cheklangan SELECT ko'rsatmalari yoki kirish parametrlarini qabul qiluvchi INSERT va UPDATE ko'rsatmalari. Parametrli ko'rsatmalar SQL injektsiya xavfini saqlaydi, agar parametrlar to'g'ri tarzda tayyorlanmagan bo'lsa. DatabaseClient parametrlar xavfini bartaraf etish uchun R2DBC bind API interfeysini ishlatadi. Siz execute(…) operatori yordamida parametrli SQL ko'rsatmasini ko'rsatishingiz va Statementga parametrlarni bog'lashingiz mumkin. Keyin R2DBC driver s o'rnatilgan ko'rsatma yordamida bajarishni amalga oshiradi.
Parametrlarni bog'lash ikki bog'lash strategiyasini qo'llab-quvvatlaydi:
-
Indeks bo'yicha, nol indeksli parametrlar yordamida.
-
Nom bo'yicha, joy tutuvchi nomidan foydalangan holda.
Quyidagi misol parametrlarni so'rov uchun bog'lashni ko'rsatadi:
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34);
So'rov preprocessor'i Collection nomlangan parametrlarini bog'lash markerlar ketma-ketligiga kengaytirib, argumentlar soniga qarab so'rov yaratishni dinamik ravishda amalga oshirishga ehtiyoj qoldirmaydi. Ob'ektlar massivlari kengaytiriladi, bu (masalan) ochiladigan ro'yxatlar foydalanishni imkonini beradi.
Quyidagi so'rovni ko'rib chiqing:
SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))
Oldingi so'rov parametrli bo'lishi va quyidagicha bajarilishi mumkin:
List<Object[]> tuples = new ArrayList<>();
tuples.add(new Object[] {"John", 35});
tuples.add(new Object[] {"Ann", 50});
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples);
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples)
Quyidagi misol IN predikatlarini ishlatishning oddiy variantini ko'rsatadi:
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", Arrays.asList(35, 50));
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("tuples", arrayOf(35, 50))
Collection turidagi qiymatlarni qo'llab-quvvatlamaydi. Biroq, yuqoridagi misolda
Listni kengaytirish Spring'dagi R2DBC qo'llab-quvvatlash vositalarida masalan,
IN ifodalari uchun yuqorida ko'rsatilishicha ishlashiga imkon beradi. Biroq, qatorlar turi bilan ustunlar qo'shish yoki yangilash (masalan, Postgres) uchun qora yoki qator tipi talab qilinadi, odatda bu R2DBC asosiy dastaklari tomonidan qo'llab-quvvatlanadi: odatda Java qatorlari, masalan,
String[]
text[] ustuni yangilash uchun. Array parametri sifatida
Collection<String> yoki boshqa narsalarni qo'ymang.
Bayonot filtrlari
Ba'zan Statementning o'ziga parametrlarni nozik sozlash kerak bo'ladi. DatabaseClient orqali Statement filtri (StatementFilterFunction)ni ro'yxatdan o'tkazing, bu esa bajarish jarayonida bayonotlarni tuzatuvchi va o'zgartiruvchi bo'lib xizmat qiladi, quyidagi misolda ko'rsatilgan:
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
.bind("name", …)
.bind("state", …)
DatabaseClient xuddi shunday filter(…)ni o'zgartirishni soddalashtiruvchi Function<Statement, Statement>ni qabul qiladi:
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"));
client.sql("SELECT id, name, state FROM table")
.filter(statement -> s.fetchSize(25));
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
client.sql("SELECT id, name, state FROM table")
.filter { statement -> s.fetchSize(25) }
StatementFilterFunction implementatsiyalari Statementni va Result ob'ektlarini filtrlaydi.
DatabaseClient bilan optimal ishlash usullari
Sozlangandan so'ng DatabaseClient sinfi instansiyalari ko'p oqimli xavfsizdir. Bu muhim, chunki bu bitta DatabaseClient instansiyasini sozlash, keyin esa ushbu umumiy ulanishni bir nechta DAO (yoki repozitoriylar) ichida xavfsiz tarzda kiritish mumkinligini anglatadi. DatabaseClient holatni saqlaydi, chunki u ConnectionFactoryga ishora qiladi, lekin bu holat dialog holati emas.
DatabaseClient dan foydalanishda odatda qo'llaniladigan usul ConnectionFactoryni Spring konfiguratsiya faylida sozlash va keyin ConnectionFactory bilan umumiy ulanishdan foydalangan holda sinf DAOga kiritish. DatabaseClient ConnectionFactory uchun o'rnatuvchi ichida yaratildi. Ushbu DAO taxminan quyidagi ko'rinishga olib keladi:
public class R2dbcCorporateEventDao implements CorporateEventDao {
private DatabaseClient databaseClient;
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.databaseClient = DatabaseClient.create(connectionFactory);
}
// CorporateEventDao uchun R2DBC qo'llab-quvvatlovchi metodlar amalga oshirilishi quyida amalga oshiriladi...
}
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao {
private val databaseClient = DatabaseClient.create(connectionFactory)
// CorporateEventDao uchun R2DBC qo'llab-quvvatlovchi metodlar amalga oshirilishi quyida amalga oshiriladi...
}
Yaqqol konfiguratsiyani bir variant komponentlarni skanerlash va moslashuvchanlik kiritish uchun annotatsiya qo'llab-quvvatlovchi vositalarini ishlatish alternativdir. Ushbu holatda sinfni @Component orqali belgilash (bu uni komponentlarni skanerlash namzodi qiladi) va ConnectionFactory o'rnatuvchi metodini @Autowired orqali belgilash mumkin. Quyidagi misol ushbu jarayonni qanday bajarilishini ko'rsatadi:
@Component
public class R2dbcCorporateEventDao implements CorporateEventDao {
private DatabaseClient databaseClient;
@Autowired
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.databaseClient = DatabaseClient.create(connectionFactory);
}
// CorporateEventDao uchun R2DBC qo'llab-quvvatlovchi metodlar amalga oshirilishi quyida amalga oshiriladi...
}
- Sinfni
@Componentyordamida belgilaymiz. ConnectionFactoryo'rnatuvchi metodni@Autowiredorqali belgilaymiz.ConnectionFactoryyordamida yangiDatabaseClientyaratamiz.
@Component
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao {
private val databaseClient = DatabaseClient(connectionFactory)
// CorporateEventDao uchun R2DBC qo'llab-quvvatlovchi metodlar amalga oshirilishi quyida amalga oshiriladi...
}
- Sinfni
@Componentyordamida belgilaymiz. ConnectionFactorykonnector injection.ConnectionFactoryyordamida yangiDatabaseClientyaratamiz.
Yuqorida ko'rsatilgan shablonning boshida sozlash uslubidan foydalanishni yoki ulardan foydalanishni afzal bilmasligingizdan qat'i nazar, SQLni bajarishingiz kerak bo'lgan har safar yangi DatabaseClient sinfining yangi nusxasini yaratishga ehtiyoj juda kamdir. Sozlangandan keyin DatabaseClientning bir nusxasi ko'p oqimli xavfsizdir. Agar sizning dasturingiz bir nechta ma'lumotlar bazalariga murojaat qilsa, shunda bir nechta DatabaseClient nusxalari talab qilinishi mumkin, bu bir nechta ConnectionFactorylarni va shunga muvofiq bir nechta turli sozlangan DatabaseClient nuqtalarini talab qiladi.
Avtomatik ravishda yaratilgan kalitlarni olish
INSERT ko'rsatmalari avtoinkrement qismi yoki identifikatorni aniqlovchi ustunli jadvallarga saqlangan qatorlarda kalitlarni yaratishi mumkin. Yaralgan ustun nomi ustidan to'liq nazoratni olish uchun faqat kerakli ustun uchun kalitni so'ragan StatementFilterFunction ro'yxatga oling.
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"))
.map(row -> row.get("id", Integer.class))
.first();
// generatedId INSERT ko'rsatmasi tugagandan so'ng yaratilgan kalitni chiqaradi
val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
.map { row -> row.get("id", Integer.class) }
.awaitOne()
// generatedId INSERT ko'rsatmasi tugagandan so'ng yaratilgan kalitni chiqaradi
GO TO FULL VERSION