Напредни апликативен развој (Трансакции, Pooling)
Трансакции
Зошто ни се потребни трансакции? При креирање на корисници, како што се благајник, родител, наставник, записот мора да биде запишан прво во наткласата Корисник а потоа во поткласите. Овој процес мора да биде конзистентен и да се изврши без прекини. Затоа при креирање на овие корисници ја користиме анотацијата @Transactional. Оваа анотација се користи за да му се каже на Spring Boot автоматски да се грижи за COMMIT и ROLLBACK кој може да се случи доколку настане некоја грешка.
Креирање на благајник
@Value("${wallet.receiver}")
private String metamusk_adresa;
@Override
@Transactional
public BlagajnikResponseDTO createBlagajnik(BlagajnikRequestDTO blagajnikRequestDTO) {
System.out.println("Пристигнат DTO: " + blagajnikRequestDTO);
if(blagajnikRequestDTO.getE_posta() == null || blagajnikRequestDTO.getE_posta().isEmpty() ||
blagajnikRequestDTO.getLozinka() == null || blagajnikRequestDTO.getLozinka().isEmpty() ||
blagajnikRequestDTO.getIme() == null || blagajnikRequestDTO.getIme().isEmpty() ||
blagajnikRequestDTO.getPrezime() == null || blagajnikRequestDTO.getPrezime().isEmpty() ||
blagajnikRequestDTO.getPol() == null || blagajnikRequestDTO.getPol().isEmpty() ||
blagajnikRequestDTO.getAdresa() == null || blagajnikRequestDTO.getAdresa().isEmpty() ||
blagajnikRequestDTO.getKreiranOdId() == null
){
throw new IllegalArgumentException("invalid blagajnik data");
}
String encodedPass = passwordEncoder.encode( blagajnikRequestDTO.getLozinka());
this.direktorAdminService.getDirektorAdminById(blagajnikRequestDTO.getKreiranOdId());
KorisnikRequestDTO korisnikDto = new KorisnikRequestDTO(
blagajnikRequestDTO.getE_posta(),
encodedPass,
blagajnikRequestDTO.getIme(),
blagajnikRequestDTO.getPrezime(),
blagajnikRequestDTO.getPol(),
blagajnikRequestDTO.getAdresa(),
blagajnikRequestDTO.getKreiranOdId()
);
KorisnikResponseDTO korisnik = korisnikService.createKorisnik(korisnikDto);
UUID korisnikId = korisnik.getId();
this.blagajnikRepository.createBlagajnik(korisnikId,metamusk_adresa, true);
BlagajnikResponseDTO blagajnikResponseDTO = this.getBlagajnikById(korisnikId);
return blagajnikResponseDTO;
}
Креирање на наставник
@Override
@Transactional
public NastavnikResponseDTO createNastavnik(NastavnikRequestDTO dto) {
if (dto.getE_posta() == null || dto.getE_posta().isEmpty() ||
dto.getLozinka() == null || dto.getLozinka().isEmpty() ||
dto.getIme() == null || dto.getIme().isEmpty() ||
dto.getPrezime() == null || dto.getPrezime().isEmpty() ||
dto.getPol() == null || dto.getPol().isEmpty() ||
dto.getAdresa() == null || dto.getAdresa().isEmpty() ||
dto.getKreiranOdId() == null) {
throw new IllegalArgumentException("Invalid nastavnik data");
}
String encodedPass = passwordEncoder.encode( dto.getLozinka());
direktorAdminService.getDirektorAdminById(dto.getKreiranOdId());
KorisnikRequestDTO korisnikDto = new KorisnikRequestDTO(
dto.getE_posta(),
encodedPass,
dto.getIme(),
dto.getPrezime(),
dto.getPol(),
dto.getAdresa(),
dto.getKreiranOdId()
);
KorisnikResponseDTO korisnik = korisnikService.createKorisnik(korisnikDto);
UUID korisnikId = korisnik.getId();
this.nastavnikRepository.createNastavnik(korisnikId, true);
NastavnikResponseDTO nastavnikResponseDTO = this.getNastavnikById(korisnikId);
return nastavnikResponseDTO;
}
Креирање на родител
@Override
@Transactional
public RoditelResponseDTO createRoditel(RoditelRequestDTO roditelRequestDTO) {
String encodedPass = passwordEncoder.encode( roditelRequestDTO.getLozinka());
this.direktorAdminService.getDirektorAdminById(roditelRequestDTO.getKreiranOdId());
KorisnikRequestDTO korisnikDto = new KorisnikRequestDTO(
roditelRequestDTO.getE_posta(),
encodedPass,
roditelRequestDTO.getIme(),
roditelRequestDTO.getPrezime(),
roditelRequestDTO.getPol(),
roditelRequestDTO.getAdresa(),
roditelRequestDTO.getKreiranOdId()
);
KorisnikResponseDTO korisnik = korisnikService.createKorisnik(korisnikDto);
UUID korisnikId = korisnik.getId();
String metamusk = "SET LATER";
this.roditelRepository.createRoditel(korisnikId,true,metamusk);
RoditelResponseDTO roditelResponseDTO = this.roditelRepository.findRoditelById(korisnikId);
return roditelResponseDTO;
}
Истото го имаме и кај оценките, односно според дијаграмот наткласа за усна, писмена и крајна оценка е табелата Ocenka. Затоа, за да немаме случај како што е оценката постои во табелата Ocenka, но не постои во Usna_Ocenka, Pismena_Ocenka, Krajna_Ocenka, треба да користиме трансакција.
Внесување на усна оценка
@Override
@Transactional
public void enterUsnaOcenkaForStudent(UUID ucenikId, FullUsnaOcenkaRequestDTO fullUsnaOcenkaRequestDTO) {
if (ucenikId == null) {
throw new IllegalArgumentException("ucenikId is null");
}
if (fullUsnaOcenkaRequestDTO == null) {
throw new IllegalArgumentException("Request is null");
}
if (fullUsnaOcenkaRequestDTO.getOcenka() == null || fullUsnaOcenkaRequestDTO.getOcenka() < 1 || fullUsnaOcenkaRequestDTO.getOcenka() > 5) {
throw new IllegalArgumentException("Invalid ocenka");
}
if (fullUsnaOcenkaRequestDTO.getOsvoeni_poeni() > fullUsnaOcenkaRequestDTO.getMax_poeni()) {
throw new IllegalArgumentException("Invalid points");
}
UcenikResponseDTO ucenik = this.ucenikService.getUcenikById(ucenikId);
if (ucenik == null) {
throw new IllegalArgumentException("Ucenik not found");
}
this.ocenkaRepository.createOcenka(fullUsnaOcenkaRequestDTO.getOcenka(), fullUsnaOcenkaRequestDTO.getDatum(), fullUsnaOcenkaRequestDTO.getZabeleska(), fullUsnaOcenkaRequestDTO.getVnesena_Od(), fullUsnaOcenkaRequestDTO.getDobienaZaUcenikot_Id(), fullUsnaOcenkaRequestDTO.getEZaPredmetot_Id(), fullUsnaOcenkaRequestDTO.getZaAktivnosta_id());
//insert in usna ocenka
UUID insertedId = this.ocenkaRepository.getOcenkaByUcenikAndAktivnost(fullUsnaOcenkaRequestDTO.getDobienaZaUcenikot_Id(), fullUsnaOcenkaRequestDTO.getZaAktivnosta_id());
this.usnaOcenkaRepository.insertUsnaOcenka(insertedId, fullUsnaOcenkaRequestDTO.getTema(), fullUsnaOcenkaRequestDTO.getOsvoeni_poeni(), fullUsnaOcenkaRequestDTO.getMax_poeni(), String.valueOf(fullUsnaOcenkaRequestDTO.getVidUsnoIsprasuvanje()));
}
Внесување на писмена оценка
@Override
@Transactional
public void enterPismenaOcenkaForStudent(UUID id, FullPismenaOcenkaRequestDTO fullPismenaOcenkaRequestDTO) {
if (id == null) {
throw new IllegalArgumentException("ucenikId is null");
}
if (fullPismenaOcenkaRequestDTO == null) {
throw new IllegalArgumentException("Request is null");
}
if (fullPismenaOcenkaRequestDTO.getOcenka() == null || fullPismenaOcenkaRequestDTO.getOcenka() < 1 || fullPismenaOcenkaRequestDTO.getOcenka() > 5) {
throw new IllegalArgumentException("Invalid ocenka");
}
if (fullPismenaOcenkaRequestDTO.getOsvoeni_poeni() > fullPismenaOcenkaRequestDTO.getMax_poeni()) {
throw new IllegalArgumentException("Invalid points");
}
UcenikResponseDTO ucenik = this.ucenikService.getUcenikById(id);
if (ucenik == null) {
throw new IllegalArgumentException("Ucenik not found");
}
this.ocenkaRepository.createOcenka(fullPismenaOcenkaRequestDTO.getOcenka(), fullPismenaOcenkaRequestDTO.getDatum(), fullPismenaOcenkaRequestDTO.getZabeleska(), fullPismenaOcenkaRequestDTO.getVnesena_Od(), fullPismenaOcenkaRequestDTO.getDobienaZaUcenikot_Id(), fullPismenaOcenkaRequestDTO.getEZaPredmetot_Id(), fullPismenaOcenkaRequestDTO.getZaAktivnosta_id());
//insert in pismenaocenka
UUID insertedId = this.ocenkaRepository.getOcenkaByUcenikAndAktivnost(fullPismenaOcenkaRequestDTO.getDobienaZaUcenikot_Id(), fullPismenaOcenkaRequestDTO.getZaAktivnosta_id());
this.pismenaOcenkaRepository.insertPismenaOcenka(insertedId, fullPismenaOcenkaRequestDTO.getTema(), fullPismenaOcenkaRequestDTO.getOsvoeni_poeni(), fullPismenaOcenkaRequestDTO.getMax_poeni(), String.valueOf(fullPismenaOcenkaRequestDTO.getVid()));
}
Внесување на крајна оценка
@Override
@Transactional
public void enterKrajnaOcenkaForStudent(UUID id, FullKrajnaOcenkaRequestDTO fullKrajnaOcenkaRequestDTO) {
if (id == null) {
throw new IllegalArgumentException("ucenikId is null");
}
if (fullKrajnaOcenkaRequestDTO == null) {
throw new IllegalArgumentException("Request is null");
}
if (fullKrajnaOcenkaRequestDTO.getOcenka() == null || fullKrajnaOcenkaRequestDTO.getOcenka() < 1 || fullKrajnaOcenkaRequestDTO.getOcenka() > 5) {
throw new IllegalArgumentException("Invalid ocenka");
}
UcenikResponseDTO ucenik = this.ucenikService.getUcenikById(id);
if (ucenik == null) {
throw new IllegalArgumentException("Ucenik not found");
}
UUID ocenkaId = UUID.randomUUID();
this.ocenkaRepository.createOcenkaWithId(ocenkaId, fullKrajnaOcenkaRequestDTO.getOcenka(), fullKrajnaOcenkaRequestDTO.getDatum(), fullKrajnaOcenkaRequestDTO.getZabeleska(), fullKrajnaOcenkaRequestDTO.getVnesena_Od(), fullKrajnaOcenkaRequestDTO.getDobienaZaUcenikot_Id(), fullKrajnaOcenkaRequestDTO.getEZaPredmetot_Id()
);
this.krajnaOcenkaRepository.createKrajnaOcenka(ocenkaId, String.valueOf(fullKrajnaOcenkaRequestDTO.getTip()));
}
Pooling
Наместо за секое SQL Query да се отвара нова сесија до базата, апликацијата ќе користи Pooling. Односно имаме pool од конекции. Во Java Spring Boot тоа се прави со Hikari, кој доаѓа заедно со spring boot started data jpa dependency кој што се грижи за извршување на Queries.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
Потоа во application.properties имаме конфигурација:
# имаме вкупно максимум 10 конекции во pool spring.datasource.hikari.maximum-pool-size=10 #конекции кои што стојат неискористени (idle) доколку дојде request за query spring.datasource.hikari.minimum-idle=2 #максимално време на чекање за конекција од pool (30 sec) spring.datasource.hikari.connection-timeout=30000 #10 минути време за чекање за добивање на конекција spring.datasource.hikari.idle-timeout=600000 #времетраење на конекцијата spring.datasource.hikari.max-lifetime=1800000
