Spring data jak zrobić metodę, która nadpisuje rekord wybrany na podstawie zmiennej?

0

Witam. Robię projekt w Spring Boot, z restowymi metodami do baz danych. O ile z metodami GET, POST, DELETE nie mam problemu, to nie mogę zrobić prostej metody PUT, która by na podstawie ID nadpisywała wskazany rekord w bazie danych. Na początku próbowałem jakoś analogicznie do tego tutaj: https://www.baeldung.com/http-put-patch-difference-spring

    @PutMapping("country/{name}")
    public void replaceCountry(@RequestBody Country country, @PathVariable("name")String name){
        countryRepository.save(country, name);
    }

Tyle, że countryRepository nie posiada w ogóle metody save z dwoma argumentami. Pomyślałem, że może trzeba to nadpisać i będzie grało, ale też nie. Próbowałem jeszcze tak:

    @PutMapping("country/{name}")
    Country replaceCountry(@RequestBody Country newCountry, @PathVariable String name) {
        return countryRepository.findFirstByName(name)
                .map(country -> {
                    country=newCountry;
                    return countryRepository.save(country);
                })
                .orElseGet(() -> {
                    return countryRepository.save(newCountry);
                });
    }

Jeśli dobrze kumam, to z samym zapytaniem jest coś nie tak. No próbowałem przez Postmana w ten sposób, że ustawiam metodę PUT, w sekcji body robię JSona, którym ma być nadpisany rekord w bazie danych, a parametr w zapytaniu przekazuję o tak: localhost:8080/country?name=Erathia.

1

A coś takiego działa?

country.setName(name);
countryRepository.save(country);

Swoją drogą ciekawe jak reagować gdy country.getName() jest różne od name?

0

@KamilAdam: Nie rozumiem chyba. Na podstawie name ma mi wyszukać dane państwo. Na podstawie JSona ma zostać zamienione na inne. W zależności czy chcę tylko poprawić informacje o jakimś państwie czy zamienić sobie Tibię na Rurytanię nie powinno to chyba mieć znaczenia. A ten sposób, żeby pobierać każde pole tutaj raczej nie jest za dobry, bo klasa Country ma ich 15. No ale dobra, tak czy inaczej nie wiem czy przypadkiem nie ma problemu z samym zapytaniem w Postmanie, bo jeśli zrobię metodę o tak:

    @PutMapping("country/{name}")
    Country replaceCountry(@RequestBody Country newCountry, @PathVariable String name) {
        Country country = countryRepository.findFirstByName(name).get();
        country.setName(newCountry.getName());
        return country;
    }

To wywala mi dokładnie ten sam błąd:

"timestamp": "2021-10-01T19:28:59.607+00:00",
    "status": 405,
    "error": "Method Not Allowed",
    "trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'PUT' not supported\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:253)\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:442)\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:383)\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:125)\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:67)\n\tat org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:498)\n\tat org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1261)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1043)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:920)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:684)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:832)\n",
    "message": "Request method 'PUT' not supported",
    "path": "/country"
0

@paranoise

  1. Ten kod nadal jest źle, bo raczej nie masz @Transactional na kontrolerze, więc brakuje ci tam .save() na tym zmienionym obiekcie
  2. Błąd sugeruje ze walisz PUTem w endpoint /country a nie /country/uganda tak jak powinieneś
0

@Shalom: Faktycznie. Robiłem tak: localhost:8080/country?name=Uganda, a miało być tak jak mówisz. A co do tego @Transactional, to czytam sobie teraz o tym, ale póki co tak piąte przez dziesiąte to jarzę. Póki co mam tak:

@Transactional
@RestController
public class WorldService {
    @Autowired
    CountryRepository countryRepository;
    @Autowired
    LanguageRepository languageRepository;
    @Autowired
    CityRepository cityRepository;
    @PutMapping("country/{name}")

//inne metody
    Country replaceCountry2(@RequestBody Country newCountry, @PathVariable String name) {
        Country country = countryRepository.findFirstByName(name).get();
        country.setName(newCountry.getName());
        return country;
    }

ale pewnie coś musi być jeszcze nad samą metodą, bo na razie wywołanie metody nie wywala błędów, ale też nie daje żadnych rezultatów.

0

xD Wincyj adnotacji! Mało jeszcze jest!

A tak poważnie, to weź zrób sobie jakiś serwis do którego wrzucisz logikę a kontroler zostaw "czysty". No i nadal nie masz w kodzie countryRepository.save(country) które rozwiązałoby twój problem... Z tym Transactional to sobie tak tylko zażartowałem, absolutnie nie dodawaj go do kontrolera.

0

Poczytaj o czymś takim jak cykl życia encji w hibernate.

0

Ogolnie jesli 'name' w 'country' jest u Ciebie primary key to raczej bym tego nigdy nie zmienial. Lepiej bedzie jak zrobis sobie osobny 'id' i po tym wyszukiwac, wtedy zmiana nazwy nie bedzie juz wymagala dodatkowej wartosci w metodzie 'PUT'. Podeslesz tylko caly obiekt 'country' razem ze stalym (niezmiennym) id.

0

@hugos290: Nie. Akurat ta tabela ma tak, że za klucz robi jakiś kod kraju. Klasa encji może nie wyszła mi idealnie, ale działają różne joiny z nią, więc chyba nie jest źle:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="country")
public class Country {
    @Id
private String code;
private String name;
private String continent;
private String region;
private Integer surfacearea;
private Integer indepyear;
private Integer population;
private Double lifeexpectancy;
private Integer gnp;
private Integer gnpold;
private String localname;
private String governmentform;
private String headofstate;
private Integer capital;
private String code2;
}

No ale właśnie. Wydaje mi się, że w takim wypadku trochę przypałowo jest ustawiać wartość każdego pola i zastanawiam się czy tutaj metodę nie mogę zrobić o tak:

    @PutMapping("country/{name}")
    Country replaceCountry(@RequestBody Country newCountry, @PathVariable String name) {
        Country country = countryRepository.findFirstByName(name).get();
        country=newCountry;
        countryRepository.save(country);
        return country;
    }

No bo jeśli dobrze kumam normalnie nie robi się tego country=newCountry, bo tworzy dziwne referencje, które jeśli będą powodować błędy, to trudno je będzie namierzyć. No ale chyba obiekty country i newCountry są sobie tutaj przez parę linijek do wykonania metody, a po zapisie w bazie danych już ich i tak nie ma?

0

Zaraz zaraz...

  1. Czy tylko mi się wydaje, czy PUT zawsze powinien tworzyć nową encję - a ty przecież chcesz poprawić wartość encji istniejącej?
  2. Podmiana referecji nic ci nie da. Równie dobrze mógłbyś wywołać countryRepository.save(newCountry).

Jeśli chcesz coś podmienić to musisz zamienić code po którym identyfikujesz w repozytorium elementy:

Country country = countryRepository.findFirstByName(name).get();
newCountry.setCode(country.getCode());
countryRepository.save(newCountry);

return newCountry;

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