sommaire
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–lapersistance–lacouched'accèsauxdonnées–lemappingobjet/relationnel–JavaPersistenceAPI(JPA)–SpringData–miseenoeuvre–annexes
lapersistancelaguerredesmondesLapersistance,c'estlemécanismeresponsabledelasauvegardeetdelarestaurationdesdonnéesdansunespacenonvolatile(fichierstexte,XML,basesdedonnées...).
Destechnologiestrèsdifférentesexistentdansunprojet:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–leslangagesdeprogrammationobjet:-trèsrépanduscarilsontfaitleurspreuves-trèsoutillés
–lesbasesdedonnéesrelationnelles:-les+utiliséesetconviennentbienauxdonnéesstructurées-beaucoupdereculsurcestechnologies-puissantlangagederequêtes(SQL)-maislesénormesvolumétriessontproblématiques-maisnécessiteunescalabilitéverticaleencasderessourcesinsuffisantes
–lesbasesdedonnéesNoSQL(NotOnlySQL):-enforteexpansion-meilleuregestiondesfortesvolumétries-scalabilitéhorizontale+hautedisponibilité-plutôtutiliséespourdesdonnéesfaiblementstructurées,nonrelationnelles-chacunespécialiséedansundomaine(clé-valeur,document...).
lapersistancelaguerredesmondesCommentfairecommuniquercesmondes?
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–codageàlamaindesrequêtesSQL:-implémentationsdifférentesd'unebaseàl'autre(ex:codeSQLdifférent)-codedifficileàtester
–mappingobjet/relationnel(ORM:ObjectRelationalMapping):-miseenrelationdesobjetsavecdestables-simplificationdel'écrituredelacouched'accèsauxdonnées-abstractiondesrequêtes-utilisationdeframeworksdédiés.
lacouched'accèsauxdonnéesL'implémentationdelapersistancedoitêtreisoléedanslacouched'accèsauxdonnées.
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
lacouched'accèsauxdonnéesElles'appuiesurledesignpatternDAO(DataAccessObject)pourrendrelacouchemétierindépendantedelasourcededonnéessous-jacente(SQL,mappingO/R...):
LeDAOproposegénéralementlesméthodesCRUD(Create/Read/Update/Delete)suivantes:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–expositiond'uneinterfaceindépendantedelatechnologieutilisée–implémentationdelalogiquedepersistancedansunetechnologiedonnée–échangedeDTO(DataTransferObject,ie.lesconceptsmétiers)représentantlemodèleobjetpersistant.
–get:récupérationd'unobjetpersistantgrâceàsonidentifiant–create:enregistrementd'unnouvelobjetpersistant–update:miseàjourd'unobjetpersistant–delete:suppressiond'unobjetpersistant–findAll:récupérationdetouteslesinstancesd'uneclassepersistante–findBy:récupérationdesobjetspersistantsselondescritèresdesélectionoudetri.
lacouched'accèsauxdonnéesL'interfaceduDAOexposelesopérationsdisponiblespourchaqueobjetpersistant.
L'implémentationpermetderéalisercesopérationsdansplusieurstechnologies,oumêmedebouchonneràdesfinsdetestsunitaires.
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
lemappingobjet/relationnel
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
lemappingobjet/relationnelIls'agitdedéfinirlescorrespondancesetrèglesdepassageentrelesobjetsetlestables,pourl'écriture,lasuppression,larecherche...
Danslecasgénéral,1instancedeclasse=1enregistrementdansunetable
Ilfautpourcelaprendreencomptelesdifférencesentreles2modèles:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–l'organisationdesdonnées
objets/attributs<=>tables/colonnes
–letypagedesdonnées
conversions,restrictions:String<=>varchar
–l'unicitédesobjets
identifant(référence)enmémoire<=>cléprimaire
–relationsentreobjets/données
héritage,associations<=>tablesdejointures,clésexternes
lemappingobjet/relationnelLesapportsd'unframeworkdemappingobjet/relationnelsont:
Maisilyaquelquesdésavantages:
Hibernateestleframeworkdemappingobjet/relationnelrecommandé.
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–codeplusclair,moinsverbeux–pasdeconnaissanceSQLnécessaire(maisutilequandmême)–codeprojetindépendantdelaBDDsous-jacente–migrationfacilitéed'uneBDDàuneautre–gaindeproductivité–fonctionnalitésavancéesconfigurables:cache,transactions,pagination...–meilleuresperformances(encasdebonneconfiguration)–outillage:affichagedesrequêtesgénérées,reverseengineering...
–nouveauxconceptsàappréhender–importantsproblèmesdeperformanceencasdemauvaiseconfiguration–peuts'avérerlourdàmettreenplacepourdetouspetitsprojets.
lemappingobjet/relationnelVoicilesprincipalesfonctionsavancéesapportéesparl’ORM:
Lechoixentrelazyeteagerloadingesttrèsdépendantducasd'utilisation.C'estunsubtilmélangeentre:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–gestiondecachesd'objetspersistants:-permetdeminimiserlenombrederequêtesverslasourcededonnées
–gestiondelaconcurrenced'accès:-verrouillageoptimisteoupessimistedesdonnées
–optimisationduchargementdesobjetspersistants:-àlademande(lazyloading)pournepaschargerd'objetsinutiles-dugraphecomplet(eagerloading)d'objetspersistantspouréviterl'effetN+1Select.
–lenombrederequêtes–laquantitéd'informationsramenéesparrequête–l'utilisationquiestfaitedecesinformations.
JPAJPA(JavaPersistenceAPI)estunstandarddelaplateformeJEEquidéfinit:
LesobjetsprincipalementmanipuléssontlesEntity:classespersistantes(POJO)avecdesannotationsJPAdéfinissantlescorrespondancesentreobjets/attributsettables/champs.
TouslesDAOmanipulantchacundesobjetspersistentsontunegrandepartiedeleurcodequiestcommune:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–desrèglesdecorrespondanceentreobjetsJavaettablesdeBDDrelationnelles–lesinterfaces(l'API)àrespecter.
–seconnecteràlaBDD–ouvrirunetransaction–écrireunerequêtesurcetobjetpersistent–récupérerlerésultat–créeroumettreàjourlebeanJava–commiter/rollbackerlatransaction–...
JPALesframeworksdemappingO/R(Hibernate...):
Avantages:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–implémententlesinterfacesdel'APIJPA–ajoutentparfoisdesfonctionnalitéspropres.
–lecodecommunauxDAOestfourni(lacouched'abstraction)–encasdechangementdeframework(pourdesraisonsdeperformances,deroadmap...):
-changersimplementlalibrairieutilisée(pom.xmlMaven)paruneautrerespectantlestandardJPA-lecodeapplicatifs'appuyantsurJPAresteinchangé.
JPAparamétrageavecSpringBootPardéfaut,SpringBootoffreunparamétragedelapersistanceavecuneconfigurationrespectantlesbonnespratiquesenvigueur.
Pouraffinerceparamétrage,onpeutajouterdesinformationsdanslefichierapplication.properties(ouapplication.yml):
spring.jpa.show-sql:true
#PostgreSQLparametersspring.datasource.url=jdbc:postgresql://localhost:port/...spring.datasource.username=<myUsername>spring.datasource.password=<myPwd>spring.datasource.driver-class-name=org.postgresql.Driver
D'autresparamètressontdisponibles.
Note:endehorsd'uneapplicationSpringBoot,ilfautconfigurerlapersistencedansunfichierpersistence.xml,ycomprislespréconfigurationsquisontoffertesdebaseparSpringBoot(voirenannexes).
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
modèleobjet4classesavecunhéritageetuneassociation
JPAexempledemappingO/R
modèlerelationnel2tablesdontuneavecunecléétrangère
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
JPA1/fairecorrespondrelesclassesetlestables
Enbasededonnées,l'héritagepeutêtrereprésentédedifférentesfaçons:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–1classe<=>1tabledanslescasclassiques,maiscen'estpassystématique,parexemplepourreprésenterunhéritage.
–1tablepourlahiérarchiecomplète,avecunchampdiscriminantetdescolonnesvides–1tableparclasseconcrèteavecunion:lacléidartestpartagéeparles2tablesServiceetMateriel–1tablepourchaqueclassedelahiérarchiesibeaucoupd'attributs(etpeuencommun).
JPA1/fairecorrespondrelesclassesetlestablesAvecJPA,grâceauxannotations:
@Entity@Table(name="Catalogue")//ici,nonutilepublicclassCatalogueimplementsSerializable{
privateintidentifiant;privateStringnom;privateStringdescription;privateList<Article>articles;
//gettersandsetters...}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–@Entity:indiquequ'ils'agitd'uneentité(classe)persistante–@Table(optionnel):déterminelenomdelatables'iln'estpaslemêmequeceluidelaclasse.
JPA2/fairecorrespondrelesattributssimplesetlescolonnesLesattributssimplessontceuxquineréférencentpasd'autresobjetspersistants.
Pourchacund'eux,desconversionsserontappliquéesentreletypeJavaetletypeSQL.
Pourlecasparticulierdelacléprimaire,ilfautindiquercommentelleestgénérée.
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
JPA2/fairecorrespondrelesattributssimplesetlescolonnesAvecJPA,grâceauxannotations:
@EntitypublicclassCatalogueimplementsSerializable{
@Id@GeneratedValue(strategy=GenerationType.AUTO)@Column(name="idcat")privateintidentifiant;
@Column(name="nomcat",nullable=false,unique=true)privateStringnom;...}
Attention:nullable=false,unique=truenesontutilesquesiongénèreleschémadeBDDàpartirducode.Auruntime,cescontraintessurleschampssontvérifiéesparlaBDD,pasparl'application.
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–@Id:indiquequel'attributjouelerôledecléprimaire(simple).SelonlaspécificationJPA,chaqueentitédoitavoirunidentifiantunique–@GeneratedValue:indiquelastratégiedegénérationdelacléprimaire(séquenceOracle...)–@Column(optionnel):déterminelenomdelacolonnes'iln'estpaslemêmequel'attributdelaclasse.
JPA3/définirlesattributsderelation
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–indiquerlamultiplicité(1-1,1-N,N-N...)–indiquersilarelationestuni-directionnelleoubi-directionnellesuivantlesparcourspossiblesdanslesdonnées–définirsinécessaireunetabledejointure–listerla(les)clé(s)étrangère(s)représentantlacibledesattributsderelation.
JPA3/définirlesattributsderelationAvecJPA,grâceauxannotations:
@EntitypublicclassCatalogueimplementsSerializable{...@OneToMany(mappedBy="catalogue")//navigationCatalogue->collectiond'ArticlesprivateList<Article>articles;...}
@EntitypublicclassArticleimplementsSerializable{...@ManyToOne//navigationArticle->Catalogue@JoinColumn(name="idcat")//inutilesilacléétrangèredelatablealemêmenomquel'attributprotectedCataloguecatalogue;...}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–@OneToMany(1-N)–@ManyToOne(N-1)–@OneToOne(1-1)–@ManyToMany(N-N)
JPA3/définirlesattributsderelation:paramétrageCesannotationspeuventêtrecomplétéesdeparamètresimportants:
@EntitypublicclassCatalogueimplementsSerializable{...@OneToMany(mappedBy="catalogue",fetch=FetchType.LAZY,cascade=CascadeType.ALL,orphanRemoval=true)privateList<Article>articles;...}
@EntitypublicclassArticleimplementsSerializable{...@ManyToOne(fetch=FetchType.EAGER)@JoinColumn(name="idcat")protectedCataloguecatalogue;...}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–cascade:propagationdesopérations(persist,delete...)encascadeauxentitésenrelation(pasdisponiblepour@ManyToOne)–orphanRemoval:uneentitépeut-elleexistersanspère?(disponibleseulementpour@OneToOneet@OneToMany)–fetch:chargementdelarelationàlademande(lazyloading,choixpardéfautpourlesrelationsXxxToMany)ouimmédiate(eagerloading,choixpardéfautpourlesrelationsXxsToOne).
JPA3/définirlesattributsderelation:conseilspourlesrelationsbidirectionnellesEncréationet/oumodification,Hibernate:
Leproblèmepeutsurvenirsurlesrelationsbidirectionnellesnotamment.Silagrapped'objetsenrelationestcréée/modifiéeetpersistée:
Pourévitercela:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–sechargedelacohérencedesdonnéesdanslabasededonnées–laisseàlachargedel'applicationlemaintiendelacohérenceenmémoiredesobjetsJava.
–en1fois,aucunproblèmedecohérence–enplusieursétapes,desincohérencespeuventapparaîtrecôtéJava.
–créerdesméthodes(addXxx(),removeXxx())surlaclassepersistantepropriétairedelarelation–ellessechargerontdemaintenirlacohérence(niveauJava)delarelationdansles2sens.
JPA3/définirlesattributsderelation:conseilspourlesrelationsbidirectionnelles
Exemple:relationbidirectionnelleentreunCatalogueetsesArticles:
@EntitypublicclassCatalogue{
@OneToMany(mappedBy="catalogue")privateList<Article>articles=newArrayList<Article>();
publicvoidaddArticle(Articlearticle){articles.add(article);//ajouterunArticleàlalisteci-dessusarticle.setCatalogue(this);//ETrenseigneràquelCatalogueappartientcetArticle}
publicvoidremoveArticle(Articlearticle){article.setCatalogue(null);//supprimerlelienArticle->Cataloguefor(inti=0;i<articles.size();i++){if(articles.get(i).getId()==article.getId()){articles.remove(i);//supprimerl'Article}}}...}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
JPAsynchronisationaveclaBDDLasynchronisationenbasededonnéess'effectueàl'initiative:
Leparamétragedelasynchronisationestpossible:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–dumoteurdepersistanceavantl'exécutiond'unerequêtepourassurerlacohérencedesdonnées–dumoteurdepersistanceaumomentducommit–del'applicationvialaméthodeflush().
–FlushModeType.AUTO:commeindiquéci-dessus–FlushModeType.COMMIT:seulementlorsducommitouduflush.
JPAconcurrenced'accèsVerrouillagepessimiste:
Verrouillageoptimiste:
LaspécificationJPAsepositionneexplicitementenfaveurd'unverrouillageoptimistepourobtenirdemeilleuresperformances.
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–ladonnéeestverrouillée(miseàjourimpossible)tantquecelle-ciestutilisée(enlecture,enécriture...)parunclient.Touslesautresutilisateursnepeuventquelalire.
–ladonnéen'estverrouilléequelorsdesamodificationeffectiveenbasededonnées(trèsrapide)–avanttoutemodification:
-lemoteurdepersistancecomparelesversionsdeladonnéeentrel'entitépersistante(objetJava)etl'enregistrementenbasededonnées-sileurversionestégale:miseàjoureffectuéeetlaversiondeladonnéeestincrémentéedes2côtés-sinon:ladonnéeaétémodifiéeentresonaccès(salecture)etsatentativedemodification.UneOptimisticLockExceptionestautomatiquementlevée.
JPAconcurrenced'accèsPourmettreenplaceunverrouillageoptimiste:
@EntitypublicclassCatalogueimplementsSerializable{...@VersionprivateIntegerversion;...}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–danslatabledelabasededonnées:avoirunecolonneversion–dansl'entitépersistante:avoirunattributversion(doncmappésurlacolonneversion)annoté@Version,uniquementmanipuléparlemoteurdepersistance,quisechargedelacohérencedesdonnées:
JPAvalidationdesdonnéesBeanValidation(JSR303)apourobjectifdestandardiserladéclarationetlavérificationdescontraintessurunmodèleJava(avantl'appeldelaBDD),viadesannotations:@NotNull,@Null,@NotEmpty,@Size,@Min,@Max,@Pattern...
Encasd'échecdevalidation:
@NotEmpty(message="NotEmpty.catalogue.nom")//@NotEmpty=@NotNull+@NotBlankprivateStringnom;
Pouractiverlavalidationautomatiquedescontraintessurleschamps,ilfautajoutercettedépendancedanslefichierpom.xml:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–uneConstraintViolationExceptionestlevée.LatransactionestmarquéerollbackOnly(ie:devantêtreannulée).–ilestpossiblederenseignerunecléd'unfichier.propertiesafind'associerlemessageadéquat(etinternationalisé):
JPAgestiondescachesIlexiste3typesdecaches:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–cachedeniveau1:-lesentitésutiliséessontmisesencachepourladuréed'unetransaction-siellessontmodifiéesplusieursfoislorsdelamêmetransaction,unseulcommitenbaseserafaitàlafindelatransaction
–cachedeniveau2:-partagépartouteslestransactions-àutiliserpourlesdonnéesderéférencequin'évoluentpas(pasdesynchronisationàfaire)-annoterlesclasseséligiblesavecjavax.persistence.@Cacheable(true)
–cachederequêtes:-utilepourdesrequêtestrèsconsommatrices/volumineusesetsouventutilisées-miseencachedelarequête(+sesparamètres)etdesiddesentitésretournées,mais:-unaccèsàlabaseserafaitpouraccéderauxentités-àutiliserconjointementaveclecachedeniveau2,pourstockerlesentitésau-delàd'unetransaction.
JPAlangagesderequêtesIlestpossibled'écriredesrequêtesenplusieurslangages:
//récupérerlepaysetsacapitaleSelectcountryFromCountrycountryJOINFETCHcountry.capital;
List<Market>markets=query.selectFrom(market).where(market.name.eq("Entreprise")).uniqueResult(market);
CriteriaBuildercb=em.getCriteriaBuilder();CriteriaQuery<Country>cq=cb.createQuery(Country.class);Root<Country>country=cq.from(Country.class);cq.select(country);TypedQuery<Country>tq=em.createQuery(cq);List<Country>allCountries=tq.getResultList();
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–JPQL(JavaPersistenceQueryLanguage):
–QueryDSL(DomainSpecificLanguage):
–APICriteria:
SpringDataSpringDataestcomposédenombreuxmodulesspécialiséspartechnologie:
SpringDataoffreuneapprocheDomainDrivenDesign(DDD)del'accèsauxdonnées,ens'appuyantsurledesignpatternRepository.
DomainDrivenDesign(DDD):approchepartantd'unemodélisationdudomainemétier:
PatternRepository:mappingentrelesdonnéesdumodèlemétieretlesdonnéesstockées,selonlestechnologiessous-jacentes(BDDrelationnelles...).
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–SpringDataJPA:interactionsaveclesBDDrelationnellesens'appuyantsurHibernate–SpringDataREST–SpringDataMongoDB–SpringDataKeyValue–...
–définitiondeconceptsmétierspartagéspartouslesacteursduprojet:développeurs,fonctionnels...–précisionssurlecomportementetlesrèglesd'interactionsentrecesconcepts–donneraucodeuneconceptionorientéemétier.
SpringDatarepositoriesSpringDataoffredesrepositoriesgénériquesetd'autresspécialisésenfonctiondelasourcededonnéessous-jacente(JPA,MongoDB...).
Ainsi,SpringDatafournituneinterfacegénériqueRepository<T,ID>,permettantdespécifier:
L'interfacegénériqueCrudRepository<T,ID>:
L'interfacegénériquePagingAndSortingRepository<T,ID>:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–letyped'entité(T)géréparcerepository–letypedel'identifiantdecetteentité(ID).
–étendRepository<T,ID>–définitlessignaturesdesméthodesCRUD(leurimplémentationseraautomatiquementgénéréeparleframework).
–étendCrudRepository<T,ID>–ajoutedesméthodesdepaginationetdetri.
SpringDatarepositoriesL'interfacegénériqueQueryByExampleExecutor<T>:
L'interfaceSpringDataJPAJpaRepository<T,ID>:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–ajoutedesméthodesderechercheàpartird'unExample<T>passéenparamètre–Example<T>estuneinstancedelaclassed'entitéquisertdepatternderecherche.
–étendPagingAndSortingRepository<T,ID>–étendQueryByExampleExecutor<T>–ajoutedesméthodesliéesauxbatchsoudeflushdesdonnéesenBDD.
miseenoeuvreconfigurationMavenDanslefichierpom.xml,ilfautd'abordavoirladépendanceverslestarterspring-boot-starter-data-jpa:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
Cettedépendancepermetl'utilisationdeslibrairiesliéesaudéveloppementJPA(notammentHibernateavecdespropriétésconfiguréespardéfaut).
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
miseenoeuvreutilisationd'unebasededonnéesmémoireLeplussimplepourdébuterundéveloppementnécessitantl'usaged'unebasededonnées,estd'utiliserunebasededonnéesmémoire.Celaévited'installerunevraiebasededonnées.
Elleserachargéeenmémoireaudémarragedel'application,etseradétruiteàl'arrêtdecelle-ci.
Pardéfaut,SpringBootpeutauto-configurerunebasededonnéesH2,HSQLouDerby,dumomentqueladépendanceMavenestprésente.
LadépendanceMavensuivantepermetd'utiliserlabasemémoireH2:
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency>
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
miseenoeuvreutilisationd'unebasededonnéespersistanteParlasuite(enintégrationcontinue,enproduction...),ilfautbiensûrs'appuyersurunebasededonnéespersistante(MariaDB,MySQL,PostgreSQL,Oracle...).
Préciserdanslepom.xmlqueltypedebaseserautilisée(ici,MariaDB):
<dependency><groupId>org.mariadb.jdbc</groupId><artifactId>mariadb-java-client</artifactId><scope>runtime</scope></dependency>
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
miseenoeuvreutilisationd'unebasededonnéespersistanteEnsuite,enconfigurant(aumoins)l'URLdelabasededonnéesdanslefichierapplication.properties/yml,SpringBootconsidèrequ'ilnedoitpasutiliserunebasededonnéesmémoire,maislabasespécifiée:
spring.datasource.url=jdbc:mysql://${TODO_BACK_DB_SERVICE_HOST}:3306/${MY_DATABASE}?autoReconnect=true&useSSL=falsespring.datasource.userName=${MY_USER}spring.datasource.password=${MY_PASSWORD}
spring.datasource.driver-class-name=org.mariadb.jdbc.Driverspring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB10Dialect
Engénéral,ledriveràutiliserestfacultatif,carSpringBootsauraledéduireàpartirdel'URL.
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
miseenoeuvrecréationd'unRepositorystandardPouraccéderauxdonnées,ilsuffitdecréeruneinterfaceannotée@Repository,quiétendl'undesprécédentsrepositories(JpaRepository<T,ID>...).
Ainsi,onpeut:
findByXxx...(Xxxétantlenomd'unattributdelaclassepersistante)
And,Or,Like,Containing,StartingWith,Exists,OrderBy,GreaterThan,Between,After...
Remarque:l'utilisationd'Example<T>estuneautrefaçond'accéderauxdonnéessansdevoirajouterdenouvellesméthodes.Maisilfautquandmêmecréersonrepository.
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–bénéficierautomatiquementdesméthodeshéritées(ex:CRUD)–ajouterdenouvellessignaturesdeméthodesrespectantunnommageprécis:
–utilisercertainsmots-clésdanscesnomsdeméthodes:
–ajouterdesquerymethodsannotées@Query,enprécisantlarequêteJPQLàexécuter:
miseenoeuvrecréationd'unRepositorystandard
Exempled'unRepositorydegestiondeclassepersistantePerson:
@RepositorypublicinterfacePersonRepositoryextendsJpaRepository<Person,Long>{//JpaRepository<Person,Long>fournitlesméthodesCRUD.//Lesméthodessuivantesn'ontpasbesoind'implémentation.
//SELECTpFROMPersonpwherep.lastname='lastname'List<Person>findByLastname(Stringlastname);
//SELECTpFROMPersonpwherep.lastname='lastname'orderby...List<Person>findByLastname(Stringlastname,Sortsort);
//SELECTpFROMPersonpwherep.lastname='lastname'andp.firstname='firstname'List<Person>findByLastnameAndFirstname(Stringlastname,Stringfirstname);
//SELECTpFROMPersonpwherep.lastnameLIKE'lastname'List<Person>findByLastnameContaining(Stringlastname);
...
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
miseenoeuvrecréationd'unRepositorystandard...
//SELECTpFromPersonpwherep.address.zipcode='zipcode'List<Person>findByAddress_ZipCode(Stringzipcode);
//querymethod:définitionduJPQLàexécuterparSpringData@Query("SELECTCOUNT(p)FROMPersonpwherep.lastnameLIKECONCAT('%',CONCAT(?1,'%'))")longcountPersonWithLastnameContaining(Stringlastname);
//querymethodavecdesparamètresnommés@Query("SELECTpFROMPersonpwherep.lastname=:lastname)")List<Person>findByLastname(@Param("lastname")Stringlastname);
}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
miseenoeuvrecréationd'unRepositoryspécifiqueCertainscasnesontpasprévusparSpringData(recherchemulti-critères...),etlestechniquesvuesprécédemmentnesuffisentpas.
Ilesttoujourspossiblededéfinirsespropresméthodesspécifiques,qu'ilfautimplémenter.
Pourqueletouts'articulebiendansSpringData,ilfautalorscréer:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–uneinterfacespécifiquequilistelessignaturescustomisées–unRepositorystandard(avecousansméthodesauto-implémentées)quiétend:
-unrepositorystandardSpringData(JpaRepository<T,ID>...)-l'interfacespécifique
–uneclassed'implémentationpourl'interfacespécifique:-annotée@Repository-quiapournom:<nomInterfaceRepositoryStandard>Impl.java.
miseenoeuvrecréationd'unRepositoryspécifique//interfacespécifiquepublicinterfacePersonRepositoryCustom{//signaturesdeméthodesspécifiques...}
//repositorystandardpublicinterfacePersonRepositoryextendsJpaRepository<Person,Long>,PersonRepositoryCustom{
//méthodesrespectantlespatternsattendus,ouquerymethods......
}
//classed'implémentationnommée<repositoryStandard>Impl@RepositorypublicclassPersonRepositoryImplimplementsPersonRepositoryCustom{//implementationdesméthodesspécifiques...}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
miseenoeuvreutilisationdesExample<T>Unproblèmepeutsurveniraveclaméthodeprécédente,lorsquel'entitépersistanteenquestioncontientplusieursattributs,etqu'onaimeraitpouvoirfairedesrecherchessurunouplusieursattibutscombinés.
Lenombredesignaturesàajouterpeutvitedevenirgrandetfastidieuxàénumérer.
UnealternativepuissanteestdepasserparlesExample<T>(grâceàl'interfaceQueryByExampleExecutor<T>).
Danslacouchemétier,ilfaut:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–créer/récupéreruneinstancedel'entitépersistante,dontcertainsattributssontrenseignés(critèresderecherche)–ajouterdesrègles(ExampleMatcher)surlarecherche(containing,starting...)–appelerl'unedesméthodesdenotrerepositoryquiaccepteunExample<T>enparamètre.
miseenoeuvreutilisationdesExample<T>
Exemple:imaginonsqu'unutilisateur,viaunformulairederecherche,asaisilescritèresderecherchesuivantspourunePerson:
//Person(id,firstName,lastName,age,address)Personperson=newPerson(null,"Mickael",null,18,null);
VoicicomentutilisercetexempledePerson:
publicList<Person>findAllPersonsByExample(Personperson){
//créationdesrèglesderechercheExampleMatchermatcher=ExampleMatcher.matching().withIgnorePaths("id","lastname","address").withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
//créationdel'Exampleaveclescritères+lesrèglesExample<Person>exampleOfPerson=Example.of(person,matcher);
returnrepoPerson.findAll(exampleOfPerson);}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
testsSpringDatatesterunitairementlapersistanceIlfauttestersilapersistancedesdonnéesfonctionne,afinquelesautrescouchesdel'applicationpuissents'apuyerdessusavecconfiance.Ilfautvaliderque:
EnplusdeJUnit,SpringBootapportel'annotation@DataJpaTestpourfaciliterletravaildeconfiguration:
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–lesannotationssurlesentitéspersistantessontcorrectementutilisées–lacohérencedesdonnéesestassurée(notammentlesrelationsentreobjetspersistants)–lerepositoryfonctionnepourleCRUDetlesméthodescustomisées.
–auto-configurationd'Hibernateetd'unebasemémoirepourlestests–recherchedesentitésJPA(anciennement@EntityScan)–activationdeslogsSQL–fournitured'unTestEntityManageroffrantdesméthodessouventutiliséespourlestests(persistAndGetId(),persistFlushFind()...).
testsSpringDatatesterunitairementlapersistance@DataJpaTestpublicclassToDoListRepositoryTest{
@InjectprivateTestEntityManagerentityManager;
@InjectprivateToDoListRepositorytdlRepo;
@TestpublicvoidsaveToDoList_OK(){longnb=tdlRepo.count();ToDoListtdl=newToDoList("vacances","12/03/2018",false,null);tdl=tdlRepo.saveAndFlush(tdl);assertNotNull(tdl.getId());assertEquals(tdlRepo.count(),nb+1);}...}
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
documentation
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)
–SiteofficielSpring–DocumentationSpringData–DocumentationSpringDataJPA–DocumentationHibernate–DocumentationJPA–JavadocJPA–SiteSpringFrameworkGURU:tutoriel–SiteBaeldung:denombreuxtutoriaux
annexeparamétragedanslefichierpersistence.xml<?xmlversion="1.0"encoding="UTF-8"?><persistencexmlns="http://java.sun.com/xml/ns/persistence"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/persistencehttp://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"version="2.0">
<persistence-unitname="myPU"transaction-type="RESOURCE_LOCAL"><non-jta-data-source>jdbc/formationDataSource</non-jta-data-source><class>com.orange.formation.model.persistent.Catalogue</class><exclude-unlisted-classes/>...<properties><!--replacewithcorrectdatabasedialect--><propertyname="hibernate.dialect"value="org.hibernate.dialect.PostgreSQLDialect"/><!--developmentconfig-switchtofalseinproduction--><propertyname="hibernate.show_sql"value="true"/>...</properties></persistence-unit></persistence>
deSpringàSpringBoot-lapersistancedesdonnéesavecSpringData(Orangeinternal)