====== 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