Relacje one-many/many-one miedzy 2 encjami do JSON'a - rekurencyjnie zapetlone

0

Siemacie
Mam taki problem, postaram sie powiedziec krótko. Mam 2 encje, mniej-wiecej takie:

@Entity
@Table(name="user")
public class User implements Serializable{
    ...
    @OneToMany(mappedBy = "reporter")
    @Cascade(CascadeType.ALL)
    private List<Report> reportList;

i druga:

@Entity
@Table(name = "report")
public class Report implements Serializable {
    ...
    @ManyToOne
    private User reporter;

I teraz kwestia tego typu, że: jakis tam springowy controller zwraca mi w postaci JSON'a obiekt User. User ma swoja liste Report'ow, natomiast kazdy report ma swojego Usera jako właściciela, który znowu ma liste Reportów i tak w rekurencyjna nieskonczoność.

Rozwiązania które wyczytałem w internetach są dwa:

  1. Dodac do klasy Report przy User owner; @JsonIgnore. Wtedy jak zwracam jsona to pole User owner jest ignorowane i sie nie zapętla. Powiedzmy, że to troche średnie rozwiazanie, bo co jak chce zeby controller zwrocil mi JSON;owa postać obiektu Report? wtedy nie bede mial danych kogo jest ten Report, bo jest tam @JsonIgnore
  2. Dodac adnotacje: dla listy Reports w klasie User: @JsonManagedReference i dla User owner; dac @JsonBackReference, jednak generalnie daje to to samo, co @JsonIgnore.

Gdybym nawet chciał przecierpieć to, że zwracajac Report nie bede mial jego ownera, to problem pojawia sie, jakbym chciał np. wysłać nowy raport, wtedy nie moge dodac pola User bo: 1.po pierwsze mam tam @JsonIgnore 2.Jakbym go nie miał to znowu wleze w rekurencyjna pętle.

Może jakoś źle zaprojektowałem to, ale czuje, że to może być powszechny problem i po prostu nie wiem o jakiejś istotnej rzeczy.

Jakieś wsparcie? ;)

1

Jak używasz Jacksona to możesz użyć referencji opartych o identyfikatory > http://wiki.fasterxml.com/JacksonFeatureObjectIdentity

To powstało z myślą o właśnie takich sytuacjach, bo JSON nie obsługuje sam w sobie referencji cyklicznych.

0

Znalazłem to wczoraj przed pójściem spać, ale już nie czytalem za dużo.
@Koziołek Ale chyba ta adnotacja nie jest w bilbiotece Jacksona? bo mam najnowszą wersje w mavenie, a nie istnieje taka w ścieżce klas
pytam na szybko, jeszcze nie czytałem o tym bo nie mialem czasu, sprawdze to sobie dziś :)

0

A na pewno masz najnowszą wersję 2+?

0

@Koziołek
używam tych zależności: http://mvnrepository.com/artifact/org.codehaus.jackson/jackson-jaxrs
i z tego co widze to najwyższe 1.9.13

ale znalazłem też to: http://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
z tym, że jak podmieniam zależności na jedne z tych, to nie istnieje żadna adnotacja z @jSon na początku

edit
jak dodałem te zależności:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.0.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.0.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.0.0</version>
    </dependency>

To o dziwo działa, da rade te 3 zastąpić jednymi?

0

Zapewne nie da rady ponieważ to są osobne moduły, a nie ma pakietu jackson-all. Ale pytanie brzmi po co? BTW, 2.4.1 jest najnowsze.

0

@Koziołek zwykle jak mam nawalone tych zależności to potem są konflikty, więc ograniczam sie tylko do tych które sa potrzebne. Core i Annotations pewnie są niezbedne, a databind? do czego służy?

wersje już ustawiłem na najnowsza :P

1

https://github.com/FasterXML/jackson-databind/wiki/Databind-annotations dodatkowe adnotacje i metody serializacji. Przydatne.

0

@Koziołek
ok fajnie działa, tylko mam jeszcze jeden problem, nie wiem od czego zależny
chcialem przetestować czy User z Reportami sie zapisuje do bazy i jak wyglada JSON, to w service zrobilem taka metode ktora tworzy Usera i jego reporty i zapisuje.
wyglada to mni-wincy tak:

public User createUserTEST(){
        User user = new User();
        user.setProfile(null);
        user.setAddress(null);
        user.setName("Imie");
        user.setLastname("Nazwisko");
        user.setAge(12);
        Report report = new Report("r1", "rr1", new Date(), user);
        Report report1 = new Report("r2", "rr2", new Date(), user);
        Report report2 = new Report("r3", "rr3", new Date(), user);
        List<Report> list = new ArrayList<Report>(1);
        list.add(report);
        list.add(report1);
        list.add(report2);
        user.setReportList(list);
        this.create(user); //zapis encji do bazy
        return user;
    }

I teraz mam metode w kontrolerze (po prostu woła userService.createUserTEST(); ) i get'em zwraca mi zserializowany obiekt Usera do JSON'a i wyglada on tak:

{"id":0,"name":"Imie","lastname":"Nazwisko","age":12,"profile":null,"address":null,"reportList":[{"id":0,"title":"r1","reportText":"rr1","date":1407102141456,"reporter":0},{"id":0,"title":"r2","reportText":"rr2","date":1407102141456,"reporter":0},{"id":0,"title":"r3","reportText":"rr3","date":1407102141456,"reporter":0}]}

I generalnie za kazdym razem jak wejde pod ten url z co jest zmapowany do tej metody w kontrolerze, to do bazy dodają sie kolejne encje i maja w bazie poprawne id: 1,2,3,4... a w JSONie zawsze jest ID zero, dlaczego?

0

Mi to wygląda jakbyś miał:

 	int id;

zamiast:

 	Integer id;

tzn id jest prymitywem, nie obiektem.

0

@azalut kod:

this.create(user); //zapis encji do bazy

To ustawia ID tego obiektu w parametrze czy zwraca nowy? Bo mi coś mówi, że zwraca nowy, a ty zwracasz w następnej linii obiekt "wzorcowy", który nie ma ustawionego id.

0

zmiana z long na Long (bo używam do "id" typu long) nic nie dała, zamiast 0 mam nulle
@Koziołek metoda create wyglada tak:

public User create(User user){
        return userRepository.create(user);
    }

natomiast w repository tak:

public User create(User user) {
        try{
            entityManager.merge(user);
            return user;
        }catch(EntityExistsException eee){
            System.err.println("Entity exists already.");
            return null;
        }
    }

Ogólnie na polu "id" mam ustawione @ GeneratedValue i @ Id więc domyślam się, że id jest nadawane w późniejszym momencie i dlatego dostaje 0.
Myślałem też, żeby w metodzie create jakimś TypedQuery wyciagać encje z bazy wg tego co mam w parametrze create(User user) i wtedy zwracać, ale pewnie tam jeszcze tego ID nie ma, i w dodatku - pal licho moj projekt, ale w większym kwestia wydajności byłaby przegięta jak znam życie :)

Tak by the way:
Nie jestem do konca pewien czy metoda create powinna mi zwracac obiekt, w wypadku sukcesu podczas zapisu. Ale z drugiej strony w podejściu REST po zapisie mam mieć location do zasobu ktory zostal utworzony (tj. nagłówek Location:) ale chce zwracać też JSON'owa postac encji ktora zapisalem bo może się przydać w warstwie widoku. Nie wiem czy to dobre podejście :P

1

Mi się osobiście pomysł ze zwracaniem encji podoba, bo ma jedną zaletę. Dość dobrze czyta się taki kod. Ma tu miejsce pewna "konstrukcja literacka", którą można zapisać w ten sposób:

zmienna = nowa Zmienna();
zmienna ustaw atrybuty
zapisanaZmienna = zapisz(zmienna)
zwróć zapisanaZmienna

samo wywołanie zapisu do bazy nie daje informacji czy zmienna zawiera obiekt zapisany i zarządzany przez EM, czy też tylko ten, na podstawie którego dokonano zapisu.

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