Vejledning til Kotlin Exposed Framework

1. Introduktion

I denne vejledning skal vi se på, hvordan man forespørger på en relationsdatabase ved hjælp af Exposed.

Exposed er et open source-bibliotek (Apache-licens) udviklet af JetBrains, som giver en idiomatisk Kotlin API til nogle relationelle databaseimplementeringer, mens forskellene mellem databaseleverandører udjævnes.

Exposed kan bruges både som et højt niveau DSL over SQL og som en let ORM (Object-Relational Mapping). Således dækker vi begge anvendelser i løbet af denne vejledning.

2. Eksponeret rammeopsætning

Exposed er endnu ikke på Maven Central, så vi er nødt til at bruge et dedikeret lager:

  eksponeret eksponeret //dl.bintray.com/kotlin/exposed 

Derefter kan vi inkludere biblioteket:

 org.jetbrains.exposed eksponeret 0.10.4 

I de følgende sektioner viser vi også eksempler, der bruger H2-databasen i hukommelsen:

 com.h2database h2 1.4.197 

Vi kan finde den nyeste version af Exposed on Bintray og den nyeste version af H2 på Maven Central.

3. Oprette forbindelse til databasen

Vi definerer databaseforbindelser med Database klasse:

Database.connect ("jdbc: h2: mem: test", driver = "org.h2.Driver")

Vi kan også specificere en bruger og en adgangskode som navngivne parametre:

Database.connect ("jdbc: h2: mem: test", driver = "org.h2.Driver", bruger = "mig selv", adgangskode = "hemmelig")

Bemærk at påberåbe sig Opret forbindelse opretter ikke en forbindelse til DB med det samme. Det gemmer bare forbindelsesparametrene til senere.

3.1. Yderligere parametre

Hvis vi har brug for at angive andre forbindelsesparametre, bruger vi en anden overbelastning af Opret forbindelse metode, der giver os fuld kontrol over erhvervelsen af ​​en databaseforbindelse:

Database.connect ({DriverManager.getConnection ("jdbc: h2: mem: test; MODE = MySQL")})

Denne version af Opret forbindelse kræver en lukningsparameter. Exposed påberåber lukningen, når den har brug for en ny forbindelse til databasen.

3.2. Brug af en Datakilde

Hvis vi i stedet opretter forbindelse til databasen ved hjælp af en Datakilde, som det normalt er tilfældet i virksomhedsapplikationer (f.eks. for at drage fordel af pooling af forbindelser), kan vi bruge det relevante Opret forbindelse overbelaste:

Database.connect (datakilde)

4. Åbning af en transaktion

Hver databasehandling i Exposed har brug for en aktiv transaktion.

Det transaktion metoden tager en lukning og påberåber sig den med en aktiv transaktion:

transaktion {// Gør seje ting}

Det transaktion returnerer hvad lukningen returnerer. Derefter lukker Exposed automatisk transaktionen, når eksekveringen af ​​blokken ophører.

4.1. Forpligtelse og tilbageførsel

Når transaktion blokken returnerer med succes, Exposed forpligter transaktionen. Når lukningen i stedet afsluttes ved at kaste en undtagelse, ruller rammen transaktionen tilbage.

Vi kan også manuelt begå eller tilbageføre en transaktion. Lukningen, som vi leverer til transaktion er faktisk en forekomst af Transaktion klasse takket være Kotlin-magi.

Således har vi en begå og en tilbageførsel tilgængelig metode:

transaktion {// Foretag nogle ting () // Gør andre ting}

4.2. Registreringserklæringer

Når vi lærer rammen eller debugging, kan vi måske finde det nyttigt at inspicere de SQL-sætninger og forespørgsler, som Exposed sender til databasen.

Vi kan nemt tilføje en sådan logger til den aktive transaktion:

transaktion {addLogger (StdOutSqlLogger) // Gør ting}

5. Definition af tabeller

Normalt arbejder vi i Exposed ikke med rå SQL-strenge og navne. I stedet definerer vi tabeller, kolonner, nøgler, forhold osv. Ved hjælp af et højt niveau DSL.

Vi repræsenterer hver tabel med en forekomst af Bord klasse:

objekt StarWarsFilms: Tabel ()

Exposed beregner automatisk navnet på tabellen fra klassens navn, men vi kan også give et eksplicit navn:

objekt StarWarsFilms: Tabel ("STAR_WARS_FILMS")

5.1. Kolonner

En tabel er meningsløs uden kolonner. Vi definerer kolonner som egenskaber for vores tabelklasse:

objekt StarWarsFilms: Tabel () {val id = heltal ("id"). autoIncrement (). primaryKey () val sequelId = heltal ("sequel_id"). unikIndex () val navn = varchar ("navn", 50) val direktør = varchar ("direktør", 50)}

Vi har udeladt typerne for kortfattethed, da Kotlin kan udlede dem for os. Under alle omstændigheder er hver kolonne af typen Kolonne og det har et navn, en type og muligvis typeparametre.

5.2. Primære nøgler

Som vi kan se fra eksemplet i det foregående afsnit, vi kan let definere indekser og primære nøgler med en flydende API.

I det almindelige tilfælde af en tabel med en heltal primærnøgle giver Exposed imidlertid klasser IntIdTable og LongIdTable der definerer nøglen for os:

objekt StarWarsFilms: IntIdTable () {val sequelId = heltal ("sequel_id"). unikIndex () val name = varchar ("name", 50) val director = varchar ("director", 50)}

Der er også en UUIDTable; desuden kan vi definere vores egne varianter ved at underklasse IdTable.

5.3. Udenlandske nøgler

Udenlandske nøgler er nemme at introducere. Vi drager også fordel af statisk skrivning, fordi vi altid henviser til egenskaber, der er kendt på kompileringstidspunktet.

Antag, at vi vil spore navnene på de skuespillere, der spiller i hver film:

objekt Spillere: Tabel () {val sequelId = heltal ("sequel_id") .uniqueIndex () .references (StarWarsFilms.sequelId) val name = varchar ("name", 50)}

For at undgå at skulle stave kolonnetypen (i dette tilfælde heltal) når det kan afledes fra den refererede kolonne, kan vi bruge reference metode som stenografi:

val sequelId = reference ("sequel_id", StarWarsFilms.sequelId) .uniqueIndex ()

Hvis henvisningen er til den primære nøgle, kan vi udelade kolonnens navn:

val filmId = reference ("film_id", StarWarsFilms)

5.4. Oprettelse af tabeller

Vi kan oprette tabellerne som defineret ovenfor programmatisk:

transaktion {SchemaUtils.create (StarWarsFilms, Players) // Gør ting}

Tabellerne oprettes kun, hvis de ikke allerede findes. Der er dog ingen understøttelse af databaseoverførsler.

6. Forespørgsler

Når vi har defineret nogle tabelklasser, som vi har vist i de foregående sektioner, kan vi udstede forespørgsler til databasen ved hjælp af de udvidelsesfunktioner, der leveres af rammen.

6.1. Vælg alle

For at udtrække data fra databasen bruger vi Forespørgsel objekter bygget fra bordklasser. Den enkleste forespørgsel er en, der returnerer alle rækkerne i en given tabel:

val forespørgsel = StarWarsFilms.selectAll ()

En forespørgsel er en Iterabel, så det understøtter for hver:

query.forEach {assertTrue {it [StarWarsFilms.sequelId]> = 7}}

Lukningsparameteren kaldes implicit det i eksemplet ovenfor er en forekomst af ResultatRow klasse. Vi kan se det som et kort indtastet efter kolonne.

6.2. Valg af et undersæt af kolonner

Vi kan også vælge et undersæt af tabellens kolonner, dvs. udføre en projektion ved hjælp af skive metode:

StarWarsFilms.slice (StarWarsFilms.name, StarWarsFilms.director) .selectAll () .forEach {assertTrue {it [StarWarsFilms.name] .startsWith ("The")}}

Vi bruger skive at anvende en funktion til en kolonne også:

StarWarsFilms.slice (StarWarsFilms.name.countDistinct ())

Ofte, når du bruger samlede funktioner som f.eks tælle og gns. vi har brug for en gruppe efter klausul i forespørgslen. Vi taler om gruppen i afsnit 6.5.

6.3. Filtrering efter hvor udtryk

Exposed indeholder en dedikeret DSL til hvor udtryk, som bruges til at filtrere forespørgsler og andre typer udsagn. Dette er et mini-sprog baseret på de kolonneegenskaber, vi er stødt på tidligere, og en række boolske operatører.

Dette er et hvor udtryk:

{(StarWarsFilms.director som "J.J.%") og (StarWarsFilms.sequelId eq 7)}

Dens type er kompleks; det er en underklasse af SqlExpressionBuilder, der definerer operatører som f.eks ligesom, ligning, og. Som vi kan se, er det en sekvens af sammenligninger kombineret med og og eller operatører.

Vi kan videregive et sådant udtryk til Vælg metode, som igen returnerer en forespørgsel:

val select = StarWarsFilms.select {...} assertEquals (1, select.count ())

Takket være typeforståelse behøver vi ikke at stave den komplekse type af hvor-udtrykket, når det sendes direkte til Vælg fremgangsmåde som i ovenstående eksempel.

Da udtryk er Kotlin-objekter, er der ingen specielle bestemmelser for forespørgselsparametre. Vi bruger simpelthen variabler:

val sequelNo = 7 StarWarsFilms.select {StarWarsFilms.sequelId> = sequelNo}

6.4. Avanceret filtrering

Det Forespørgsel genstande returneret af Vælg og dens varianter har en række metoder, som vi kan bruge til at forfine forespørgslen.

For eksempel vil vi muligvis ekskludere duplikerede rækker:

query.withDistinct (true) .forEach {...}

Eller vi vil måske kun returnere et undersæt af rækkerne, for eksempel når vi paginerer resultaterne for brugergrænsefladen:

query.limit (20, offset = 40) .forEach {...}

Disse metoder returnerer en ny Forespørgsel, så vi let kan kæde dem.

6.5. BestilleVed og GruppeVed

Det Query.orderBy metoden accepterer en liste over kolonner tilknyttet en Sorteringsrækkefølge værdi, der angiver, om sortering skal være stigende eller faldende:

query.orderBy (StarWarsFilms.name til SortOrder.ASC)

Mens grupperingen efter en eller flere kolonner, især nyttig ved brug af samlede funktioner (se afsnit 6.2.), Opnås ved hjælp af groupBy metode:

StarWarsFilms .slice (StarWarsFilms.sequelId.count (), StarWarsFilms.director) .selectAll () .groupBy (StarWarsFilms.director)

6.6. Tilslutter sig

Forbindelser er uden tvivl et af salgsstederne i relationsdatabaser. I de mest enkle tilfælde, når vi har en fremmed nøgle og ingen tilslutningsbetingelser, kan vi bruge en af ​​de indbyggede tilslutningsoperatører:

(StarWarsFilms innerJoin Players) .selectAll ()

Her har vi vist indre tilmeld dig, men vi har også venstre, højre og tværsammenføjning til rådighed med det samme princip.

Derefter kan vi tilføje sammenføjningsbetingelser med et hvor-udtryk; for eksempel, hvis der ikke er en fremmed nøgle, og vi skal udføre sammenføjningen eksplicit:

(StarWarsFilms innerJoin Players) .Vælg {StarWarsFilms.sequelId eq Players.sequelId}

Generelt er den fulde form for en sammenføjning følgende:

val complexJoin = Deltag (StarWarsFilms, Players, onColumn = StarWarsFilms.sequelId, otherColumn = Players.sequelId, joinType = JoinType.INNER, additionalConstraint = {StarWarsFilms.sequelId eq 8}) complexJoin.selectAll ()

6.7. Aliasing

Takket være kortlægningen af ​​kolonnenavne til egenskaber behøver vi ingen aliasing i en typisk sammenføjning, selv når kolonnerne tilfældigvis har det samme navn:

(StarWarsFilms innerJoin Players) .selectAll () .forEach {assertEquals (it [StarWarsFilms.sequelId], it [Players.sequelId])}

Faktisk i ovenstående eksempel, StarWarsFilms.sequelId og Players.sequelId er forskellige kolonner.

Men når den samme tabel vises mere end én gang i en forespørgsel, vil vi måske give den et alias. Til det bruger vi alias fungere:

val sequel = StarWarsFilms.alias ("sequel")

Vi kan derefter bruge aliaset lidt som en tabel:

Deltag i (StarWarsFilms, sequel, additionalConstraint = {sequel [StarWarsFilms.sequelId] eq StarWarsFilms.sequelId + 1}). SelectAll (). ForEach {assertEquals (it [sequel [StarWarsFilms.sequelId]], it [StarWarsFilms.sequel )}

I ovenstående eksempel kan vi se, at efterfølger alias er en tabel, der deltager i en deltagelse. Når vi ønsker at få adgang til en af ​​dens kolonner, bruger vi den aliasede tabels kolonne som en nøgle:

efterfølger [StarWarsFilms.sequelId]

7. Erklæringer

Nu hvor vi har set, hvordan man forespørger om databasen, skal vi se på, hvordan man udfører DML-udsagn.

7.1. Indsættelse af data

For at indsætte data kalder vi en af ​​varianterne af indsæt fungere. Alle varianter lukker:

StarWarsFilms.insert {it [name] = "The Last Jedi" it [sequelId] = 8 it [director] = "Rian Johnson"}

Der er to bemærkelsesværdige genstande involveret i lukningen ovenfor:

  • det her (selve lukningen) er en forekomst af StarWarsFilms klasse; Derfor kan vi få adgang til kolonnerne, som er egenskaber, ved deres ukvalificerede navn
  • det (lukningsparameteren) er en Indsæt erklæring; jegt er en kortlignende struktur med en plads til hver søjle, der skal indsættes

7.2. Udpakning af automatisk stigning af kolonneværdier

Når vi har en indsætningserklæring med automatisk genererede kolonner (typisk automatisk forøgelse eller sekvenser), vil vi muligvis hente de genererede værdier.

I det typiske tilfælde har vi kun en genereret værdi, og vi kalder insertAndGetId:

val id = StarWarsFilms.insertAndGetId {it [name] = "The Last Jedi" it [sequelId] = 8 it [director] = "Rian Johnson"} assertEquals (1, id.value)

Hvis vi har mere end en genereret værdi, kan vi læse dem ved navn:

val insert = StarWarsFilms.insert {it [name] = "The Force Awakens" it [sequelId] = 7 it [director] = "J.J. Abrams"} assertEquals (2, indsæt [StarWarsFilms.id] ?. værdi)

7.3. Opdatering af data

Vi kan nu bruge det, vi har lært om forespørgsler og indsættelser, til at opdatere eksisterende data i databasen. Faktisk ligner en simpel opdatering en kombination af en select med en insert:

StarWarsFilms.update ({StarWarsFilms.sequelId eq 8}) {it [name] = "Episode VIII - The Last Jedi"}

Vi kan se brugen af ​​et hvor-udtryk kombineret med et UpdateStatement lukning. Faktisk, UpdateStatement og InsertStatement dele det meste af API og logik gennem en fælles superklasse, UpdateBuilder, som giver mulighed for at indstille værdien af ​​en kolonne ved hjælp af idiomatiske firkantede parenteser.

Når vi har brug for at opdatere en kolonne ved at beregne en ny værdi ud fra den gamle værdi, udnytter vi SqlExpressionBuilder:

StarWarsFilms.update ({StarWarsFilms.sequelId eq 8}) {med (SqlExpressionBuilder) {it.update (StarWarsFilms.sequelId, StarWarsFilms.sequelId + 1)}}

Dette er et objekt, der giver infix-operatorer (som f.eks plus, minus og så videre), som vi kan bruge til at oprette en opdateringsinstruktion.

7.4. Sletning af data

Endelig kan vi slette data med deleteWhere metode:

StarWarsFilms.deleteWhere ({StarWarsFilms.sequelId eq 8})

8. DAO API, en letvægts ORM

Indtil videre har vi brugt Exposed til direkte at kortlægge fra operationer på Kotlin-objekter til SQL-forespørgsler og udsagn. Hver metode påkald som indsæt, opdater, vælg og så videre resulterer i, at en SQL-streng straks sendes til databasen.

Imidlertid har Exposed også et højere niveau DAO API, der udgør en simpel ORM. Lad os nu dykke ned i det.

8.1. Enheder

I de foregående sektioner har vi brugt klasser til at repræsentere databasetabeller og til at udtrykke operationer over dem ved hjælp af statiske metoder.

Flytter vi et skridt videre, kan vi definere enheder baseret på disse tabelklasser, hvor hver forekomst af en enhed repræsenterer en databaserække:

klasse StarWarsFilm (id: EntityID): Enhed (id) {ledsagende objekt: EntityClass (StarWarsFilms) var sequelId af StarWarsFilms.sequelId var navn af StarWarsFilms.name var direktør af StarWarsFilms.director}

Lad os nu analysere ovenstående definition stykke for stykke.

I den første linje kan vi se, at en enhed er en klasse, der udvider sig Enhed. Det har et ID med en bestemt type, i dette tilfælde Int.

klasse StarWarsFilm (id: EntityID): Entity (id) {

Derefter støder vi på en ledsagende objektdefinition. Det ledsagende objekt repræsenterer enhedsklassen, det vil sige de statiske metadata, der definerer enheden og de operationer, vi kan udføre på den.

Desuden forbinder vi enheden i erklæringen om ledsagende objekt, StarWarsFilm - ental, da det repræsenterer en enkelt række til bordet, StarWarsFilms - flertal, fordi det repræsenterer samlingen af ​​alle rækkerne.

ledsagende objekt: EntityClass (StarWarsFilms)

Endelig har vi egenskaberne implementeret som ejendomsdelegater til de tilsvarende tabelkolonner.

var sequelId af StarWarsFilms.sequelId var navn af StarWarsFilms.name var instruktør af StarWarsFilms.director

Bemærk, at vi tidligere har erklæret kolonnerne med val fordi de er uforanderlige metadata. Nu erklærer vi i stedet enhedens egenskaber med var, fordi de er mutable slots i en databaserække.

8.2. Indsættelse af data

For at indsætte en række i en tabel opretter vi simpelthen en ny forekomst af vores enhedsklasse ved hjælp af den statiske fabriksmetode ny i en transaktion:

val theLastJedi = StarWarsFilm.new {name = "The Last Jedi" sequelId = 8 instruktør = "Rian Johnson"}

Bemærk, at operationer mod databasen udføres doven; de udstedes kun, når varm cache er skyllet. Til sammenligning kalder Dvaletilstand den varme cache a session.

Dette sker automatisk, når det kræves; f.eks. første gang vi læser den genererede identifikator, udfører Exposed lydløst indsætningserklæringen:

assertEquals (1, theLastJedi.id.value) // Læsning af ID forårsager en skylning

Sammenlign denne adfærd med indsæt metode fra afsnit 7.1., som straks udsender en erklæring mod databasen. Her arbejder vi på et højere abstraktionsniveau.

8.3. Opdatering og sletning af objekter

For at opdatere en række tildeler vi blot dens egenskaber:

theLastJedi.name = "Afsnit VIII - Den sidste Jedi"

Mens vi sletter et objekt, vi kalder slet på det:

theLastJedi.delete ()

Som med ny, opdateringen og operationerne udføres doven.

Opdateringer og sletninger kan kun udføres på et tidligere indlæst objekt. Der er ingen API til massive opdateringer og sletninger. I stedet skal vi bruge det lavere API, som vi har set i afsnit 7. De to API'er kan stadig bruges sammen i samme transaktion.

8.4. Forespørgsel

Med DAO API kan vi udføre tre typer forespørgsler.

For at indlæse alle objekter uden betingelser bruger vi den statiske metode alle:

val film = StarWarsFilm.all ()

For at indlæse et enkelt objekt efter ID kalder vi findById:

val theLastJedi = StarWarsFilm.findById (1)

Hvis der ikke er noget objekt med det ID, findById vender tilbage nul.

Endelig bruger vi i det generelle tilfælde finde med et hvor udtryk:

val film = StarWarsFilm.find {StarWarsFilms.sequelId eq 8}

8.5. Mange-til-en-foreninger

Ligesom sammenføjninger er et vigtigt træk ved relationsdatabaser, kortlægningen af ​​sammenføjninger til referencer er et vigtigt aspekt af en ORM. Så lad os se, hvad Exposed har at tilbyde.

Antag, at vi vil spore brugernes vurdering af hver film. For det første definerer vi to yderligere tabeller:

objekt Brugere: IntIdTable () {val navn = varchar ("navn", 50)} objekt UserRatings: IntIdTable () {val værdi = lang ("værdi") val film = reference ("film", StarWarsFilms) val bruger = reference ("bruger", Brugere)}

Derefter skriver vi de tilsvarende enheder. Lad os udelade Bruger enhed, hvilket er trivielt, og flytte direkte til Brugerbedømmelse klasse:

klasse UserRating (id: EntityID): IntEntity (id) {ledsagende objekt: IntEntityClass (UserRatings) var værdi af UserRatings.value var film af StarWarsFilm refereretOn UserRatings.film var bruger af bruger referencedOn UserRatings.user}

Bemærk især refereret På infix-metode kalder på egenskaber, der repræsenterer foreninger. Mønsteret er følgende: a var erklæring, ved den refererede enhed refereret På henvisningskolonnen.

Egenskaber, der erklæres på denne måde, opfører sig som almindelige egenskaber, men deres værdi er det tilknyttede objekt:

val someUser = User.new {name = "Some User"} val rating = UserRating.new {value = 9 user = someUser film = theLastJedi} assertEquals (theLastJedi, rating.film)

8.6. Valgfri sammenslutninger

De foreninger, vi har set i det foregående afsnit, er obligatoriske, dvs. vi skal altid angive en værdi.

Hvis vi ønsker en valgfri tilknytning, skal vi først erklære kolonnen som ugyldig i tabellen:

val user = reference ("user", Users) .nullable ()

Så bruger vi optionalReferencedOn i stedet for refereret På i enheden:

var bruger af bruger optionalReferencedOn UserRatings.user

På den måde, den bruger ejendom er ugyldig.

8.7. En-til-mange foreninger

Vi vil måske også kortlægge den modsatte side af foreningen. En vurdering handler om en film, det er det, vi modellerer i databasen med en fremmed nøgle; derfor har en film en række ratings.

For at kortlægge en films vurderinger tilføjer vi simpelthen en ejendom til den ene side af foreningen, det vil sige filmenheden i vores eksempel:

klasse StarWarsFilm (id: EntityID): Enhed (id) {// Andre egenskaber elided val ratings af UserRating referrersOn UserRatings.film}

Mønsteret ligner mønsteret for mange-til-en-forhold, men det bruger henvisere På. Den således definerede ejendom er en Iterabel, så vi kan krydse det med for hver:

theLastJedi.ratings.forEach {...}

Bemærk, at vi i modsætning til almindelige egenskaber har defineret vurderinger med val. Faktisk er ejendommen uforanderlig, vi kan kun læse den.

Værdien af ​​ejendommen har heller ikke nogen API til mutation. Så for at tilføje en ny vurdering skal vi oprette den med en henvisning til filmen:

UserRating.new {value = 8 user = someUser film = theLastJedi}

Så er filmen vurderinger listen indeholder den nyligt tilføjede vurdering.

8.8. Mange-til-mange foreninger

I nogle tilfælde har vi muligvis brug for en mange-til-mange-forbindelse. Lad os sige, at vi vil tilføje en reference og Aktører bord til StarWarsFilm klasse:

object Actors: IntIdTable () {val firstname = varchar ("firstname", 50) val lastname = varchar ("lastname", 50)} class Actor (id: EntityID): IntEntity (id) {companion object: IntEntityClass (Actors) var fornavn efter Actors.firstname var efternavn af Actors.lastname}

Efter at have defineret tabellen og enheden har vi brug for en anden tabel til at repræsentere foreningen:

objekt StarWarsFilmActors: Tabel () {val starWarsFilm = reference ("starWarsFilm", StarWarsFilms) .primaryKey (0) val actor = reference ("actor", Actors) .primaryKey (1)}

Tabellen har to kolonner, der begge er fremmednøgler, og som også udgør en sammensat primærnøgle.

Endelig kan vi forbinde associeringstabellen med StarWarsFilm enhed:

klasse StarWarsFilm (id: EntityID): IntEntity (id) {ledsagende objekt: IntEntityClass (StarWarsFilms) // Andre egenskaber elided var skuespillere af Actor via StarWarsFilmActors}

I skrivende stund er det ikke muligt at oprette en enhed med en genereret identifikator og inkludere den i en mange-til-mange-tilknytning i den samme transaktion.

Faktisk er vi nødt til at bruge flere transaktioner:

// Opret først filmen val film = transaktion {StarWarsFilm.new {name = "The Last Jedi" sequelId = 8 instruktør = "Rian Johnson" r}} // Opret derefter skuespilleren val actor = transaktion {Actor.new {firstname = "Daisy" lastname = "Ridley"}} // Til sidst skal du forbinde de to sammen transaktion {film.actors = SizedCollection (listOf (skuespiller))}

Her har vi brugt tre forskellige transaktioner for nemheds skyld. Imidlertid ville to have været tilstrækkelige.

9. Konklusion

I denne artikel har vi givet en grundig oversigt over den eksponerede ramme for Kotlin. For yderligere information og eksempler, se Exposed wiki.

Implementeringen af ​​alle disse eksempler og kodestykker findes i GitHub-projektet.


$config[zx-auto] not found$config[zx-overlay] not found