From e797546253002a7fb6d2bc01de72db3e2b2f02a3 Mon Sep 17 00:00:00 2001 From: Rostyslav Novak Date: Sat, 27 Sep 2025 16:10:53 +0300 Subject: [PATCH 1/5] complited assigment --- pom.xml | 59 ++++++++++++++++++- .../academy/rickandmorty/Application.java | 13 +++- .../rickandmorty/config/MapperConfig.java | 13 ++++ .../controller/CharacterController.java | 42 +++++++++++++ .../dto/external/CharacterInfoDto.java | 8 +++ .../dto/external/CharacterResponseDto.java | 10 ++++ .../dto/external/CharacterResultsDto.java | 11 ++++ .../dto/internal/CharacterDto.java | 11 ++++ .../exception/ExternalApiException.java | 6 ++ .../rickandmorty/mapper/CharacterMapper.java | 22 +++++++ .../academy/rickandmorty/model/Character.java | 36 +++++++++++ .../repository/CharacterRepository.java | 9 +++ .../service/CharacterExternalApiService.java | 56 ++++++++++++++++++ .../service/CharacterService.java | 15 +++++ .../service/CharacterServiceImpl.java | 44 ++++++++++++++ src/main/resources/application.properties | 8 ++- 16 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 src/main/java/mate/academy/rickandmorty/config/MapperConfig.java create mode 100644 src/main/java/mate/academy/rickandmorty/controller/CharacterController.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CharacterResultsDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/exception/ExternalApiException.java create mode 100644 src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java create mode 100644 src/main/java/mate/academy/rickandmorty/model/Character.java create mode 100644 src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/CharacterExternalApiService.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/CharacterService.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java diff --git a/pom.xml b/pom.xml index 0c754f19..7b85bf57 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,8 @@ jv-rick-and-morty jv-rick-and-morty + 1.6.3 + 0.2.0 17 3.1.1 @@ -32,6 +34,23 @@ test + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + org.projectlombok + lombok + 1.18.30 + + org.springframework.boot spring-boot-starter-data-jpa @@ -41,10 +60,48 @@ com.h2database h2 + + org.springframework.boot + spring-boot-starter-web + + + mysql + mysql-connector-java + 8.0.33 + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.2.0 + - + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/java/mate/academy/rickandmorty/Application.java b/src/main/java/mate/academy/rickandmorty/Application.java index cdea84fc..ca5761f8 100644 --- a/src/main/java/mate/academy/rickandmorty/Application.java +++ b/src/main/java/mate/academy/rickandmorty/Application.java @@ -1,12 +1,23 @@ package mate.academy.rickandmorty; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.service.CharacterExternalApiService; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class Application { +@RequiredArgsConstructor +public class Application implements CommandLineRunner { + private final CharacterExternalApiService externalApiService; public static void main(String[] args) { SpringApplication.run(Application.class, args); + + } + + @Override + public void run(String... args) throws Exception { + externalApiService.fetchCharacters(); } } diff --git a/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java b/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java new file mode 100644 index 00000000..450e58db --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.config; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.NullValueCheckStrategy; + +@org.mapstruct.MapperConfig( + componentModel = "spring", + injectionStrategy = InjectionStrategy.CONSTRUCTOR, + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + implementationPackage = ".impl" +) +public class MapperConfig { +} diff --git a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java new file mode 100644 index 00000000..c483c954 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -0,0 +1,42 @@ +package mate.academy.rickandmorty.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.constraints.NotBlank; +import java.util.List; +import java.util.Random; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.model.Character; +import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/characters") +@Validated +public class CharacterController { + private final CharacterService characterService; + + @Operation(description = "The request randomly generates a wiki " + + "about one character in the universe the animated series Rick & Morty") + @GetMapping("/random") + public Character getRandomCharacter() { + long randomId = new Random().nextLong(1, 827); + return characterService.getById(randomId); + } + + @Operation(description = "The request takes a string as an argument," + + " and returns a list of all characters whose name contains the search string.") + @GetMapping + public List getAllCharactersByName( + @Parameter(example = "?name=Rick+Sanchez") + @RequestParam("name") + @NotBlank + String name) { + return characterService.findByName(name); + } +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java new file mode 100644 index 00000000..f1491582 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java @@ -0,0 +1,8 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record CharacterInfoDto( + String next) { +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java new file mode 100644 index 00000000..6fc334db --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java @@ -0,0 +1,10 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record CharacterResponseDto( + CharacterInfoDto info, + List results) { +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResultsDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResultsDto.java new file mode 100644 index 00000000..af348693 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResultsDto.java @@ -0,0 +1,11 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record CharacterResultsDto( + Long id, + String name, + String status, + String gender) { +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java new file mode 100644 index 00000000..521c623e --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java @@ -0,0 +1,11 @@ +package mate.academy.rickandmorty.dto.internal; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record CharacterDto( + Long externalId, + String name, + String status, + String gender) { +} diff --git a/src/main/java/mate/academy/rickandmorty/exception/ExternalApiException.java b/src/main/java/mate/academy/rickandmorty/exception/ExternalApiException.java new file mode 100644 index 00000000..64a7552d --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/exception/ExternalApiException.java @@ -0,0 +1,6 @@ +package mate.academy.rickandmorty.exception; + +public class ExternalApiException extends RuntimeException { + public ExternalApiException(String s, Exception e) { + } +} diff --git a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java new file mode 100644 index 00000000..f39f062f --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -0,0 +1,22 @@ +package mate.academy.rickandmorty.mapper; + +import java.util.List; +import mate.academy.rickandmorty.config.MapperConfig; +import mate.academy.rickandmorty.dto.external.CharacterResultsDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.model.Character; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(config = MapperConfig.class) +public interface CharacterMapper { + CharacterDto toDto(Character character); + + @Mapping(target = "id", ignore = true) + Character internalToModel(CharacterDto characterDto); + + @Mapping(target = "externalId", source = "id") + CharacterDto externalToDto(CharacterResultsDto characterResultsDto); + + List externalToDtoList(List externalDtos); +} diff --git a/src/main/java/mate/academy/rickandmorty/model/Character.java b/src/main/java/mate/academy/rickandmorty/model/Character.java new file mode 100644 index 00000000..93341e85 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/model/Character.java @@ -0,0 +1,36 @@ +package mate.academy.rickandmorty.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@Table(name = "characters") +@Entity +public class Character { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(nullable = false, unique = true) + @NotNull + private Long externalId; + @Column(nullable = false) + @NotBlank + private String name; + @Column(nullable = false) + @NotBlank + private String status; + @Column(nullable = false) + @NotBlank + private String gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java new file mode 100644 index 00000000..b53fd9cd --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java @@ -0,0 +1,9 @@ +package mate.academy.rickandmorty.repository; + +import java.util.List; +import mate.academy.rickandmorty.model.Character; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CharacterRepository extends JpaRepository { + List findByName(String name); +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterExternalApiService.java b/src/main/java/mate/academy/rickandmorty/service/CharacterExternalApiService.java new file mode 100644 index 00000000..e96eecd7 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterExternalApiService.java @@ -0,0 +1,56 @@ +package mate.academy.rickandmorty.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.CharacterResponseDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.exception.ExternalApiException; +import mate.academy.rickandmorty.mapper.CharacterMapper; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CharacterExternalApiService { + private static final String BASE_URL = "https://rickandmortyapi.com/api/character"; + private final ObjectMapper objectMapper; + private final CharacterMapper characterMapper; + private final CharacterService characterService; + + public void fetchCharacters() { + List characters = new ArrayList<>(); + String nextUrl = BASE_URL; + + HttpClient client = HttpClient.newHttpClient(); + while (nextUrl != null) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(nextUrl)) + .build(); + try { + HttpResponse response = client.send( + request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new IOException("API returned non-successful status code: " + + response.statusCode()); + } + CharacterResponseDto characterResponseDto = objectMapper.readValue( + response.body(), CharacterResponseDto.class); + + characters.addAll(characterMapper.externalToDtoList( + characterResponseDto.results())); + nextUrl = characterResponseDto.info().next(); + } catch (IOException | InterruptedException e) { + throw new ExternalApiException( + "An error occurred while trying to get a response from the server.", e); + } + } + characterService.saveAll(characters); + } + +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java new file mode 100644 index 00000000..1bedb4c1 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -0,0 +1,15 @@ +package mate.academy.rickandmorty.service; + +import java.util.List; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.model.Character; + +public interface CharacterService { + void save(CharacterDto characterDto); + + void saveAll(List dtos); + + Character getById(Long id); + + List findByName(String name); +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java new file mode 100644 index 00000000..8a97520c --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java @@ -0,0 +1,44 @@ +package mate.academy.rickandmorty.service; + +import jakarta.persistence.EntityNotFoundException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.mapper.CharacterMapper; +import mate.academy.rickandmorty.model.Character; +import mate.academy.rickandmorty.repository.CharacterRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CharacterServiceImpl implements CharacterService { + private final CharacterMapper mapper; + private final CharacterRepository repository; + + @Override + public void save(CharacterDto characterDto) { + Character character = mapper.internalToModel(characterDto); + repository.save(character); + } + + @Override + public void saveAll(List dtos) { + List characters = dtos.stream() + .map(mapper::internalToModel) + .toList(); + repository.saveAll(characters); + } + + @Override + public Character getById(Long id) { + return repository.findById(id).orElseThrow( + () -> new EntityNotFoundException( + "Could not find Character with id: " + id) + ); + } + + @Override + public List findByName(String name) { + return repository.findByName(name); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..6eab8d98 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ - +spring.application.name=spring-project +spring.datasource.url=jdbc:mysql://localhost:3306/rick_and_morty_db +spring.datasource.username=root +spring.datasource.password=12345678 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.jooq.sql-dialect=MYSQL +spring.jpa.hibernate.ddl-auto=create-drop From 5c06fd50ad61b611a1df4afeec6c30e763af0f34 Mon Sep 17 00:00:00 2001 From: Rostyslav Novak Date: Sat, 27 Sep 2025 19:39:55 +0300 Subject: [PATCH 2/5] plugin fix --- pom.xml | 17 +++++++++++++---- src/main/resources/application.properties | 1 - 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 7b85bf57..d495ffc6 100644 --- a/pom.xml +++ b/pom.xml @@ -102,10 +102,6 @@ - - org.springframework.boot - spring-boot-maven-plugin - org.apache.maven.plugins maven-checkstyle-plugin @@ -123,8 +119,21 @@ true true false + src/main + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6eab8d98..53f3ab89 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,3 @@ -spring.application.name=spring-project spring.datasource.url=jdbc:mysql://localhost:3306/rick_and_morty_db spring.datasource.username=root spring.datasource.password=12345678 From df5315026582b6ede50152ed0ecc13df955a3f98 Mon Sep 17 00:00:00 2001 From: Rostyslav Novak Date: Sun, 28 Sep 2025 00:25:13 +0300 Subject: [PATCH 3/5] fixed --- .../controller/CharacterController.java | 4 +-- .../exception/ExternalApiException.java | 3 +- .../rickandmorty/mapper/CharacterMapper.java | 3 ++ .../repository/CharacterRepository.java | 9 +++++- .../service/CharacterService.java | 2 ++ .../service/CharacterServiceImpl.java | 29 +++++++++++++++++-- src/main/resources/application.properties | 2 +- 7 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java index c483c954..73701d3e 100644 --- a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.constraints.NotBlank; import java.util.List; -import java.util.Random; import lombok.RequiredArgsConstructor; import mate.academy.rickandmorty.model.Character; import mate.academy.rickandmorty.service.CharacterService; @@ -25,8 +24,7 @@ public class CharacterController { + "about one character in the universe the animated series Rick & Morty") @GetMapping("/random") public Character getRandomCharacter() { - long randomId = new Random().nextLong(1, 827); - return characterService.getById(randomId); + return characterService.getRandomCharacter(); } @Operation(description = "The request takes a string as an argument," diff --git a/src/main/java/mate/academy/rickandmorty/exception/ExternalApiException.java b/src/main/java/mate/academy/rickandmorty/exception/ExternalApiException.java index 64a7552d..a0bdda77 100644 --- a/src/main/java/mate/academy/rickandmorty/exception/ExternalApiException.java +++ b/src/main/java/mate/academy/rickandmorty/exception/ExternalApiException.java @@ -1,6 +1,7 @@ package mate.academy.rickandmorty.exception; public class ExternalApiException extends RuntimeException { - public ExternalApiException(String s, Exception e) { + public ExternalApiException(String message, Throwable cause) { + super(message, cause); } } diff --git a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java index f39f062f..c1575d37 100644 --- a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -7,6 +7,7 @@ import mate.academy.rickandmorty.model.Character; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; @Mapper(config = MapperConfig.class) public interface CharacterMapper { @@ -19,4 +20,6 @@ public interface CharacterMapper { CharacterDto externalToDto(CharacterResultsDto characterResultsDto); List externalToDtoList(List externalDtos); + + void updateEntityFromDto(CharacterDto characterDto, @MappingTarget Character Character); } diff --git a/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java index b53fd9cd..d7579e53 100644 --- a/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java +++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java @@ -1,9 +1,16 @@ package mate.academy.rickandmorty.repository; import java.util.List; +import java.util.Optional; import mate.academy.rickandmorty.model.Character; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface CharacterRepository extends JpaRepository { - List findByName(String name); + List findByNameContainingIgnoreCase(String name); + + Optional findByExternalId(Long externalId); + + @Query("SELECT MAX(c.externalId) FROM Character c") + Optional findMaxExternalId(); } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java index 1bedb4c1..9506b9e1 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -12,4 +12,6 @@ public interface CharacterService { Character getById(Long id); List findByName(String name); + + Character getRandomCharacter(); } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java index 8a97520c..45c5bd3e 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java @@ -2,12 +2,15 @@ import jakarta.persistence.EntityNotFoundException; import java.util.List; +import java.util.Random; + import lombok.RequiredArgsConstructor; import mate.academy.rickandmorty.dto.internal.CharacterDto; import mate.academy.rickandmorty.mapper.CharacterMapper; import mate.academy.rickandmorty.model.Character; import mate.academy.rickandmorty.repository.CharacterRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -22,16 +25,23 @@ public void save(CharacterDto characterDto) { } @Override + @Transactional public void saveAll(List dtos) { List characters = dtos.stream() - .map(mapper::internalToModel) + .map(characterDto -> { + Character newCharacter = repository + .findByExternalId(characterDto.externalId()) + .orElse(mapper.internalToModel(characterDto)); + mapper.updateEntityFromDto(characterDto, newCharacter); + return newCharacter; + }) .toList(); repository.saveAll(characters); } @Override public Character getById(Long id) { - return repository.findById(id).orElseThrow( + return repository.findByExternalId(id).orElseThrow( () -> new EntityNotFoundException( "Could not find Character with id: " + id) ); @@ -39,6 +49,19 @@ public Character getById(Long id) { @Override public List findByName(String name) { - return repository.findByName(name); + return repository.findByNameContainingIgnoreCase(name); + } + + @Override + public Character getRandomCharacter() { + Long maxId = repository.findMaxExternalId() + .orElseThrow(() -> new EntityNotFoundException("Could not find max characters count")); + long randomId = new Random().nextLong(1, maxId + 1); + return repository.findById(randomId) + .orElseThrow( + () -> new EntityNotFoundException( + "Error occurred when trying to find Character with id: " + + randomId) + ); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 53f3ab89..2d1822bf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,4 +3,4 @@ spring.datasource.username=root spring.datasource.password=12345678 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jooq.sql-dialect=MYSQL -spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=create From a50797164ef271194da158fafd59a588f70eb93e Mon Sep 17 00:00:00 2001 From: Rostyslav Novak Date: Sun, 28 Sep 2025 00:26:31 +0300 Subject: [PATCH 4/5] checkstyle fix --- .../mate/academy/rickandmorty/mapper/CharacterMapper.java | 2 +- .../academy/rickandmorty/service/CharacterServiceImpl.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java index c1575d37..bcd250e8 100644 --- a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -21,5 +21,5 @@ public interface CharacterMapper { List externalToDtoList(List externalDtos); - void updateEntityFromDto(CharacterDto characterDto, @MappingTarget Character Character); + void updateEntityFromDto(CharacterDto characterDto, @MappingTarget Character character); } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java index 45c5bd3e..8ac56944 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java @@ -3,7 +3,6 @@ import jakarta.persistence.EntityNotFoundException; import java.util.List; import java.util.Random; - import lombok.RequiredArgsConstructor; import mate.academy.rickandmorty.dto.internal.CharacterDto; import mate.academy.rickandmorty.mapper.CharacterMapper; @@ -55,7 +54,8 @@ public List findByName(String name) { @Override public Character getRandomCharacter() { Long maxId = repository.findMaxExternalId() - .orElseThrow(() -> new EntityNotFoundException("Could not find max characters count")); + .orElseThrow(() -> + new EntityNotFoundException("Could not find max characters count")); long randomId = new Random().nextLong(1, maxId + 1); return repository.findById(randomId) .orElseThrow( From 718b5d621ee6ff29cdadfcbfac804e82834cc21c Mon Sep 17 00:00:00 2001 From: Rostyslav Novak Date: Sun, 28 Sep 2025 14:42:31 +0300 Subject: [PATCH 5/5] fix2 --- .../rickandmorty/config/MapperConfig.java | 2 +- .../controller/CharacterController.java | 6 ++-- .../dto/external/CharacterResultsDto.java | 2 +- .../dto/internal/CharacterDto.java | 3 +- .../rickandmorty/mapper/CharacterMapper.java | 10 +++++- .../academy/rickandmorty/model/Character.java | 2 +- .../repository/CharacterRepository.java | 6 ++-- .../service/CharacterService.java | 9 ++--- .../service/CharacterServiceImpl.java | 36 +++++++------------ 9 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java b/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java index 450e58db..d757f88a 100644 --- a/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java +++ b/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java @@ -7,7 +7,7 @@ componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, - implementationPackage = ".impl" + implementationPackage = "mate.academy.rickandmorty.mapper.impl" ) public class MapperConfig { } diff --git a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java index 73701d3e..a1bf72c1 100644 --- a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -5,7 +5,7 @@ import jakarta.validation.constraints.NotBlank; import java.util.List; import lombok.RequiredArgsConstructor; -import mate.academy.rickandmorty.model.Character; +import mate.academy.rickandmorty.dto.internal.CharacterDto; import mate.academy.rickandmorty.service.CharacterService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -23,14 +23,14 @@ public class CharacterController { @Operation(description = "The request randomly generates a wiki " + "about one character in the universe the animated series Rick & Morty") @GetMapping("/random") - public Character getRandomCharacter() { + public CharacterDto getRandomCharacter() { return characterService.getRandomCharacter(); } @Operation(description = "The request takes a string as an argument," + " and returns a list of all characters whose name contains the search string.") @GetMapping - public List getAllCharactersByName( + public List getAllCharactersByName( @Parameter(example = "?name=Rick+Sanchez") @RequestParam("name") @NotBlank diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResultsDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResultsDto.java index af348693..92992be3 100644 --- a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResultsDto.java +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResultsDto.java @@ -4,7 +4,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record CharacterResultsDto( - Long id, + String id, String name, String status, String gender) { diff --git a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java index 521c623e..5ea4214f 100644 --- a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java +++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java @@ -4,7 +4,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record CharacterDto( - Long externalId, + Long id, + String externalId, String name, String status, String gender) { diff --git a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java index bcd250e8..77b70507 100644 --- a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -8,6 +8,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; @Mapper(config = MapperConfig.class) public interface CharacterMapper { @@ -16,9 +17,16 @@ public interface CharacterMapper { @Mapping(target = "id", ignore = true) Character internalToModel(CharacterDto characterDto); - @Mapping(target = "externalId", source = "id") + @Mappings({ + @Mapping(target = "externalId", source = "id"), + @Mapping(target = "id", ignore = true) + }) CharacterDto externalToDto(CharacterResultsDto characterResultsDto); + @Mappings({ + @Mapping(target = "externalId", source = "id"), + @Mapping(target = "id", ignore = true) + }) List externalToDtoList(List externalDtos); void updateEntityFromDto(CharacterDto characterDto, @MappingTarget Character character); diff --git a/src/main/java/mate/academy/rickandmorty/model/Character.java b/src/main/java/mate/academy/rickandmorty/model/Character.java index 93341e85..0b68b47d 100644 --- a/src/main/java/mate/academy/rickandmorty/model/Character.java +++ b/src/main/java/mate/academy/rickandmorty/model/Character.java @@ -23,7 +23,7 @@ public class Character { private Long id; @Column(nullable = false, unique = true) @NotNull - private Long externalId; + private String externalId; @Column(nullable = false) @NotBlank private String name; diff --git a/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java index d7579e53..4db25bc0 100644 --- a/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java +++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java @@ -9,8 +9,8 @@ public interface CharacterRepository extends JpaRepository { List findByNameContainingIgnoreCase(String name); - Optional findByExternalId(Long externalId); + Optional findByExternalId(String externalId); - @Query("SELECT MAX(c.externalId) FROM Character c") - Optional findMaxExternalId(); + @Query(value = "SELECT * FROM characters ORDER BY RAND() LIMIT 1", nativeQuery = true) + Character findRandomCharacter(); } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java index 9506b9e1..f6bb9e12 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -2,16 +2,13 @@ import java.util.List; import mate.academy.rickandmorty.dto.internal.CharacterDto; -import mate.academy.rickandmorty.model.Character; public interface CharacterService { - void save(CharacterDto characterDto); - void saveAll(List dtos); - Character getById(Long id); + CharacterDto getById(Long id); - List findByName(String name); + List findByName(String name); - Character getRandomCharacter(); + CharacterDto getRandomCharacter(); } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java index 8ac56944..69e1370a 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java @@ -2,7 +2,6 @@ import jakarta.persistence.EntityNotFoundException; import java.util.List; -import java.util.Random; import lombok.RequiredArgsConstructor; import mate.academy.rickandmorty.dto.internal.CharacterDto; import mate.academy.rickandmorty.mapper.CharacterMapper; @@ -17,12 +16,6 @@ public class CharacterServiceImpl implements CharacterService { private final CharacterMapper mapper; private final CharacterRepository repository; - @Override - public void save(CharacterDto characterDto) { - Character character = mapper.internalToModel(characterDto); - repository.save(character); - } - @Override @Transactional public void saveAll(List dtos) { @@ -39,29 +32,24 @@ public void saveAll(List dtos) { } @Override - public Character getById(Long id) { - return repository.findByExternalId(id).orElseThrow( - () -> new EntityNotFoundException( - "Could not find Character with id: " + id) + public CharacterDto getById(Long id) { + return repository.findById(id) + .map(mapper::toDto) + .orElseThrow( + () -> new EntityNotFoundException( + "Could not find Character with id: " + id) ); } @Override - public List findByName(String name) { - return repository.findByNameContainingIgnoreCase(name); + public List findByName(String name) { + return repository.findByNameContainingIgnoreCase(name).stream() + .map(mapper::toDto) + .toList(); } @Override - public Character getRandomCharacter() { - Long maxId = repository.findMaxExternalId() - .orElseThrow(() -> - new EntityNotFoundException("Could not find max characters count")); - long randomId = new Random().nextLong(1, maxId + 1); - return repository.findById(randomId) - .orElseThrow( - () -> new EntityNotFoundException( - "Error occurred when trying to find Character with id: " - + randomId) - ); + public CharacterDto getRandomCharacter() { + return mapper.toDto(repository.findRandomCharacter()); } }