Java/Spring/H2 db - problem z połączeniem z bazą? Dane są update'owane w bazie, ale nie zwraca w aplikacji zmienionych.

0

Cześć

Mam problem z moją aplikacją i nawet nie wiem już, gdzie szukać, bo nigdy wcześniej nie miałem problemów przy innych projektach. Zaznaczam, że dopiero się uczę i to jest kwestia nauki. Mianowicie robię prosty projekt na zasadzie, że mamy pralkę, która po uruchomieniu z linka (REST Controller) zmienia się jej Mode i "pracuje", a potem kończy pracę i też zmienia się jej Mode. Problem jest z zaciąganiem danych z bazy oraz ze zmianą danych poprzez adnotacje @Modyfing i @Query w Repo. Pierw przedstawię dane i będzie łatwiej wytłumaczyć. Dane tak jak poniżej:

@AllArgsConstructor
@RequiredArgsConstructor
@Getter
@Setter
@Entity
public class WashingMachine {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private UUID uuid;

    @Enumerated(EnumType.STRING)
    private Mode mode;
}

Mode jak widać, jest Enumem i jest do wyboru ON, OFF, PAUSE, RUN i FINISH. Repo to jak poniżej:

@Repository
public interface WashingMachineRepository extends CrudRepository<WashingMachine, Integer> {

   List<Job> findAll();

   @Modifying
   @Query("update WashingMachine w set w.mode = :mode where w.uuid = :uuid")
   @Transactional
   void updateMode(@Param(value = "uuid") UUID uuid, @Param(value = "mode") Mode mode);
}

I ta metoda totalnie nie działa. Status zostaje zmieniony w bazie, ale jeśli chcę go wyciągnąć to mi wyskakuje poprzedni. I teraz Serwis:

@Service
@RequiredArgsConstructor
public class WashingMachineService {
    private final WashingMachineRepository washingMachineRepository;

    public void pauseWashing(UUID uuid) {
 //          washingMachineRepository.updateMode(wm.getUuid(), MODE.PAUSE);
           updateModeManually(uuid, Mode.PAUSE);
        }
    }

    public String startWashing(WashingMachine wm) throws Exception {
        wm.setUuid(UUID.randomUUID());
        wm.setMode(Mode.RUN);
        washingMachineRepository.save(wm);
        for (int i = 0; i < 20; i++) {
            if (washingMachineRepository.findAll().get(0).getMode().equals(Mode.PAUSE)) { //obojętnie czy zrobię to po prostu 
                                                                                          //  wm.getMode().equals(Mode.PAUSE)
                break;
 //               throw new Exception("Washing machine paused");
            }
            System.out.println("washing in progress");
            TimeUnit.SECONDS.sleep(5);
        }
//        washingMachineRepository.updateMode(wm.getUuid(), Mode.FINISH);
        updateModeManually(wm.getUuid(), Mode.FINISH);
        System.out.println(findByUUID(wm.getUuid()).getMode());

        return "Washing done";
    }

    private void updateModeManually(UUID uuid, Mode mode) { //to jest zmiana manualna
        WashingMachine wm = findByUUID(uuid);
        wm.setMode(mode);
        washingMachineRepository.save(wm);
    }

   public WashingMachine findByUUID(UUID uuid) {
        return washingMachineRepository.findAll().stream()
                .filter(w -> w.getUuid().equals(uuid))
                .findFirst()
                .orElseThrow(() -> new NotFoundException(uuid));
    }
}    

No i teraz 2 problemy:

  1. Metoda z Repo updateMode nie działa i nie wiem czemu, bo sprawdziłem i jest de facto taka sama jak w innych projektach. W serwisie jest zakomentowana. Problem jest taki, że jak jej użyję to jak wejdę przez konsolę H2 to w Repo jest ten Mode zmieniony na prawidłowy. Ale jak robię print to wyświetla mi poprzedni. Przez to musiałem zmienić i zrobić to "manualnie" tak jak widać. I wtedy "teoretycznie" już działa.
  2. ALE jak chcę użyć metody pauseWashing to nic się nie zmienia - nieważne czy jest wstawiony break czy zakomentowany Exception. A przecież w pętli jest użyty odczyt z bazy aktualnego Mode. Jak sprawdzałem to tak samo w ogóle nie zczytuje z bazy danych aktualnego, już zmienionego statusu.

No i tutaj jest pytanie? Gdzie zrobiłem ten czeski błąd? Ja wiem, że to pierdoła, ale pytanie które to xD Próbowałem wszystkiego, w pom'ie jest standardowo: spring starter web, spring starter test, spring devtools, lombok, spring starter data jpa . Dodałem ekstra mysql connector java i hibernate core w razie w, bo może tutaj jest problem (wiem, że spring data jpa zawiera hibernate). W propertiesach tylko podstawowe ustawienia pod bazę H2, z czego jest zaciągana z memory, ale nic więcej.

Będę bardzo wdzięczny za pomoc!

5

Smuci mnie ten twój kod. Bo nawaliłeś tam adnotacji i różnych magii springowych i jpa i teraz dzielnie walczysz z "frameworkiem" zamiast skupić sie na pisaniu aplikacji. Jeszcze bardziej smuci kiedy widać gołym okiem ze te frameoworki pasują ci tu jak pięść do nosa, bo w niby masz findAll wygenerowane przez spring-data, ale w sumie to go nie używasz, bo chcesz wybrać z bazy konkretny rekord. Niby napisałeś jakaś metodę modyfikujacą, ale w sumie i tak masz tam z palca napisanego sqla do niej. Ale że to wszystko leci przez entitymanagera i 17 kręgów cache to "coś nie działa" i nie wiadomo czemu.
Zgaduje że problem jest tu taki: https://www.baeldung.com/spring-data-jpa-modifying-annotation#clear czyli robisz update ale entitymanager o tym nie wie i próbuje sprytnie zwracać ci obiekty z cache zamiast czytać z bazy.

Niemniej moja rada to:

  1. Wywal stąd JPA w ogóle
  2. Wywal Spring-Data
  3. Zrób po bożemu normalne repozytorium "ręcznie", weź jakieś JDBI albo Spring JDBC Template jeśli bardzo chcesz tu mieć springa i puszczaj normalne query. Myk polega na tym, ze będziesz mógł wygodnie zdebugować wszystko, a nie ze coś się dzieje "magicznie" w tle i nawet nie masz gdzie dać breakpointa.
0

@Shalom: Teraz to mocno do końca zwątpiłem w swoją wiedzę. Myślałem, że to inaczej działa i chyba czeka mnie mnóstwo lektury w takim razie.
Dzięki za rady, póki co muszę to zrobić w ten sposób, bo to będzie mocno rozszerzane o zwracane wyjątki ze statusem aktualnej pracy. O ile w ogóle mi się uda, bo jak widać jest ciężko.
Spróbowałem zrobić to co napisałeś z linku i działa to połowicznie. Mianowicie już zmienia mode, ale w metodzie startWashing, w pętli, w if'e pomimo, że zaciągam z bazy jaki jest mode to dalej w pętli cały czas ma jako Mode.RUN, pomimo, że metoda pauseWashing go zmieniła. I robiłem to za pomocą washingMachineRepository.updateMode(wm.getUuid(), MODE.PAUSE);, gdzie zmieniłem @Modifying(flushAutomatically = true, clearAutomatically = true). Wcześniej było bez flush'a i to samo. Już stawiam wszystko.
Może zamiast bazy zrobić to na cache?

1

pomimo, że zaciągam z bazy

A jaką masz pewność że coś leci z bazy z a z internal cache entity managera? ;) Przykro mi ale im więcej magii, tym mniej kod działa tak jak "ci sie wydaje ze działa". Zgaduje że ten pause to w ogóle wołasz z innego wątku i pewnie dlatego znowu nie widzisz tych zmian, bo entity manager z wątku z pętlą nie wie że cośtam się zmieniło :D
Jeszcze raz: jak chcesz mieć pewność że pobierasz coś z bazy to musisz to robić jakimś ludzkim interfejsem jak chociażby JDBC Template, a nie magią która po drodze ma jakieś 9 kręgów piekła. Włącz sobie logging query wysyłanych przez JPA do bazy i pewnie sie okaże że żadne query nie są robione jak ta twoja pętla się kręci.

Może zamiast bazy zrobić to na cache?

To już trochę pytanie do ciebie w kwestii persystencji - czy ten stan ma być zachowany pomiędzy restartami aplikacji. Niemniej nadal utrzymuje że to nie "baza" sama w sobie jest problemem, tylko ze wrzuciłeś tu kilka bardzo poteznych i skomplikowanych frameworków i walisz z armaty do muchy.

0

Pokaż jak używasz tego serwisu to wszystko będzie jasne

0

@Shalom:

A jaką masz pewność że coś leci z bazy z a z internal cache entity managera? ;) Przykro mi ale im więcej magii, tym mniej kod działa tak jak "ci sie wydaje ze działa". Zgaduje że ten pause to w ogóle wołasz z innego wątku i pewnie dlatego znowu nie widzisz tych zmian, bo entity manager z wątku z pętlą nie wie że cośtam się zmieniło :D
Jeszcze raz: jak chcesz mieć pewność że pobierasz coś z bazy to musisz to robić jakimś ludzkim interfejsem jak chociażby JDBC Template, a nie magią która po drodze ma jakieś 9 kręgów piekła. Włącz sobie logging query wysyłanych przez JPA do bazy i pewnie sie okaże że żadne query nie są robione jak ta twoja pętla się kręci.

A tutaj mnie masz :D nie wiem tego i prawdopodobnie jest dokładnie tak jak piszesz, bo z Postman'a wysyłam drugie zapytanie i pomimo, że używam metody findByUUID, która odnosi się do washingMachineRepository to bierze to z cache Entity Managera. W sumie nie spodziewałem się, że w ten sposób tutaj to działa.

To już trochę pytanie do ciebie w kwestii persystencji - czy ten stan ma być zachowany pomiędzy restartami aplikacji. Niemniej nadal utrzymuje że to nie "baza" sama w sobie jest problemem, tylko ze wrzuciłeś tu kilka bardzo poteznych i skomplikowanych frameworków i walisz z armaty do muchy.

Na to wychodzi, że poprzez cache będzie dużo prościej i wygodniej, bo nie muszą być zapisywane dane do bazy. Baza została prosta postawiona H2 na memory i za każdym razem wyłączenia apki się zeruje. Pierw w ogóle chciałem tak zrobić z cache, ale nie wiem czemu nie mogłem tego zaimplementować, aby to działało poprawnie. Muszę w takim razie spróbować jeszcze raz, bo od razu uczyłem się na tej "Magii" bez ręcznego wstawiania poprzez @Query.

1

Nie wiem w sumie co masz na mysli pisząc cache kiedy tutaj to by ci wystarczyło trzymać sobie ConcurrentHashMap<UUID, Cośtam>

0

@lookacode1:

@RestController
@RequestMapping("/washings")
@RequiredArgsConstructor
public class WashingMachineController {

    private final WashingMachineService washingMachineService;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public String startWashing(@RequestBody WashingMachine wm) throws Exception {
        return washingMachineService.startWashing(wm);
    }

    @GetMapping("/{uuid}")
    @ResponseStatus(HttpStatus.OK)
    public void checkMode(@PathVariable("uuid") UUID uuid) {
        washingMachineService.checkWashing(uuid); //do tego nie wstawiałem na forum
    }

    @PostMapping("/{uuid}/pause")
    @ResponseStatus(HttpStatus.OK)
    public void pauseWashing(@PathVariable("uuid") UUID uuid) {
        washingMachineService.pauseWashing(uuid);
    }
}
0

@Shalom: miałem na myśli zaimplementować np. jcache czy encache i tam przechowywać. Ale w sumie fakt, że HashMapa może będzie w tym przypadku najprostszym rozwiązaniem. Tylko czy nie, aż nazbyt "prostym"? :)

3

Wrzuc jeszcze ze 3 frameworki, redisa i cassandre tam :D Technologie dobiera się do problemu. Jeśli z jakiegoś powodu mapa nie wystarcza (bo chcesz mieć persystencje, albo chcesz współdzielic stan pomiędzy wiele nodów aplikacji) to wtedy myśli sie o czymś lepszym, ale zaczynanie prostego projektu od napchania 17 frameworków to jest bardzo słaby plan.

0

Masz tu jakis miks z tutoriala.
Wez zrob wg jednego zrodla i powinno byc ok:
https://www.baeldung.com/transaction-configuration-with-jpa-and-spring
(Stare) https://spring.io/blog/2011/02/10/getting-started-with-spring-data-jpa/

W tym pierwszym poscie na oko brakuje @Transactional w serwisie.

0
Shalom napisał(a):

ale zaczynanie prostego projektu od napchania 17 frameworków to jest bardzo słaby plan.

Nadzieja na szybką komercjalizację ?

1

@vpiotr: Niestety, ale tak jak napisał @Shalom (btw odpisałem Ci w komentarzu w Twojej odpowiedzi) w komentarzu - wtedy totalnie nie widzi, że się wykonuje zadanie, a jeszcze jest jeden @GetMapping, który będzie sprawdzał stopień tryb pracy pralki na podstawie uuid.

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