Dwa sposoby identyfikowania tej samej encji w celu łatwiejszego testowania

0

Mam na przykład klasę

@Setter
@EqualsAndHashCode(of = "id")
@Entity
public class MovieBoxOfficeEntity {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Setter(AccessLevel.NONE)
        private Long id;
}

który ma automatycznie generowany identyfikator bez ręcznego ustawiania. Mam również klasę z metodą

@Entity
@Table(name = "movies")
public class MovieEntity {

       List<MovieBoxOffice> boxOffices = new ArrayList<>();

       public void addBoxOffice(MovieBoxOffice boxOffice) throws ResourceConflictException {
            if (this.boxOffices.contains(boxOffice)) {
                 throw new ResourceConflictException("A box office with id " + boxOffice.getId() + " is already added");
            }
           this.boxOffices.add(boxOffice);
      }
}

Występuje problem z testowaniem metody addBoxOffice, ponieważ porównywanie obiektów MovieBoxOfficeEntity odbywa się za pomocą identyfikatora id, a identyfikator jest generowany automatycznie tylko podczas zapisu do bazy danych i nie można go ustawić ręcznie.

Wpadłem na pomysł dodania pola uniqueId do klasy MovieBoxOfficeEntity i dodania go do adnotacji @EqualsAndHashCode

@Setter
@EqualsAndHashCode(of = {"id", "uniqueId"})
@Entity
public class MovieBoxOfficeEntity {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Setter(AccessLevel.NONE)
        private Long id;

        private String uniqueId = UUID.randomUUID().toString();
}

to mogę przetestować w ten sposób

@Test(expected = ResourceConflictException.class)
public void canAddBoxOffice() throws ResourceConflictException{
    final String id = UUID.randomUUID().toString();
    final MovieBoxOfficeEntity boxOffice = new MovieBoxOfficeEntity();
    boxOffice.setUniqueId(id);

    this.movieEntity.addBoxOffice(boxOffice);
    this.movieEntity.addBoxOffice(boxOffice);
}

w ten sposób obiekt boxOffice będzie posiadał

id: null
uniqueId: jakieś wygenerowane UUID

a porównanie obiektów odbędzie się w oparciu uniqueId, bo każde id będzie null.

Co sądzicie o tworzeniu pola uniqueId głównie w celu testowania metod encji?

1

To co robisz totalnie nie ma sensu:

  • oczywistym jest że equals na automatycznie generowanym id nie będzie działał i to nie dlatego, że w danym momencie jeszcze go nie masz wygenerowanego a dlatego, że aplikacja nie wygeneruje Ci 2 razy tego samego;

*UUID.randomUUID() nie zwróci Ci 2 razy tego samej wartości, więc nic Ci to nie daje;

Zastanów się jakie wartości musi posiadać MovieBoxOfficeEntity żebyś uznał, że już taki rekord masz i po tych polach zrób metodę equals().

Ten twój test też jest kompletnie bez sensu.

1

I dodatkowo pomysł na uzycie Listy (która nie powinna być za bardzo używana w JPA swoją drogą) i sprawdzanie czy ta lista zawiera dany obiekt zamiast korzystania z Seta to już w ogole...
Nie wiem OPie co bierzesz ale bierz połowę dawki :D

0

Po przemyśleniu macie rację, że to kompletnie be sensu.
@scibi92: A jaka jest różnica pomiędzy List a Set. List może zawierać duplikaty, a Set nie. Dodatkowo listy już podczas pobierania są posortowane dzięki adnotacji

    @OneToMany(mappedBy = "movie", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @OrderBy("date ASC")
    private List<MovieReleaseDateEntity> releaseDates = new ArrayList<>();

natomiast na kolekcji Set nie da rady użyć adnotacji @OrderBy. Musiałbym zrobić to w ten sposób

    @OneToMany(mappedBy = "movie", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @OrderBy("date ASC")
    private SortedSet<MovieReleaseDateEntity> releaseDates = new TreeSet<>();

i implementować Comparable dla MovieReleaseDateEntity.

 // Overriding the compareTo method
   public int compareTo(MovieReleaseDateEntity entity){
      return (this.boxOffice).compareTo(entity.boxOffice);
   }
1
Czarek12 napisał(a):

natomiast na kolekcji Set nie da rady użyć adnotacji @OrderBy. Musiałbym zrobić to w ten sposób

    @OneToMany(mappedBy = "movie", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @OrderBy("date ASC")
    private SortedSet<MovieReleaseDateEntity> releaseDates = new TreeSet<>();

i implementować Comparable dla MovieReleaseDateEntity.

 // Overriding the compareTo method
   public int compareTo(MovieReleaseDateEntity entity){
      return (this.boxOffice).compareTo(entity.boxOffice);
   }

A ty masz świadomość, że posortowane sety jak np TreeSet, to właśnie do sprawdzania contains używają compareTo a nie equals prawda? Jeśli byś został przy tym rozwiązaniu, musisz uwzględnić ten kontrakt właśnie pomiędzy compareTo a equals - żeby nie było że w secie będziesz mieć dwa obiekty na których equals dałby true

0

Czyli jeśli mam już na liście objekt np.
MovieBoxOfficeEntity (boxOffice = "1000", country = Country.USA)

i ta klasa ma
@EqualsAndHashCode(of = {"boxOffice", "country"}

to podczas sprawdzania czy kolekcja zawiera element
if (this.boxOffices.contains(boxOffice))

nie będzie sprawdzało za pomocą metody equals tylko compareTo? To jak tutaj w taki razie sprawdzić hashCode obiektów w celu uniknięcia duplikatów?

Oczywiście pomijam opcję pobierania całej kolekcji z objektami do jakiejś nowej kolekcji nie posortowanej (HashSet) i porównywania w pętli każdego objektu z osobna.

1

Lista i HashSet użyją equals, a TreeSet użyje compareTo

0

Mam jeszcze ostatnie pytanie. Jeśli mam trzy obiekty

1. MovieBoxOfficeEntity (boxOffice = "1000", country = Country.USA)
2. MovieBoxOfficeEntity (boxOffice = "2000", country = Country.POLAND)
3. MovieBoxOfficeEntity (boxOffice = "1000", country = Country.CANADA)

a metoda compareTo z interfejsu Comparable będzie sortowała na podstawie atrybutu boxOffice, to nie będzie to tak wyglądało, że w kolekcji znajdą się tylko dwa elementy 2. i 3., bo elementy 1. i 3. mają taki sam atrybut boxOffice? Czy może kolejność zadziała w oparciu o wcześniejsze wystąpienie w kolekcji?

3

Sety są naturalne w JPA, a właśnie Listy sa bardziej kosztowne Dodatkowo JPA ma jakieś swoje implementacje tych List Setow itd. Poczytajcie ziomeczki
https://www.thoughts-on-java.org/5-common-hibernate-mistakes-that-cause-dozens-of-unexpected-queries/

0

@scibi92: używam listy, ponieważ podczas pobierania chcę, aby była posortowana @OrderBy("boxOffice ASC") Również lista konsumuje mniej pamięci https://stackoverflow.com/questions/50668646/which-method-is-more-optimal-for-the-base-and-sorting-speed/50668830?noredirect=1#comment88348046_50668830

1
Czarek12 napisał(a):

Mam jeszcze ostatnie pytanie. Jeśli mam trzy obiekty

1. MovieBoxOfficeEntity (boxOffice = "1000", country = Country.USA)
2. MovieBoxOfficeEntity (boxOffice = "2000", country = Country.POLAND)
3. MovieBoxOfficeEntity (boxOffice = "1000", country = Country.CANADA)

a metoda compareTo z interfejsu Comparable będzie sortowała na podstawie atrybutu boxOffice, to nie będzie to tak wyglądało, że w kolekcji znajdą się tylko dwa elementy 2. i 3., bo elementy 1. i 3. mają taki sam atrybut boxOffice? Czy może kolejność zadziała w oparciu o wcześniejsze wystąpienie w kolekcji?

Wówczas będziesz mieć w secie dwa elementy - na pewno nr 2, a czy nr 1 czy nr 3, to już pytanie co pierwsze włożyłeś. Będzie wówczas to, co pierwsze włożyłeś.

1
scibi92 napisał(a):

Sety są naturalne w JPA, a właśnie Listy sa bardziej kosztowne Dodatkowo JPA ma jakieś swoje implementacje tych List Setow itd. Poczytajcie ziomeczki
https://www.thoughts-on-java.org/5-common-hibernate-mistakes-that-cause-dozens-of-unexpected-queries/

@scibi92 Dobry artykuł. Ogólnie o wszystkich 5 wiedziałem, w sumie zawsze używałem setu a nie listy ale nie do końca zastanawiałem się czemu. Ogólnie wniosek jest jeden - czasem do bulk update / deleta / selecta lepiej napisać jakieś native query czy fetch profile, a resztę już pozostawiać Hibernate'owi.

0

Jak lepiej zwrócić posortowaną kolekcję?

Zostawić List z adnotacją @OrderBy("date ASC")

czy ustawić
private SortedSet<MovieReleaseDateEntity> releaseDates = new TreeSet<>();
a sortowanie robić interfejsem Comparable?

Dajmy przykładowy film na IMDB ma 53 daty premier https://www.imdb.com/title/tt6644200/releaseinfo?ref_=tt_dt_dt#releases. Czyli w moim przypadku posiadałbym 53 encji MovieReleaseDateEntity, które trzeba posortować według daty.

Który sposób będzie szybszy. Ten z List i adnotacją czy Set z Comparable?

0

To daj set ;)

0

Użytkownik na SO twierdzi, że lepiej wybrać List, bo konsumuje mniej pamięci. Co o tym sądzisz?

EDIT: Chciałem sprawdzić czy serio JPA dla List wykonuje te dodatkowe polecenia, ale ich nie zaobserwowałem.

Dodałem obiekt MovieReleaseDateEntity do listy

@OneToMany(mappedBy = "movie", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @OrderBy("date ASC")
    private List<MovieReleaseDateEntity> releaseDates = new ArrayList<>();
movie.getReleaseDates().add(movieReleaseDate);
this.movieRepository.save(movie);

i EclipseLink wykonał polecenie

[EL Fine]: sql: 2018-06-04 14:57:20.633--ClientSession(1803617259)--Connection(190514252)--INSERT INTO movies_info (ID, release_date_country, release_date, entity_version, reported_for_delete, reported_for_update, status, movie_id, dtype) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	bind => [1005, USA, 2018-06-05, 1, false, false, WAITING, 2, RELEASE_DATE]

Dokładnie TYLKO jedna kwerenda dodania do bazy.

0

Nie zrobiłeś tego samego przypadku co na stronce od scibi92. Chodzi o to, że w swojej encji miałbyś kolejną jakaś kolekcję, czyli podsumowując: kolekcja encji, spośród których każda ma swoją kolekcję

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