====== Spring - pierwsze kroki ======
===== Wygeneruj projekt =====
Wygeneruj projekt wybierając w InteliJ opcję Spring Initializer i zaznaczając:
* maven
* Developer Tools -> Lombok
* Web -> Spring Web
* SQL -> Spring Data JPA i Postgresql Driver
===== Lokalna baza danych =====
*Utwórz rolę (użytkownika) ''spring_demo_user'' z hasłem ''spring_demo_user''; nadaj mu uprawnienia do logowania
*Utwórz lokalną baze danych PostgreSQL o nazwie ''spring_demo'' i ustaw jako właściciela ''spring_demo_user''
===== application.properties =====
Plik znajduje się w katalogu resources. Wpisz tam parametry połączenia z bazą danych
spring.sql.init.mode=always
spring.datasource.initialize=true
spring.datasource.url=jdbc:postgresql://localhost:5432/spring_demo
spring.datasource.username=spring_demo_user
spring.datasource.password=spring_demo_user
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.hibernate.show-sql=true
spring.jpa.database=POSTGRESQL
===== model =====
Utwórz pakiet ''model'' i dodaj do niego klasę ''AdminUnit''
package com.example.spring_demo.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "admin_units")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminUnit {
@Id
// @GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int adminLevel;
double area;
double population;
Long parentId=-1L;
// lub
// AdminUnit parent;
}
Czasem dla H2 '' @GeneratedValue(strategy = GenerationType.IDENTITY)'' nie działa, wtedy trzeba generować jawnie.
===== repository =====
Utwórz pakiet repository i interfejs:
package com.example.spring_demo.repository;
import com.example.spring_demo.model.AdminUnit;
import org.springframework.data.domain.Example;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface AdminUnitRepository extends JpaRepository {
}
Ten (minimalistyczny) interfejs zapewnia wszystkie operacje CRUD (Create, Read, Update, Delete) oraz sortowanie i paginację.
===== controller =====
Dodaj klasę ''AdminUnitController''. Zawiera definicje wszystkich metod wyeksportowanych przez REST API. Korzysta z AdminUnitRepository (wszczepiona zależność).
package com.example.spring_demo.controller;
import com.example.spring_demo.model.AdminUnit;
import com.example.spring_demo.repository.AdminUnitRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api")
public class AdminUnitController {
@Autowired
private AdminUnitRepository adminUnitRepository;
@GetMapping("/hello")
public String sayHello(@RequestParam(value = "myName", defaultValue = "World") String name) {
return String.format("Hello %s!", name);
}
@GetMapping(value="/units", produces="application/json")
public List getAllAdminUnits() {
return this.adminUnitRepository.findAll();
}
@PutMapping("/units")
@ResponseBody
public void update(@RequestBody AdminUnit newAdminUnit) {
adminUnitRepository.save(newAdminUnit);
}
@DeleteMapping("/units/{id}")
public ResponseEntity delete(@PathVariable("id") long id){
if(adminUnitRepository.getReferenceById(id)==null){
return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND);
}
adminUnitRepository.deleteById(id);
return new ResponseEntity<>("OK", HttpStatus.OK);
}
}
Jak widac wszystkie punkty końcowe będą miały adres ''localhost:8080/api/units...''
===== Kod wykonywany podczas uruchamiania =====
Biblioteka Hibernate została skonfigurowana tak, aby przy każdym uruchomieniu usuwać i tworzyć od nowa bazę.
Aby wypełnić ją rekordami dodaj (w głownym katalogu) klasę odpowiedzialną za wypełnienie bazy danych rekordami.
package com.example.spring_demo;
import com.example.spring_demo.model.AdminUnit;
import com.example.spring_demo.repository.AdminUnitRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class LoadDataOnStartUp {
@Autowired
private AdminUnitRepository adminUnitRepository;
@EventListener(ApplicationReadyEvent.class)
public void loadData()
{
// do something
AdminUnit[] units = {
new AdminUnit(1L,"Kraków",7,300,800000,-1L),
new AdminUnit(2L,"Warszawa",7,500,1500000,-1L)
};
// adminUnitRepository.saveAll(new ArrayList<>(Arrays.asList(units)));
for(var u:units)adminUnitRepository.save(u);
}
}
Opcjonalnie, kod można wykonać tylko raz, aby zasilić baze danych, a potem zakomentować. Należy wtedy przełączyć tryb generacji bazy danych Hibernate w ''application.properties'' na ''update''.
===== Dodaj wsparcie dla Swaggera =====
Dodaj zależność do ''pom.xml''
org.springdoc
springdoc-openapi-starter-webmvc-ui
2.2.0
Dzięki temu pod adresem [[http://localhost:8080/swagger-ui/index.html]] można będzie przetestować wszystkie metody REST API
{{ :pz1:zrzut_ekranu_2023-10-29_182352.png?direct&800 |}}
===== Wyszukiwanie według kryteriów =====
W ''AdminUnitRepository'' dodaj metodę interfejsu, a w adnotacji Query kwerendę w języku JPQL:
@Query("SELECT u FROM AdminUnit u WHERE u.name LIKE %:partialName% AND (:aLevel IS NULL OR u.adminLevel = :aLevel)")
List getAdminUnitsByNameContainingAndAdminLevel(@Param("partialName") String pname, @Param("aLevel") Integer al);
Dodaj w klasie ''AdminUnitController'' kolejną metodę GET:
@GetMapping(value="/units/search", produces="application/json")
public List getAdminUnitsByNameAndAdminLevel(
@RequestParam(value = "partialname", defaultValue = "") String name,
@RequestParam(value = "alevel", required=false) Integer adminLevel){
return this.adminUnitRepository.getAdminUnitsByNameContainingAndAdminLevel(name,adminLevel);
}
W oknie Swagera można przetestować wyszukiwanie, np:
*jednostki zawierające 'r' w nazwie bez podania poziomu
*jednostki zawierające 's' w nazwie poziomu 7