Tere, Habr!
Me jÀtkame teema uurimist ja , sealhulgas andmebaaside tasemel. TÀna soovitame lugeda, miks suuremate rakenduste kavandamisel peaks andmebaasi struktuur, mitte Java-kood olema otsustav, kuidas seda tehakse ja millised erandid selle reegli osas on.
Selles ĂŒsna hilinenud artiklis selgitan, miks arvan, et peaaegu kĂ”igis juhtudel peaks rakenduse andmemudel olema kavandatud 'andmebaasist lĂ€htuvalt', mitte 'Java vĂ”imalustest lĂ€htuvalt' (vĂ”i muust kliendikeelest, millega töötate). Kui valite teise lĂ€henemise, astute pika valu ja kannatuste teele, kui teie projekt hakkab kasvama.
Artikkel on kirjutatud inspiratsiooniks , mille esitas Stack Overflow'is.
Huvitavad arutelud reddit'i osades ja .
Koodi genereerimine
Ma olin hĂ€mmingus, et on nii vĂ€ike kasutajate kiht, kes, tutvudes jOOQ'ga, kurdavad selle ĂŒle, et jOOQ puhul toetutakse tĂ”siselt koodi genereerimisele. Keegi ei takista teil kasutada jOOQ'd nii, nagu soovite, ja ei sundi teid koodi genereerimist kasutama. Kuid vaike seadistuse (nagu on kirjeldatud juhendis) puhul töötab jOOQ nii: alustate (pĂ€randatud) andmebaasi skeemist, viite lĂ€bi selle tagasiprojekteerimise jOOQ koodi generaatori abil, et luua klasside kogum, mis esindab teie tabeleid, ja seejĂ€rel kirjutate tĂŒĂŒbisoodsad pĂ€ringud nende tabelite jaoks:
for (Record2 record : DSL.using(configuration)
// ^^^^^^^^^^^^^^^^^^^^^^^ TĂŒĂŒpide teave on tuletatud
// genereeritud koodist, millele viidatakse allolevas
// SELECT tingimuses
.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
// vvvvv ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ genereeritud nimed
.from(ACTOR)
.orderBy(1, 2)) {
// ...
}Kood genereeritakse kas kÀsitsi vÀljaspool koostamist vÔi kÀsitsi iga koostamise ajal. NÀiteks selline regeneratsioon vÔib toimuda kohe pÀrast .
Allikakoodi genereerimine
Tugipool, kuidas koodi genereerimist â kĂ€sitsi ja automaatsete meetoditega â seostatakse erinevate filosoofiate, eeliste ja puudustega, mida ma ei kavatse selles artiklis ĂŒksikasjalikult arutada. Kuid ĂŒldiselt on genereeritud koodi tuum selles, et see vĂ”imaldab Java-s tĂ”lgendada seda âtĂ”deâ, mida me vĂ”tame iseenesestmĂ”istetavaks, kas meie sĂŒsteemi raames vĂ”i vĂ€ljaspool seda. Teatud mĂ”ttes teevad sama ka kompilaatorid, genereerides baitkoodi, masinkoodi vĂ”i mĂ”ne muu koodi vormi lĂ€htedokumentide pĂ”hjal â saame meie âtĂ”eâ esituse teises keeles, sĂ”ltumata konkreetsest pĂ”hjusest.
On palju selliseid koodigeneraatoreid. NÀiteks, PÔhimÔte on alati sama:
- On mingi tĂ”de (sisemine vĂ”i vĂ€line) â nĂ€iteks spetsifikatsioon, andmemudel jne.
- Me vajame kohaliku esitluse loomist selles tÔdes meie programmeerimiskeeles.
Pealegi on sellise esitluse genereerimine peaaegu alati mĂ”istlik â et vĂ€ltida liigset kordust.
TĂŒĂŒpide pakkujad ja annotatsioonide töötlemine
MĂ€rkus: veel ĂŒks, kaasaegsem ja spetsiifilisem lĂ€henemine koodi genereerimiseks jOOQ jaoks, on seotud tĂŒĂŒpide pakkujate kasutamisega, Sel juhul genereerib koodi kompilaator, tegelikult kompileerimise etapis. Sellise koodi lĂ€htefailid ei eksisteeri pĂ”himĂ”tteliselt. Java-s on sarnaseid, kuigi mitte nii elegantseid tööriistu â need on annotatsiooniprotsessorid, nĂ€iteks, .
Teatud mÔttes toimub siin sama, mis esimesel juhul, vÀlja arvatud:
- Te ei nÀe genereeritud koodi (vÔib-olla tundub see kellelegi vÀhem hirmutav?)
- Te peate tagama, et tĂŒĂŒbid oleksid kergesti kĂ€ttesaadavad, see tĂ€hendab, et âtĂ”deâ peab alati olema kĂ€tte saadav. See on lihtne Lomboki puhul, mis annotatsiooniga âtĂ”deâ tĂ€histab. Veidi keerulisem on andmemudelitega, mille töö sĂ”ltub pidevalt kĂ€ttesaadavast elavast ĂŒhendusest.
Mis on probleem koodi genereerimisel?
Lisaks keerulisele kĂŒsimusele selle kohta, kuidas koodi genereerimist kĂ”ige paremini alustada - kĂ€sitsi vĂ”i automaatselt, tuleb mainida ka, et on inimesi, kes arvavad, et koodi genereerimine pole ĂŒldse vajalik. Selle seisukoha pĂ”hjenduseks tuuakse kĂ”ige sagedamini vĂ€lja, et siis on keeruline seadistada kogumisĂŒsteemi. Jah, see on tĂ”epoolest keeruline. Tekkivad lisainfrastruktuuri kulud. Kui alustate teatud tootega (olgu see jOOQ, JAXB vĂ”i Hibernate jne), siis kulub arenduskeskkonna seadistamisele aega, mida tahaksite pigem kasutada API tutvumiseks, et hiljem sellest vÀÀrtust vĂ€lja tuua.
Kui kulu, mis on seotud generaatori ĂŒlesehitusega, on liiga suur, siis tĂ”epoolest on API-s koodi generaatori kasutatavus halvasti tehtud (ja hiljem selgub, et ka kasutaja seadistamine on keeruline). Kasutamise mugavus peaks olema iga sellise API kĂ”rgeim prioriteet. Kuid see on vaid ĂŒks argument koodi genereerimise vastu. Muus osas on tĂ€iesti mĂ”istlik kirjutada kohalik esitus sisemisest vĂ”i vĂ€lsest tĂ”est kĂ€sitsi.
Paljud ĂŒtlevad, et neil pole aega sellega tegeleda. Neil on tĂ€htajad oma Super-Toote osas. Kord hiljem tegeleme kogumisĂŒsteemidega, jĂ”uab kĂŒll. Ma vastan neile:

,
Kuid Hibernate / JPA-s on nii lihtne kirjutada koodi "Java" jaoks.
TĂ”epoolest. Hibernate'i ja selle kasutajate jaoks on see samal ajal Ă”nn ja needus. Hibernate'i puhul saab lihtsalt kirjutada paar ĂŒksust, nĂ€iteks nii:
@Entity
class Book {
@Id
int id;
String title;
}Ja peaaegu kĂ”ik on valmis. NĂŒĂŒd on Hibernate'i ĂŒlesanne genereerida keerulised "detailid", kuidas see ĂŒksus tĂ€pselt mÀÀratakse teie SQL "dialekti" DDL-s:
CREATE TABLE book (
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
title VARCHAR(50),
CONSTRAINT pk_book PRIMARY KEY (id)
);
CREATE INDEX i_book_title ON book (title);... ja hakkame rakendust kÀivitama. TÔeliselt suurepÀrane vÔimalus, et kiiresti tööle asuda ja erinevaid asju katsetada.
Kuid lubage, et ma eksisin.
- Kas Hibernate tÔeliselt rakendab selle nimetatud primaarvÔtme mÀÀratlemist?
- Kas Hibernate loob indeksi TITLE-s? - ma tean kindlalt, et me vajame seda.
- Ja kas Hibernate teeb selle vÔtmest identifitseeriva Identity Specificationi?
Ilmselt mitte. Kui arendate oma projekti nullist, on alati mugav lihtsalt vana andmebaas kÔrvaldada ja luua uus, kui olete vajalikud annotatsioonid lisanud. Nii nÀeb lÔpuks vÀlja olend Book:
@Entity
@Table(name = "book", indexes = {
@Index(name = "i_book_title", columnList = "title")
})
class Book {
@Id
@GeneratedValue(strategy = IDENTITY)
int id;
String title;
}
VÀga hea. Genereeri uuesti. Taaskord, sel juhul on alguses vÀga lihtne.
Aga hiljem tuleb selle eest tasuda.
Varem vÔi hiljem peate minema tootmisse. Just siis lakkab see mudel töötamast. Sest:
Tootmises ei saa te enam vajadusel vana andmebaasi kÔrvaldada ja kÔike puhtalt lehelt alustada. Teie andmebaas muutub pÀrandiks.
Edaspidi peate kirjutama . Aga mis siis juhtub teie olenditega? Saate kas kohandada neid kĂ€sitsi (ja suurendada oma töömahtu kahekordseks) vĂ”i kĂŒsida Hibernate'ilt, et ta genereeriks need uuesti teie jaoks (kui suured on vĂ”imalused, et sellisel viisil genereeritud vastab teie ootustele?) Igatahes jÀÀte kaotajaks.
Nii et niipea, kui lĂ€hete tootmisse, vajate kiireid patĆĄe. Ja neid tuleb tootmisse viia vĂ€ga kiiresti. Kuna te pole valmistunud ega organiseerinud sujuvat tootmisprotsessi oma migratsioonide jaoks, peate kĂ”ik tormiliselt patĆĄeerima. Ja siis ei jĂ”ua te kĂ”ike Ă”igesti teha. Ja sĂŒĂŒdistate Hibernate'i, kuna alati on sĂŒĂŒdi keegi muu, mitte teie...
Selle asemel oleks algusest peale saanud teha kĂ”ik hoopis teisiti. NĂ€iteks panna jalgrattale ĂŒmmargused rattad.
Esialgu andmebaas.
TĂ”eline âtĂ”deâ teie andmebaasi skeemis ja âsuverÀÀnsusâ selle ĂŒle peitub andmebaasis endas. Skeem mÀÀratletakse ainult andmebaasis ja mitte kusagil mujal, ja igal kliendil on sellele skeemile oma koopia, seega on tĂ€iesti mĂ”istlik nĂ”uda skeemi ja selle terviklikkuse jĂ€rgimist otse andmebaasis â seal, kus teave salvestatakse.
See on vana ja isegi kulunud tarkus. Esialgsed ja unikaalsed vĂ”tmed â need on head. VĂ€listingimustes vĂ”tmed â head. Piirangute kontroll â hea. â on hea.
Kuid see pole veel kÔik. NÀiteks kasutades Oracle'i, soovite tÔenÀoliselt nÀidata:
- Millises tabeliruumi teie tabel asub
- Milline on selle PCTFREE vÀÀrtus
- Kui suur on teie jÀrjestuse vahemÀlu (ID taga)
VĂ”ib-olla pole see kĂ”ik vĂ€ikestes sĂŒsteemides oluline, kuid ei pea ootama âsuurte andmeteâ valdkonda sisenemist â vĂ”ib hakata varakult kasutama andmete salvestamise pakkuja poolt pakutud optimeerimist, nagu ĂŒlal mainitud. Ăkski ORM, mida olen nĂ€inud (sealhulgas jOOQ), ei paku juurdepÀÀsu kogu DDL valikute komplektile, mida vĂ”iksite oma andmebaasis kasutada. ORM-id pakuvad mĂ”ningaid tööriistu, mis aitavad DDL-i kirjutada.
Aga lÔpuks, hÀsti kavandatud skeem on kÀsitsi kirjutatud DDL. Iga genereeritud DDL on lihtsalt selle lÀhedane jÀljend.
Kuidas on kliendimudeliga?
Nagu eelnevalt mainitud, vajate kliendil oma andmebaasi skeemi koopia, kliendi vaade. Ăksikasjad on kindlasti paar korda mainitud, see kliendi vaade peab olema sĂŒnkroonitud tegeliku mudeliga. Kuidas seda kĂ”ige paremini saavutada? Koodi generaatori abil.
KÔik andmebaasid pakuvad oma metaandmeid SQL-i kaudu. Siin on, kuidas oma andmebaasist kÔiki tabeleid erinevates SQL-i dialektides saada:
-- H2, HSQLDB, MySQL, PostgreSQL, SQL Server
SELECT table_schema, table_name
FROM information_schema.tables
-- DB2
SELECT tabschema, tabname
FROM syscat.tables
-- Oracle
SELECT owner, table_name
FROM all_tables
-- SQLite
SELECT name
FROM sqlite_master
-- Teradata
SELECT databasename, tablename
FROM dbc.tables
Need pÀringud (vÔi sarnased, sÔltuvalt sellest, kas tuleb arvesse vÔtta ka vaateid, materialiseeritud vaateid, tabeli vÀÀrtusi funktsioone) tÀidetakse samuti kutsumisega JDBC-st vÔi meta-mooduli jOOQ abil.
Selliste pÀringute tulemustest on suhteliselt lihtne genereerida mis tahes kliendi vaade teie andmebaasi mudelile, sÔltumata sellest, millist tehnoloogiat kasutate.
- Kui kasutate JDBC-d vÔi Springi, siis saate luua stringide konstantide kogumi
- Kui kasutate JPA-d, siis vĂ”ite generiseerida ise ĂŒhikut
- Kui kasutate jOOQ-i, siis saate genereerida jOOQ-i meta-mudeli
SÔltuvalt sellest, kui palju vÔimalusi teie kliendi API pakub (nt jOOQ vÔi JPA), vÔib genereeritud meta-mudel olla tÔeliselt rikka ja tÀielik. VÔtame nÀiteks vaikimisi liitmise vÔimaluse, , mis tugineb genereeritud metaandmetele vÀliste vÔtmete suhete kohta, mis kehtivad teie tabelite vahel.
NĂŒĂŒd toob iga andmebaasi suurenemine automaatselt kaasa kliendikoodi uuendamise. Kujutage ette, nĂ€iteks:
ALTER TABLE book RENAME COLUMN title TO book_title;Kas tÔesti soovite seda tööd kaks korda teha? Absoluutselt mitte. Lihtsalt salvestame DDL, töötleme seda teie ehitusprotsessis ja saame uuendatud entiteedi:
@Entity
@Table(name = "book", indexes = {
// Kas olete sellele mÔelnud?
@Index(name = "i_book_title", columnList = "book_title")
})
class Book {
@Id
@GeneratedValue(strategy = IDENTITY)
int id;
@Column("book_title")
String bookTitle;
}VĂ”i uuendatud jOOQ klass. Enamik DDL muudatusi kajastuvad ka semantikas, mitte ainult sĂŒntaksis. SeetĂ”ttu vĂ”ib olla mugav vaadata kompileeritud koodis, milline kood saab (vĂ”i vĂ”ib) teie andmebaasi suurenemise tĂ”ttu mĂ”jutatud.
Ainult ĂŒks tĂ”de
ĂkskĂ”ik, millist tehnoloogiat kasutate, on alati olemas ĂŒks mudel, mis on mingi alamsĂŒsteemi ainus tĂ”eallikas - vĂ”i vĂ€hemalt peaksime seda pĂŒĂŒdma teha ja vĂ€ltima sellist ettevĂ”tte segadust, kus "tĂ”de" on korraga igal pool ja mitte kuskil. KĂ”ik vĂ”iks olla palju lihtsam. Kui vahetate lihtsalt XML-faile mĂ”ne muu sĂŒsteemiga, siis kasutage lihtsalt XSD-d. Vaadake jOOQ meta-mudelit INFORMATION_SCHEMA XML-vormis:
- XSD on hÀsti arusaadav
- XSD mÀrgib XML-i sisu vÀga hÀsti ja vÔimaldab valideerimist kÔigis kliendikeeltes
- XSD-l on hea versioonihaldus ja arenenud tagasiĂŒhilduvus
- XSD-d saab Java koodiks tÔlkida kasutades XJC-d
Viimane punkt on oluline. Suheldes vĂ€list sĂŒsteemiga XML-sĂ”numite kaudu, tahame olla kindlad sĂ”numite kehtivuses. Seda on vĂ€ga lihtne saavutada JAXB, XJC ja XSD abil. Oleks tĂ€ielik hullumeelsus loota, et âJava-esimeneâ lĂ€henamine, kus loome oma sĂ”numid Java objektide kujul, saaks mingil viisil mĂ”istetavalt XML-iks muuta ja edastada teise sĂŒsteemi tarbimiseks. Sellega genereeritud XML oleks vĂ€ga halva kvaliteediga, dokumenteerimata ning seda oleks keeruline arendada. Kui sellise liidese puhul oleks kvaliteedi teenuse taseme (SLA) leping, oleksime selle kohe rikkunud.
Ausalt öeldes juhtub see pidevalt JSON API-dega, kuid see on juba teine lugu, jÀrgmine kord rÀÀgin veel...
Andmebaasid: see on sama asi
Andmebaasidega töötades mĂ”istate, et need kĂ”ik on pĂ”himĂ”tteliselt sarnased. Andmebaas haldab oma andmeid ja peab juhtima skeemi. Skeemi muudatused tuleb viia ellu otse DDL-is, et uuendada ĂŒhte tĂ”e allikat.
Kui allika uuendamine on toimunud, peavad kÔik kliendid samuti oma mudelid uuendama. MÔned kliendid vÔivad olla kirjutatud Java keeles, kasutades jOOQ-d ja Hibernate'i vÔi JDBC-d (vÔi kÔiki neid korraga). Teised kliendid vÔivad olla kirjutatud Perl'is (soovime neile just head Ônne), kolmandad - C#-s. See ei ole oluline. Peamine mudel asub andmebaasis. ORM-i abil genereeritud mudelid on tavaliselt halvasti kvaliteetsed, halvasti dokumenteeritud ja neid on raske arendada.
SeetĂ”ttu Ă€rge tehke vigu. Alustage Ă”igesti. Töödelge andmebaasist lĂ€htuvalt. Looge selline juurutusprotsess, mida saab automatiseerida. Lisage koodigeneraatorid, et mugavalt kopeerida oma andmebaasi mudelit ja edastada seda klientidele. Ja lĂ”petage koodigeneraatorite muretsemine. Need on head. Nende abil saate produktiivsemaks. Peate vaid alguses natuke aega nende seadistamiseks kulutama â ja siis ootavad teid aastaid suurenenud tootlikkust, millest kujuneb teie projekti lugu.
Ărge tĂ€nage veel, hiljem.
Selgitus
Selguse huvides: see artikkel ei propageeri mingil moel, et teie andmebaasi mudel peaks kogu sĂŒsteemi painutama (st, teemaala, Ă€ri loogika jne). Selles artiklis rÀÀgin ma sellest, et kliendi kood, mis suhtleb andmebaasiga, peaks toimima andmebaasi mudelilt lĂ€htuvalt, et mitte kopeerida andmebaasi mudelit "esimese klassi" staatusesse. Selline loogika asub tavaliselt teabe juurdepÀÀsu tasemel teie kliendis.
Kaksitasandilistes arhitektuurides, mis on siiani mĂ”nes kohas sĂ€ilinud, vĂ”ib selline sĂŒsteemi mudel olla ainus vĂ”imalus. Siiski, enamikus sĂŒsteemides tundub andmete juurdepÀÀsu tase olevat âalamsĂŒsteemâ, mis kapseldab endas andmebaasi mudelit.
Erandid
Igal reeglil on erandeid ja olen juba öelnud, et andmebaasi esmajÀrjekorras ja lÀhtekoodi genereerimise lÀhenemine vÔib mÔnikord olla sobimatu. Siin on paar sellist erandit (ilmselt on ka teisi):
- Kui skeem ei ole teada ja seda tuleb avada. NĂ€iteks, kui olete tööriista pakkuja, mis aitab kasutajatel navigeerida mistahes skeemis. Uff. Siin ei saa kĂŒll ilma koodi genereerimiseta hakkama. Kuid siiski - andmebaas tuleb kĂ”igepealt.
- Kui skeem peab jooksvalt genereerima mingi ĂŒlesande lahendamiseks. See nĂ€ide tundub olevat natuke keeruline versioon mustrist , st teil ei ole tĂ”eliselt mÀÀratletud skeemi. Sel juhul ei saa tihti isegi kindel olla, et teile sobib RDBMS.
Erandid on olemuselt erandlikud. Enamiku RDBMS-iga seotud juhtumite puhul on skeem ette teada, see asub RDBMS-is ja on ainus "tÔde"; kÔik kliendid peavad selle pÔhjal endale omandama koopiad, mis saadud sellest. Ideaalis tuleks kasutada koodigeneraatorit.
Allikas: habr.com
