Sesja i zduplikowana wartość klucza w bazie danych.

Sesja i zduplikowana wartość klucza w bazie danych.
TR
  • Rejestracja:11 miesięcy
  • Ostatnio:3 miesiące
  • Postów:7
0

Cześć, mam dwa modele; User i Company

Kopiuj
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "company")
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToMany(mappedBy = "followedCompany")
    private Set<User> followedUsers = new HashSet<>();
}
Kopiuj
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.EAGER)
    @JoinTable(
            name = "user_company_follow",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "company_id")
    )
    private Set<Company> followedCompany = new HashSet<>();

    public boolean addFollowedCompany(Company company){
        if(followedCompany.contains(company)){
            return false;
        }

        company.getFollowedUsers().add(this);
        followedCompany.add(company);
        return true;
    }
}

Kod mocno uprościłem do najważniejszych elementów, aby był bardziej czytelny.

Jak widać, stworzona też jest relacja ManyToMany.

Problem pojawia się w momencie korzystania z endpointa:

Kopiuj
    @ResponseBody
    @RequestMapping(value = "/follow/{id}", method = RequestMethod.POST)
    public ResponseEntity<String> followCompany(@PathVariable("id") Long id) {
        User user = userSession.getUser();
        if (user == null) {
            //TODO Obsługa braku zalogowanego użytkownika
            return ResponseEntity.badRequest().body("User not logged in");
        }
        //user = userRepository.findById(user.getId()).get();

        Optional<Company> companyOptional = repository.findById(id);
        if (companyOptional.isPresent()) {
            Company company = companyOptional.get();

            boolean added = user.addFollowedCompany(company);
            if (added) {
                userRepository.save(user);
                repository.save(company);
                return ResponseEntity.ok("Company followed successfully.");
            } else {
                return ResponseEntity.badRequest().body("User already follows this company.");
            }
        } else {
            return ResponseEntity.notFound().build();
        }
    }

Na początku wyciągam użytkownika z sesji, oraz sprawdzam czy obiekt user nie jest null - Czyli czy użytkownik jest zalogowany.
Sesja:

Kopiuj
@Data
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
    private User user;
}

Problem pojawia się, gdy dam obserwacje najpierw jednej firmie, a próbuje dać drugiej. Gdy w między czasie się wyloguje (usunę użytkownika z sesji) i zaloguje ponownie (dodam użytkownika do sesji wyciągniętego z bazy danych), to wszystko działa.
Dostaje taki błąd:

org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "user_company_follow_pkey"
Detail: Key (user_id, company_id)=(252, 1) already exists.

252 - ID użytkownika
1 - ID firmy.
Problemem jest to, że identyfikator firmy, którą właśnie zaobserwowałem jest równy 2. Aplikacja próbuje dodać ponownie firmę, którą wcześniej zaobserwowałem (z identyfikatorem = 1) i to powoduje błąd.
Aplikacja tak jakby nie jest świadoma tego, że umieściła już ten rekord wcześniej w tabeli i próbuje umieścić go ponownie.

Co ciekawe, jeśli pobiorę użytkownika ponownie z bazy danych, na podstawie ID użytkownika z sesji:
user = userRepository.findById(user.getId()).get();
to kod działa prawidłowo i nie wyrzuca wspomnianego błędu.

Moje pytanie brzmi. Czy muszę za każdym razem pobierać użytkownika z bazy danych i na nim operować, zamiast korzystać z obiektu wyciągniętego z sesji? Czy musze jakoś odświeżać obiekt w sesji, aby to działało?

BB
  • Rejestracja:ponad 2 lata
  • Ostatnio:29 dni
  • Postów:66
0

Powinieneś chyba wyciągnąć set i dodać do niej id 2 , a nie nadpisywac

99xmarcin
  • Rejestracja:około 5 lat
  • Ostatnio:5 miesięcy
  • Postów:2420
0

Dawno już nie pisałem w Hibernate ale wydaje mi się że followedCompany trzeba zastąpić getterem, zwłaszcza jak masz włączony lazy loading.
Taka rada od wujka który już z SQL niewiele ma wspólnego.


Holy sh*t, with every month serenityos.org gets better & better...
TR
  • Rejestracja:11 miesięcy
  • Ostatnio:3 miesiące
  • Postów:7
0
bbzzyyczczeek napisał(a):

Powinieneś chyba wyciągnąć set i dodać do niej id 2 , a nie nadpisywac

Nie bardzo wiem, o co chodzi 😛

0xmarcin napisał(a):

Dawno już nie pisałem w Hibernate ale wydaje mi się że followedCompany trzeba zastąpić getterem, zwłaszcza jak masz włączony lazy loading.
Taka rada od wujka który już z SQL niewiele ma wspólnego.

Jeśli chodzi Tobie o zamianę followedCompany.add(company) na getFollowedCompany().add(company) w funkcji addFollowedCompany() to nie rozwiązało to problemu

BB
  • Rejestracja:ponad 2 lata
  • Ostatnio:29 dni
  • Postów:66
0

Zobacz jeszcze zamiast @Data
normalnie equals(), hashCode()

TR
  • Rejestracja:11 miesięcy
  • Ostatnio:3 miesiące
  • Postów:7
0
bbzzyyczczeek napisał(a):

Zobacz jeszcze zamiast @Data
normalnie equals(), hashCode()

Jestem nowy w Spring Boot i wiem za bardzo co te dwie funkcje mają wspólnego z moim problemem. Może to było istotne i powinienem o tym wspomnieć, ale w klasach User oraz Company nadpisałem te funkcje:
User:

Kopiuj
  @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       User user = (User) o;
       return Objects.equals(id, user.id);
   }

   @Override
   public int hashCode() {
       return Objects.hash(id, login, password, email, role);
   }

Company

Kopiuj
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Company company = (Company) o;
        return Objects.equals(id, company.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, description, logoUrl, backgroundUrl, nameUrl);
    }

Problem rozwiązuje pobierania za każdym razem użytkownika z repozytorium zamiast korzystać z obiektu zapisanego w UserSession. Czy podejście pobierania użytkownika za każdym razem z repo jest bardzo nieefektywne i nie powinno się tego stosować, czy nie ma to dużego wpływu na szybkość działania aplikacji?

TR
  • Rejestracja:11 miesięcy
  • Ostatnio:3 miesiące
  • Postów:7
0

Ref...

Problem rozwiązuje pobierania za każdym razem użytkownika z repozytorium zamiast korzystać z obiektu zapisanego w UserSession. Czy podejście pobierania użytkownika za każdym razem z repo jest bardzo nieefektywne i nie powinno się tego stosować, czy nie ma to dużego wpływu na szybkość działania aplikacji?

Mam jeszcze inne, krótkie pytanie, niezwiązane z tym tematem.
Jeśli mam tabele miast (id (long), name (String)) oraz ~1000 rekordów, to czy warto załadować całą tabelę do Listy, aby nie odwoływać się do bazy danych za każdym razem, gdy użytkownik wyszukuje dane dla lokalizacji w mieście? Tabela ta jest niemodyfikowalna, więc nie pojawią się problemy z rozbieżnością danych w tabeli i w liście zapisanej w pamięci.

PI
  • Rejestracja:ponad 9 lat
  • Ostatnio:3 miesiące
  • Postów:2787
0
Treektus napisał(a):

Problem rozwiązuje pobierania za każdym razem użytkownika z repozytorium zamiast korzystać z obiektu zapisanego w UserSession. Czy podejście pobierania użytkownika za każdym razem z repo jest bardzo nieefektywne i nie powinno się tego stosować, czy nie ma to dużego wpływu na szybkość działania aplikacji?

Jak strzelasz z repozytorium, to ponieważ to wszystko i tak jest opakowane Hibernetem, to i tak on sobie sam ogarnie czy może użyć cache (1 poziomu automatycznie, możesz samemu skonfigurować też cache 2 poziomu, np ehcache).

Mam jeszcze inne, krótkie pytanie, niezwiązane z tym tematem.
Jeśli mam tabele miast (id (long), name (String)) oraz ~1000 rekordów, to czy warto załadować całą tabelę do Listy, aby nie odwoływać się do bazy danych za każdym razem, gdy użytkownik wyszukuje dane dla lokalizacji w mieście? Tabela ta jest niemodyfikowalna, więc nie pojawią się problemy z rozbieżnością danych w tabeli i w liście zapisanej w pamięci.

Jak ~~1000 rekordów i niemodyfikowalne, to na luzie możesz trzymać sobie w pamięci aplikacji jako swój cache.

AS
  • Rejestracja:prawie 4 lata
  • Ostatnio:około 4 godziny
  • Postów:346
1

Trzymanie obiektów JPA w stanie detached i próba używania ich w innej sesji JPA to proszenie się o problemy. Przeładowywuj obiekty przy każdym requeście - EntityManager.refresh, albo repository.findById. Jak się boisz o wydajność, spróbuj dodać second-level cache, ale nadal przeladowywuj w kodzie.

Nie należy też mylić sesji JPA i sesji HTTP.
Nie widzę, gdzie masz granice transakcji w twoim kodzie. Mam nadzieję, że nie mieszasz koncepcyjnie sesji http („@Scope(value = "session"”) z transakcjami bazodanowymi.

—-

Jeśli mam tabele miast (id (long), name (String)) oraz ~1000 rekordów, to czy warto załadować całą tabelę do Listy, aby nie odwoływać się do bazy danych za każdym razem, gdy użytkownik wyszukuje dane dla lokalizacji w mieście? Tabela ta jest niemodyfikowalna, więc nie pojawią się problemy z rozbieżnością danych w tabeli i w liście zapisanej w pamięci.

Jeszcze jak.

Ale też, to zależy, co rozumiesz pod „wyszukiwaniem danych dla lokalizacji w mieście”.

—-

Przy okazji, narzekania starego dziada ;)

Jestem nowy w Spring Boot

Nic w tym kodzie, nie wskazuje na Spring-Boota.

Czy to twoje pierwsze kroki z bazami danych w Javie? Jak mnie to wnerwia, że nowi ludzie zaczynają od spring-data-jpa+Spring-Boot, a przecież po drodze jest kilka frameworków i koncepcji, które trzeba najpierw zrozumieć, aby dobrze używać tych zaawansowanych narzędzi.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)