Problem z utworzeniem, usunięciem użytkownika (+ zmiana hasła) - Spring Boot

0

Witam. Mam problem z aplikacją. Mam utworzone 2 serwisy, z których jeden (db-connector) łączy się z bazą danych i pozwala na pewne akcje (używam Postmana, aby określać mappingi), a drugi (webApp) używany poprzez przeglądarkę łączy się z pierwszym.

db-connector :

@RestController
public class HelloWorldController {

    @Autowired
    private UserDataAccess userDataAccess;

@DeleteMapping(path = "/user")
    public void deleteUser(@RequestParam(name = "identyfikator") long userId) {
        userDataAccess.deleteUserById(userId);
    }

    @PatchMapping(path = "/user")
    public void updateUser(@RequestParam(name = "identyfikator") long userId, @RequestBody String password) {
        userDataAccess.updateUserPasswordById(userId, password);
    }

    @PostMapping(path = "/createUser")
    public String createUser(@RequestParam(name = "userLogin") String login, @RequestParam(name = "password") String password,
                             @RequestParam(name = "firstName") String firstName, @RequestParam(name = "lastName") String lastName) {

        createUserMethod(login, password, firstName, lastName);
        return login + " " + password + " " + firstName + " " + lastName + " ";
    }

    private User createUserMethod(String login, String password, String firstName, String lastName) {
        User user = UserBuilder.anUser().withLogin(login).withPassword(password).withFirstName(firstName).withLastName(lastName).build();
        userDataAccess.createUser(user);

        return user;
    }
}

//=================================== klasy znajdują się w oddzielnych plikach. Dla ułatwienia wstawiłem jedną pod drugą ;)
@Service
public class UserDataAccess {
    private final String INSERT_SQL = "INSERT INTO EXAMPLE_SCHEMA.USERS(login, password, first_name, last_name) values(:login, :password, :first_name, :last_name)";
    private final String UPDATE_SQL = "UPDATE EXAMPLE_SCHEMA.USERS SET password = :password WHERE id = :userId";
    private final String DELETE_SQL = "DELETE EXAMPLE_SCHEMA.USERS WHERE id = :userId";

    @Autowired
    NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public void createUser(User user) {
        KeyHolder holder = new GeneratedKeyHolder();
        SqlParameterSource parameters = new MapSqlParameterSource()
                .addValue("login", user.getLogin())
                .addValue("password", Base64.getEncoder().encodeToString(user.getPassword().getBytes()))
                .addValue("first_name", user.getFirstName())
                .addValue("last_name", user.getLastName());
        namedParameterJdbcTemplate.update(INSERT_SQL, parameters, holder);
        user.setId(holder.getKey().intValue());
    }

    public void deleteUserById(long userId) {
        SqlParameterSource parameter = new MapSqlParameterSource()
                .addValue("userId", userId);
        namedParameterJdbcTemplate.update(DELETE_SQL, parameter);
    }

    public void updateUserPasswordById(long userId, String password) {
        SqlParameterSource parameter = new MapSqlParameterSource()
                .addValue("userId", userId)
                .addValue("password", Base64.getEncoder().encodeToString(password.getBytes()));
        namedParameterJdbcTemplate.update(UPDATE_SQL, parameter);
    }

    private static final class UserMapper implements RowMapper<User> {
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            return UserBuilder.anUser()
                    .withId(rs.getLong("id"))
                    .withLogin(rs.getString("login"))
                    .withPassword(rs.getString("password"))
                    .withFirstName(rs.getString("first_name"))
                    .withLastName(rs.getString("last_name"))
                    .build();
        }
    }
}

I tak to wygląda to w 1. serwisie. Podałem najważniejsze metody, które są odpowiedzialne za pożądane funkcje aplikacji.
Poprzez Postmana wszystko działa: tworzenie, usuwanie i zmiana hasła. Problem zaczyna się, gdy chcę obsłużyć to przez przeglądarkę.

webApp

@Controller
public class HomeController {

    RestTemplate restTemplate = new RestTemplate();

    @GetMapping("/welcome")
    public String homePage() {
        return "welcome";
    }

    @GetMapping("/delete")
    public String goToDelete(Model model) {
        model.addAttribute("user", new User());
        return "delete";
    }

    @DeleteMapping("/delete")
    public String deleteUser(@ModelAttribute("searchingQuery") SearchingQuery searchingQuery, Model model) {
        User user = restTemplate.getForObject("http://localhost:8080/user?identyfikator=" + searchingQuery.getById(), User.class);
        if(user == null) {
            model.addAttribute("user", new User());
        } else {
            model.addAttribute("user", user);
        }

        return "delete";
    }

    @GetMapping("/updatePassword")
    public String goToUpdatePassword() {
        return "updatePassword";
    }

    @PatchMapping("/updatePassword")
    public String updatePasswordById(@ModelAttribute("createQuery") CreateQuery createQuery, Model model) {
        User user = restTemplate.getForObject("http://localhost:8080/user?identyfikator=" + createQuery.getId(), User.class);
        if(user == null) {
            model.addAttribute("user", new User());
        } else {
            model.addAttribute("user", user);
        }

        return "updatePassword";
    }

    @GetMapping("/createUser")
    public String goToCreateUser() {
        return "createUser";
    }

    @PostMapping("/createUser")
    public String createUser(@ModelAttribute("createQuery") CreateQuery createQuery) {
        User user = restTemplate.getForObject("http://localhost:8080/createUser?userLogin=" + createQuery.getLogin()
                                + "&password=" + createQuery.getPassword() + "&firstName=" + createQuery.getFirstName()
                                + "&lastName=" + createQuery.getLastName(), User.class);

        return "createUser";
    }
}

Pliki html (webApp) (podaje samo ciało)

createUser.html

<body>
<div class="container">
    <form th:action="@{/createUser}" th:object="${createQuery}" method="post">
        <div class="row mt-3">
            <div class="col">
                <span class="input-group-text" id="inputGroup-sizing-default">| Tworzenie użytkownika |</span>
            </div>
            <span class="input-group-text" id="inputGroup-sizing-default2">login:</span>
            <div class="col">
                <input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default" id="login" name="login">
            </div>
            <span class="input-group-text" id="inputGroup-sizing-default3">hasło:</span>
            <div class="col">
                <input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default" id="password" name="password">
            </div>
            <span class="input-group-text" id="inputGroup-sizing-default4">imię:</span>
            <div class="col">
                <input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default" id="firstName" name="firstName">
            </div>
            <span class="input-group-text" id="inputGroup-sizing-default5">nazwisko:</span>
            <div class="col">
                <input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default" id="lastName" name="lastName">
            </div>
            <div class="col">
                <button class="btn btn-primary" type="submit">Wprowadź</button>
            </div>
        </div>
    </form>
</div>

delete.html

<div class="container">
    <form th:action="@{/delete}" th:object="${searchingQuery}" method="post">
        <div class="row">
            <div class="col">
                <span class="input-group-text" id="inputGroup-sizing-default">Wprowadź ID</span>
            </div>
            <div class="col">
                <input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default" id="byId" name="byId">
            </div>
            <div class="col">
                <button class="btn btn-primary" type="submit">Usuń</button>
            </div>
        </div>
    </form>
</div>

*Klasy SearchingQuery i CreateQuery zawierają pola takie jak klasa User (login, password, itd.)
Przy tworzeniu użytkownika po wciśnięciu Wprowadź, w konsoli wywala błąd:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 : [{"timestamp":"2020-09-10T10:59:33.281+00:00","status":405,"error":"Method Not Allowed","message":"","path":"/createUser"}]] with root cause

org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 : [{"timestamp":"2020-09-10T10:59:33.281+00:00","status":405,"error":"Method Not Allowed","message":"","path":"/createUser"}]

Natomiast odnośnie usuwania wywala błąd:

2020-09-10 13:06:17.200  WARN 9000 --- [nio-8081-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]

Gdy staram się dodać w kontrolerze do metody odpowiedzialnej za usuwanie @PostMapping aplikacja łapie to jako inną metodę z POSTEM z członem /user (której tutaj dla ułatwienia nie podałem). Nie wiem jak to rozwikłać, ponieważ w HTML w formie method nie może przyjąć argumentu "delete".

Trochę tego kodu tutaj dużo, ale chciałem jak najlepiej zobrazować problem, ponieważ już dłuższy czas siedzę przed ścianą i nie znalazłem żadnego rozwiązania powyższych problemów.

Pozdrawiam :)

1

Przy tworzeniu użytkownika po wciśnięciu Wprowadź, w konsoli wywala błąd:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 : [{"timestamp":"2020-09-10T10:59:33.281+00:00","status":405,"error":"Method Not Allowed","message":"","path":"/createUser"}]] with root cause

org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 : [{"timestamp":"2020-09-10T10:59:33.281+00:00","status":405,"error":"Method Not Allowed","message":"","path":"/createUser"}]

W HomeController we wszystkich metodach używasz restTemplate.getForObject. Zobacz w dokumentacji co dokładnie ta metoda robi. Z resztą już sama nazwa to sugeruje. Porównaj to z wyjątkiem i tym co masz zadklarowane w klasie HelloWorldController .

Natomiast odnośnie usuwania wywala błąd:

2020-09-10 13:06:17.200  WARN 9000 --- [nio-8081-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]

Gdy staram się dodać w kontrolerze do metody odpowiedzialnej za usuwanie @PostMapping aplikacja łapie to jako inną metodę z POSTEM z członem /user (której tutaj dla ułatwienia nie podałem). Nie wiem jak to rozwikłać, ponieważ w HTML w formie method nie może przyjąć argumentu "delete".

Jak to po zmianie metody na Post łapie Ci metodę z członem /user? Z tego co widzę metoda do usuwania jest mapowana na /delete. Mówimy oczywiście o klasie HomeController. Jeśli jest faktycznie jakiś problem, to dostosuj mapowania tak żeby się nie pokrywały.

0

W HomeController we wszystkich metodach używasz restTemplate.getForObject. Zobacz w dokumentacji co dokładnie ta metoda robi. Z resztą już sama nazwa to sugeruje. Porównaj to z wyjątkiem i tym co masz zadklarowane w klasie HelloWorldController .

Próbowałem kombinować z postForObject i postForEntity i dalej klapa. Nie wiem, co w moim wypadku ma się znaleźć pod argumentem request.

Jak to po zmianie metody na Post łapie Ci metodę z członem /user? Z tego co widzę metoda do usuwania jest mapowana na /delete. Mówimy oczywiście o klasie HomeController. Jeśli jest faktycznie jakiś problem, to dostosuj mapowania tak żeby się nie pokrywały.

Mapowania się nie pokrywają. Nie wiem jak w takim razie mam rozwiązać problem, z tym, ze wymagana jest metoda POST

0

Próbowałem kombinować z postForObject i postForEntity i dalej klapa. Nie wiem, co w moim wypadku ma się znaleźć pod argumentem request.

Sprawdź dokumentację tych metod. Jest tam dokładnie napisane co może być argumentem request.

Mapowania się nie pokrywają. Nie wiem jak w takim razie mam rozwiązać problem, z tym, ze wymagana jest metoda POST

Chyba nie do końca rozumiem. Zmieniłeś metodę deleteUser w HomeController na post i po wysłaniu formularza usunięcia nie wchodzi do tej metody tylko do jakiejś innej, która ma inne mapowanie?

0

Sprawdź dokumentację tych metod. Jest tam dokładnie napisane co może być argumentem request.

Dowiedziałem się, że może być to klasa HttpEntity<T>. Niestety z dokumentacji nie potrafię wywnioskować, jak to przełożyć na moją aplikację. Podjąłem próbę:

@PostMapping("/createUser")
    public String createUser(@ModelAttribute("createQuery") CreateQuery createQuery) {
        HttpEntity<User> request = new HttpEntity<>(new User());
        restTemplate.postForObject("http://localhost:8080/createUser?userLogin=" + createQuery.getLogin()
                                + "&password=" + createQuery.getPassword() + "&firstName=" + createQuery.getFirstName()
                                + "&lastName=" + createQuery.getLastName(), request, User.class);

        return "createUser";
    }

Można powiedzieć, że osiągnąłem pewien sukces, bo poprzedni błąd zniknął, ale za to pojawił się następny.

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : [{"timestamp":"2020-09-10T14:05:59.720+00:00","status":500,"error":"Internal Server Error","message":"","path":"/createUser"}]] with root cause

org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : [{"timestamp":"2020-09-10T14:05:59.720+00:00","status":500,"error":"Internal Server Error","message":"","path":"/createUser"}]

Chyba nie do końca rozumiem. Zmieniłeś metodę deleteUser w HomeController na post i po wysłaniu formularza usunięcia nie wchodzi do tej metody tylko do jakiejś innej, która ma inne mapowanie?

Docelowo ustawione jest @DeleteMapping. Raz zmieniłem na POST w odpowiedzi na błąd i wtedy złapało tę inną metodę. Może niepotrzebnie o tym wspomniałem, przez co cała ta konfuzja.
Nie wiem jak skonfigurować to w HomeController bądź w plikach HTML, żeby łapał @DeleteMapping

0

Dowiedziałem się, że może być to klasa HttpEntity<T>. Niestety z dokumentacji nie potrafię wywnioskować, jak to przełożyć na moją aplikację.

Jako argument request możesz przesłać nagłówki lub ciało zapytania. Mógłbyś tu przesłać zasób do zapisania, jednak Twoja metoda RESTowa nie przyjmuje ciała, bo dane zasobu przekazujesz w parametrach zapytania. W związku z tym, idąc za dokumentacją:

request - the Object to be POSTed (may be null)

Dodatkowo jako argument responseType przekazujesz User.class, czyli spodziewasz się instancji tej klasy w odpowiedzi. Zobacz co zwraca Twoja metoda w klasie HelloWorldController.

Docelowo ustawione jest @DeleteMapping. Raz zmieniłem na POST w odpowiedzi na błąd i wtedy złapało tę inną metodę. Może niepotrzebnie o tym wspomniałem, przez co cała ta konfuzja.
Nie wiem jak skonfigurować to w HomeController bądź w plikach HTML, żeby łapał @DeleteMapping

Potrzebnie o tym wspomniałeś, bo to dobry trop. Z widoku wysyłasz formularz, więc robisz to przez metodę post. W związku z tym musisz ją obsłużysz w swoim kontrolerze.

0

Jako argument request możesz przesłać nagłówki lub ciało zapytania. Mógłbyś tu przesłać zasób do zapisania, jednak Twoja metoda RESTowa nie przyjmuje ciała, bo dane zasobu przekazujesz w parametrach zapytania. W związku z tym, idąc za dokumentacją:

request - the Object to be POSTed (may be null)

Dodatkowo jako argument responseType przekazujesz User.class, czyli spodziewasz się instancji tej klasy w odpowiedzi. Zobacz co zwraca Twoja metoda w klasie HelloWorldController.

Użyłem na samym początku null jako parametr i też nie działało. Teraz za Twoją wskazówką zamieniłem to na:

@PostMapping("/createUser")
    public String createUser(@ModelAttribute("createQuery") CreateQuery createQuery) {
        restTemplate.postForObject("http://localhost:8080/createUser?userLogin=" + createQuery.getLogin()
                                + "&password=" + createQuery.getPassword() + "&firstName=" + createQuery.getFirstName()
                                + "&lastName=" + createQuery.getLastName(), null, String.class);

        return "createUser";
    }

Ale niestety dalej ten sam błąd.

Potrzebnie o tym wspomniałeś, bo to dobry trop. Z widoku wysyłasz formularz, więc robisz to przez metodę post. W związku z tym musisz ją obsłużysz w swoim kontrolerze.

@GetMapping("/searchById")
    public String goToSearchById(Model model) {
        model.addAttribute("user", new User());
        return "searchById"; //view
    }

    @PostMapping("/searchById")
    public String retrieveUser(@ModelAttribute("searchingQuery") SearchingQuery searchingQuery, Model model) {
        User user = restTemplate.getForObject("http://localhost:8080/user?identyfikator=" + searchingQuery.getById(), User.class);
        if (user == null) {
            model.addAttribute("user", new User());
        } else {
            model.addAttribute("user", user);
        }

        return "searchById"; //view
    }
@GetMapping("/delete")
    public String goToDelete(Model model) {
        model.addAttribute("user", new User());
        return "delete";
    }

    @DeleteMapping("/delete")
//    @RequestMapping(value = "/delete", method = {RequestMethod.DELETE, RequestMethod.POST})
    public String deleteUser(@ModelAttribute("searchingQuery") SearchingQuery searchingQuery, Model model) {
        User user = restTemplate.getForObject("http://localhost:8080/user?identyfikator=" + searchingQuery.getById(), User.class);
        if (user == null) {
            model.addAttribute("user", new User());
        } else {
            model.addAttribute("user", user);
        }

        return "delete";
    }

Gdy używam POST to łapie mi szukanie użytkownika. Jak w takim razie obsłużyć tę metodę post?

3

Coś tu jest pomieszane wszystko. Obczaj to: https://spring.io/guides/gs/handling-form-submission/

Przecież Ty nie robisz Rest-owych calli, tylko postujesz najzwyklejsze formularze (czyli zwyczajny @Controller). Ta metoda do usuwania to tez powinien być POST na url „/delete”, a nie metoda DELETE. Zrób sobie ten tutorial i na bazie tego buduj własne rozwiązanie.

0

Czy Ty tam przesylasz login i haslo w url?!?

0

Tak, potem hasło przechodzi przez Base64.getEncoder().encodeToString(user.getPassword().getBytes()). Na te chwilę chciałbym, żeby mi to po prostu działało. Jest to w ogóle możliwe?

1 użytkowników online, w tym zalogowanych: 0, gości: 1