@ManyToMany remove relation in Spring

@ManyToMany remove relation in Spring
AP
  • Rejestracja:około 6 lat
  • Ostatnio:prawie 4 lata
  • Postów:37
0
Kopiuj
    @Override
    public void deleteGroupById(Group group, User user) {
        user.getGroupSet().remove(group);
    }

relacje w encjach

Kopiuj
@ManyToMany(mappedBy = "group_list")
    private Set<User> userList = new HashSet<>();
Kopiuj
@ManyToMany(cascade = { CascadeType.ALL })
    @JoinTable(
            name = "user_group",
            joinColumns = { @JoinColumn(name = "user_id") },
            inverseJoinColumns = { @JoinColumn(name = "group_id") }
    )
    private Set<Group> group_list = new HashSet<>();

User przechowuje listę grup i na odwrót, grupa przechowuje listę userów. Stworzona jest trzecia tabela która przechowuje klucze obce do id_usera i id_grupy. Potrzebuję usuwać grupę o określonym id z listy dla konkretnego użytkownika o określonym id.

mam sobie taki kod, ale usuwa mi tylko object z seta, a relacja w bazie zostaje, jak to uczynić w Springu czy tam Hibernate żeby to śmigało? :)

edytowany 2x, ostatnio: AfrykanskiPomorSwin
K5
  • Rejestracja:około 6 lat
  • Ostatnio:4 minuty
  • Postów:1002
1
  1. W relacjach many to many nie używaj Cascade.All ani Cascade.Remove, do poczytania: https://thoughts-on-java.org/avoid-cascadetype-delete-many-assocations/
  2. Zamień to na cascade = {CascadeType.PERSIST, CascadeType.MERGE})
  3. Musisz zdecydować kto jest ownerem relacji (definiuje to mappedBy) i zmian dokonywać tylko po tej stronie:

The mappedBy attribute of the posts association in the Tag entity marks that, in this bidirectional relationship, the Post entity owns the association. This is needed since only one side can own a relationship, and > changes are only propagated to the database from this particular side.

W twoim wypadku User jest właścicielem relacji. Więc mając usera pobierasz zbiór gróp i usuwasz z niego grupę o twoim id.
Dodatkowo musisz z omawianej grupy, dostać się do zbioru userów i usunąć tego konkretnego.

AP
  • Rejestracja:około 6 lat
  • Ostatnio:prawie 4 lata
  • Postów:37
0

a pokażesz kod jak usuwać taką grupę?
trzeba mieć entityManagera czy jest jakaś magiczna sztuczka z metodami dostępnymi w crudRepository?

K5
  • Rejestracja:około 6 lat
  • Ostatnio:4 minuty
  • Postów:1002
0

EntityManager to raczje do usuwania grupy całkowicie z bazy :)
Ty chcesz tylko usunąć relacje. Więc w swojej metodzie usuwającej dodaj kolejna linijkę, coś ala:

Kopiuj
 group.getUserList().remove(user)
edytowany 1x, ostatnio: kixe52
Zobacz pozostałe 3 komentarze
K5
A przeczytałeś dwie ostatnie linie posta, który właśnie komentujemy?
AP
aaaa, że na odwrót :P
K5
"dodaj kolejną linijkę" - słowo klucz: KOLEJNĄ
AP
user.getGroupList().remove(group); group.getUserList().remove(user); nic się nie zmienia :P
K5
Wrzuć więcej kodu. Encje i klasy w których dokonujesz usuwania.
AP
  • Rejestracja:około 6 lat
  • Ostatnio:prawie 4 lata
  • Postów:37
0

Alternatywą byłoby zrobienie kolejnej encji UserGroup.java
z polami
private long id;
private long user_id;
private long group_id;

i zrobić

Kopiuj
public interface UserGroupRepository extends CrudRepository<UserGroup, Long> {
}
...
public interface UserGroupService {
  deleteRelation(long userId, long groupId);
}

...
    @Override
    public void deleteRelation(long userId, long groupId) {
        Set<UserGroup> userGroupSet = new HashSet<>();
        userGroupRepository.findAll().iterator().forEachRemaining(userGroupSet::add);
        for(UserGroup ug : userGroupSet)
            if(uq.getUserId()==userId && uq.getGroupId() == groupId)
               userGroupRepository.deleteById(uq.getId());
        userGroupRepository.deleteById(id);
    }

no ale brzydko to wygląda

edytowany 1x, ostatnio: AfrykanskiPomorSwin
AP
  • Rejestracja:około 6 lat
  • Ostatnio:prawie 4 lata
  • Postów:37
0

A więc tak, encja Group

Kopiuj
//imports ...
@Entity
public class Group {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column(columnDefinition="DATETIME")
    @Temporal(TemporalType.TIMESTAMP)
    private Date lolA
    @Column(columnDefinition="DATETIME")
    @Temporal(TemporalType.TIMESTAMP)
    private Date lolB
    private int lolC

    @ManyToMany(mappedBy = "group_list")
    private Set<User> userList = new HashSet<>();

   //geters and setters ...
}

encja User

Kopiuj
//imports

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birth_date;

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
            name = "group_user",
            joinColumns = { @JoinColumn(name = "user_id") },
            inverseJoinColumns = { @JoinColumn(name = "group_id") }
    )
    private Set<Group> groupList = new HashSet<>();

//geters and setters
}

userReporitory i groupRepository

Kopiuj
public interface UserRepository extends CrudRepository<User, Long> {
}
...

public interface GroupRepository extends CrudRepository<Group, Long> {
}

UserService

Kopiuj
public interface UserService {
    Set<User> getAll();
    User findById(Long id);
    void deleteById(Long id);
    void update(User t);
    void deleteGroupById(User user, Group group);
}

UserServiceImplementation

Kopiuj

@Service
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository){
        this.userRepository = userRepository;
    }

    @Override
    public Set<User> getAll() {
        Set<User> userSet = new HashSet<>();
        userRepository.findAll().iterator().forEachRemaining(userSet::add);
        return userSet;
    }

    //findById wyciąłem tą metodę bo działa dobrze

    @Override
    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }

    @Override
    public void update(User t) {
        userRepository.save(t);
    }

    @Override
    public void deleteGroupById(User user, Group group) {
        user.getGroupList().remove(group);
        group.getUserList().remove(user);
    }
}

edytowany 1x, ostatnio: AfrykanskiPomorSwin
K5
Equals + hashCode ?
AP
że niby przesłonić te metody?
AP
lol, po co, nie porównuję obiektów ;p
K5
  • Rejestracja:około 6 lat
  • Ostatnio:4 minuty
  • Postów:1002
3
AfrykanskiPomorSwin napisał(a):

lol, po co, nie porównuję obiektów ;p

Niech mi ktoś jeszcze raz powie, że zaczęcie pisania webówki bez ogarnięcia podstaw javy jest spoko pomysłem, bo przecież da się to ogarnąć podczas projektu webowego :)

Wykorzystujesz w swoim kodzie HashSet. Skąd java ma wiedzieć, że obiekt, który chcesz usunąć znajduje się w zbiorze?

Polecam poczytać o kontrakcie equals - hashode oraz o tym jak działa HashSet / HashMapa w Javie.

edytowany 1x, ostatnio: kixe52
AP
HashSet przecież ma już te metody zaimplementowane nie ma potrzeby ich przesłaniać
Shalom
Hashset może i ma, ale chodzi o TWOJE KLASY, które dziedziczą te metody z Object, a to niekoniecznie jest to co cię interesuje. Nawet bardzo nie jest. Przecież to hash twojego obiektu jest liczony jak go wkładasz do HashSeta. Jakbyś wkładał HashSeta do HashSeta to by cię interesowało czy klasa HashSet ma equals i hashcode poprawne...
AP
userRepository.save(user); <--- tyle trzeba było dodać po usuwaniu żeby zatrybiło. Żadne przesłanianie equals i hashcode...
AP
tak czy siak dzięki za pomoc :P
Shalom
No nie do końca, bo jeśli to wszystko było w otwartej transakcji to takie save ani merge nie powinno być konieczne, ale w sumie u ciebie w ogóle nie widać na jakim poziomie jest transakcja.
AP
Nie ma transakcji, menedżera encji to znaczy gdzieś tam pewnie jest, ale spring się tym zajmuje, a metoda save pochodzi z crud repository
Shalom
Czyli jest na poziomie repository, a to jest słaby pomysł, szczególnie z takim kodem jak twój, bo masz tu race conditions w każdej z tych metod w serwisach. Jak 2 userów wywoła sobie deleteGroupById w tym samym czasie, to jest spora szansa ze jeden z nich "wycofa" zmiany drugiego... Bo od pobrania danych aż do save nie masz żadnej atomowości, ktoś inny może sobie pobrać dane zanim zrobiłeś save z pierwszego wątku i potem swoim save nadpisze te zmiany z pierwszego wątku.
AP
Więc użycie entity managera i transakcji w sensie tr.begin() coś tam zrob; tr.end() zalatwi ten problem?
Shalom
Tak, ale wtedy np. ten cały twój save już będzie zbędny. Z drugiej strony brak equals/hashcode to nadal problem, bo to ci działa tylko i wyłącznie dlatego, że w jednym ciągu wyjmujesz obiekty i potem je usuwasz z seta, więc masz tam realnie te same obiekty/referencje i działa to zupełnie przypadkiem. Dla zobrazowania problemu -> wyciągnij usera i z niego grupę. Następnie z bazy jeszcze raz wyciągnij tego samego usera i wywołaj swoje deleteGroupById podając tą grupę oraz usera (tego wyciągniętego drugi raz).
AP
Ok, mam @EqualsAndHashCode z lombok'a w każdej encji User i Group. Teraz sobie pomyślałem że w np deleteGroupById przed removem można by było sprawdzić czy w ogóle taka grupa dla takiego usera istnieje i wtedy ją usuwać, przy dodawaniu nowego usera/grupy można by było sprawdzić czy już taki user/grupa istnieje, jeżeli nie istnieje to wtedy dopiero go save'ować. Czy to rozwiązałoby problem race contition? :)
AP
https://www.callicoder.com/spring-boot-rest-api-tutorial-with-mysql-jpa-hibernate/ <-- tutaj np używają JpaRepository zamist CrudRepository i przy zapisie notatki jest return noteRepository.save(note); i nie przejmują się race condition :P
Shalom
No i co z tego? To jakiś g**no-tutorial pisany przez hindusa. Jeszcze napchali logikę biznesową do kontrolera. Faktycznie, jest się na czym wzorować :D :D
AP
a tamto co napisałem rozwiązałoby problem "dwóch userów robi to samo w tym samym czasie"? Bo rozważam drugą opcję, wywalić UserRepository GroupRepository i zrobić UserDAO, GroupDAO no i korzystać z managera encji zamiast crud repository
K5
Po co masz sprawdzać przed usuwaniem czy obiekt istnieje? Jak wywołasz remove na secie i nie będzie obiektu, który chcesz usunać to po prostu nic się nie stanie. Co do transakcji, @Shalom jakie masz zdanie o @Transactional ?
AP
ale nie wywołuje tylko remove na secie tylko też save'a usera z remov'niętym objectem z seta który zawiera grupy :)
AP
i chcę uniknąć tego -> "Jak 2 userów wywoła sobie deleteGroupById w tym samym czasie, to jest spora szansa ze jeden z nich "wycofa" zmiany drugiego..."
Shalom
Jak zrobisz transakcje dookoła to będzie ok, bez różnicy czy ręcznie przez begin i end czy jakimś @Transactional
K5
Wydaje mi się, że nie rozumiesz pojęcia Transakcja, po co to jest i jak tego użyć. Nie załatwisz tego sprawdzaniem, czy dany obiekt jeszcze istnieje. Use case'ów jest wiele, przecież nie zrobisz ifa dla każdego.
AP
Robiłem już transakcje w innej aplikacji, wiem do czego są. Jednak chciałem to obejść TYLKO z użyciem metod dostępnych w JpaRepository. Po coś to w końcu jest :)
K5
Obejść co? A jaki jest problem w użyciu adnotacji @Transactional?
AP
czy przypadkiem metody pochodzące z CrudRepository, JpaRepository już w domyśle nie są oznaczone @Transactional ?
Shalom
@AfrykanskiPomorSwin: tak to jest jak ktoś nie umie programować i próbuje wskoczyc od razu w jakieś frameworki. Brak mi słów. Tak, metody z JpaRepository są transakcyjne, bo inaczej w ogóle by ci to nie działało. Ale masz granularność per metoda! A ty chcesz wykonać coś co biznesowo wymaga atomowości, a jednoczesnie robi wiele zapytań do bazy. Dlatego musisz objąć transakcją większy fragment kodu...
AP
Mówisz? :D czyli po przerobieniu książki do javy powinienem ją jeszcze raz czytać? Sory, ale nie, dzięki :D
K5
Patrząc po twojej odpowiedzi: "po co mi equals, przecież nie porównuje obiektów" śmiem stwierdzić, że tak. Może nie tą samą, ale jednak powinieneś się doszkolić :)
AP
o nieee :O że niby przerabiać to samo w konsolce, no nie ma opcji haha
K5
Zawsze możesz iść na bootcamp :)
AP
@Service @Transactional, działa :D dwóch klientów odpala w tym samym czasie /user/1/group/delete/1 i nie ma błędów :) sprawdzane przy użyciu klasy Robot :P
WE
wow, jestem pod wrażeniem nieświadomości swoich braków i ogromnego ego przy takich umiejętnościach i wiedzy, nie życzę nikomu takiej osoby w zespole
AP
powiedziała osoba mająca 10 punktów :D

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.