Od jakiegoś czasu pracuję z JPA (implementacja Hibernacja) i za każdym razem, gdy muszę tworzyć encje, mam problemy z AccessType, niezmiennymi właściwościami, equals / hashCode, ....
Postanowiłem więc znaleźć ogólną najlepszą praktykę dla każdego numeru i zapisać ją na własny użytek.
Nie miałbym jednak nic przeciwko komentowaniu go lub mówieniu, gdzie się mylę.
Klasa podmiotu
implementować Serializable
Powód: specyfikacja mówi, że musisz, ale niektórzy dostawcy JPA tego nie egzekwują. Hibernacja jako dostawca JPA nie wymusza tego, ale może zawieść gdzieś głęboko w brzuchu z ClassCastException, jeśli Serializable nie został zaimplementowany.
Konstruktory
utwórz konstruktor ze wszystkimi wymaganymi polami encji
Powód: Konstruktor powinien zawsze pozostawić instancję utworzoną w stanie normalnym.
poza tym konstruktorem: mieć pakiet prywatny konstruktor domyślny
Powód: Domyślny konstruktor jest wymagany, aby Hibernacja zainicjowała jednostkę; private jest dozwolone, ale widoczność pakietu prywatnego (lub publicznego) jest wymagana do generowania proxy środowiska wykonawczego i efektywnego wyszukiwania danych bez oprzyrządowania z kodem bajtowym.
Pola / właściwości
Użyj ogólnego dostępu do pola i dostępu do nieruchomości w razie potrzeby
Powód: jest to prawdopodobnie najbardziej dyskusyjna kwestia, ponieważ nie ma jasnych i przekonujących argumentów dla jednego lub drugiego (dostęp do nieruchomości a dostęp do pola); jednak dostęp do pól wydaje się być ulubionym ze względu na bardziej przejrzysty kod, lepszą enkapsulację i brak potrzeby tworzenia ustawień dla niezmiennych pól
Pomiń setery dla niezmiennych pól (niewymagane dla pola typu dostępu)
- właściwości mogą być prywatne
Powód: Kiedyś słyszałem, że funkcja chroniona jest lepsza pod kątem (Hibernacji) wydajności, ale wszystko, co mogę znaleźć w Internecie, to: Hibernacja może uzyskać dostęp do publicznych, prywatnych i chronionych metod akcesorów, a także bezpośrednio do pól publicznych, prywatnych i chronionych . Wybór należy do Ciebie i możesz dopasować go do swojego projektu aplikacji.
Equals / hashCode
- Nigdy nie używaj wygenerowanego identyfikatora, jeśli ten identyfikator jest ustawiony tylko podczas utrwalania encji
- Preferencje: użyj niezmiennych wartości, aby utworzyć unikalny klucz biznesowy i użyj go do przetestowania równości
- jeżeli unikalny Klucz Biznesowy nie jest dostępny, należy użyć przejściowego UUID, który jest tworzony podczas inicjowania jednostki; Zobacz ten świetny artykuł, aby uzyskać więcej informacji.
- nigdy nie odnoszą się do powiązanych podmiotów (ManyToOne); jeśli ten podmiot (podobnie jak podmiot nadrzędny) musi być częścią klucza biznesowego, porównaj tylko identyfikatory. Wywołanie getId () na proxy nie spowoduje załadowania encji, o ile korzystasz z typu dostępu do właściwości .
Przykładowa jednostka
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
Inne sugestie dotyczące dodania do tej listy są mile widziane ...
AKTUALIZACJA
Od czasu przeczytania tego artykułu dostosowałem swój sposób implementacji eq / hC:
- jeśli dostępny jest niezmienny prosty klucz biznesowy: użyj tego
- we wszystkich innych przypadkach: użyj UUID
final
(sądząc po twoim pominięciu seterów, zgaduję, że ty też).notNull
pochodzi?Odpowiedzi:
Spróbuję odpowiedzieć na kilka kluczowych punktów: pochodzi z długiego doświadczenia w Hibernacji / trwałości, w tym z kilku głównych aplikacji.
Klasa jednostki: implementować Serializable?
Klucze muszą implementować Serializable. Rzeczy, które zostaną wprowadzone w sesji HttpSession lub wysłane przez RPC / Java EE, muszą zaimplementować Serializable. Inne rzeczy: nie bardzo. Poświęć czas na to, co ważne.
Konstruktory: utworzyć konstruktor ze wszystkimi wymaganymi polami encji?
Konstruktor (y) logiki aplikacji powinien mieć tylko kilka krytycznych pól „klucz obcy” lub „typ / rodzaj”, które zawsze będą znane podczas tworzenia encji. Resztę należy ustawić, wywołując metody ustawiające - po to są.
Unikaj umieszczania zbyt wielu pól w konstruktorach. Konstruktory powinny być wygodne i zapewniać obiektowi zdrowy rozsądek. Imię, typ i / lub rodzice są zwykle przydatni.
OTOH, jeśli reguły aplikacji (dzisiaj) wymagają, aby Klient miał Adres, pozostaw to ustawiającemu. To jest przykład „słabej reguły”. Może w przyszłym tygodniu chcesz utworzyć obiekt klienta przed przejściem do ekranu Wprowadź szczegóły? Nie potykaj się, pozostaw możliwość nieznanych, niekompletnych lub „częściowo wprowadzonych” danych.
Konstruktory: także, pakiet prywatny konstruktor domyślny?
Tak, ale używaj opcji „chroniony” zamiast pakietu prywatnego. Podklasowanie jest prawdziwym bólem, gdy niezbędne elementy wewnętrzne nie są widoczne.
Pola / właściwości
Użyj dostępu do pola „właściwość” dla Hibernacji i spoza instancji. W instancji użyj pól bezpośrednio. Powód: umożliwia działanie standardowego odbicia, najprostszej i najbardziej podstawowej metody hibernacji.
Jeśli chodzi o pola „niezmienne” dla aplikacji - Hibernacja wciąż musi być w stanie je załadować. Możesz spróbować ustawić te metody jako „prywatne” i / lub umieścić na nich adnotację, aby zapobiec niepożądanemu dostępowi kodu aplikacji.
Uwaga: pisząc funkcję equals (), używaj getters dla wartości w instancji „other”! W przeciwnym razie trafisz na niezainicjowane / puste pola na instancjach proxy.
Protected jest lepszy dla (hibernacji) wydajności?
Mało prawdopodobne.
Equals / HashCode?
Dotyczy to pracy z bytami, zanim zostaną one zapisane - co jest drażliwym problemem. Mieszanie / porównywanie niezmiennych wartości? W większości aplikacji biznesowych nie ma żadnych.
Klient może zmienić adres, nazwę firmy itp. - nie jest to powszechne, ale zdarza się. Korekty muszą być również możliwe, gdy dane nie zostaną wprowadzone poprawnie.
Nieliczne rzeczy, które są zwykle niezmienne, to Rodzicielstwo i być może Typ / Rodzaj - zwykle użytkownik odtwarza rekord, zamiast go zmieniać. Ale nie jednoznacznie identyfikują bytu!
Tak długo i krótko, twierdzone, że „niezmienne” dane nie są tak naprawdę. Pola klucza głównego / identyfikatora są generowane właśnie w celu zapewnienia takiej gwarantowanej stabilności i niezmienności.
Musisz zaplanować i wziąć pod uwagę potrzebę faz prac związanych z porównywaniem, mieszaniem i przetwarzaniem żądań, gdy A) pracujesz z „zmienionymi / powiązanymi danymi” z interfejsu użytkownika, jeśli porównujesz / mieszasz „rzadko zmieniane pola” lub B) pracujesz z „ niezapisane dane ”, jeśli porównasz / hash na ID.
Równa się / HashCode - jeśli unikalny klucz biznesowy nie jest dostępny, użyj nieprzemijającego UUID, który jest tworzony podczas inicjowania encji
Tak, w razie potrzeby jest to dobra strategia. Należy pamiętać, że identyfikatory UUID nie są bezpłatne, ale pod względem wydajności - a grupowanie komplikuje sprawy.
Equals / HashCode - nigdy nie odnoszą się do powiązanych jednostek
„Jeśli jednostka powiązana (jak jednostka nadrzędna) musi być częścią klucza biznesowego, dodaj pole, którego nie można wstawić i nie można jej zaktualizować, aby przechowywać identyfikator nadrzędny (o tej samej nazwie co kolumna Jointo ManytoOne) i użyj tego identyfikatora w kontroli równości „
Brzmi jak dobra rada.
Mam nadzieję że to pomoże!
źródło
Specyfikacja JPA 2.0 stanowi, że:
Specyfikacja nie zawiera żadnych wymagań dotyczących implementacji metod equals i hashCode dla encji, tylko dla klas kluczy podstawowych i kluczy mapowania, o ile mi wiadomo.
źródło
Moje 2 centy oprócz odpowiedzi tutaj to:
W odniesieniu do dostępu do pola lub właściwości (poza kwestiami wydajności) oba są legalnie dostępne za pomocą metod pobierających i ustawiających, dlatego moja logika modelu może ustawiać / uzyskiwać je w ten sam sposób. Różnica pojawia się, gdy dostawca środowiska wykonawczego trwałości (Hibernate, EclipseLink lub inny) musi utrwalić / ustawić rekord w tabeli A, który ma klucz obcy odnoszący się do niektórych kolumn w tabeli B. W przypadku typu dostępu do właściwości trwałość system wykonawczy używa mojej metody setter do przypisania komórce w kolumnie tabeli B nowej wartości. W przypadku typu dostępu do pola system wykonawczy trwałości ustawia komórkę bezpośrednio w kolumnie tabeli B. Ta różnica nie ma znaczenia w kontekście relacji jednokierunkowej, jednak NALEŻY użyć mojej własnej metody kodowania (typ dostępu do właściwości) dla relacji dwukierunkowej, pod warunkiem, że metoda metody ustawiania jest dobrze zaprojektowana w celu uwzględnienia spójności. Spójność jest kluczowym zagadnieniem w odniesieniu do relacji dwukierunkowychlink do prostego przykładu dla dobrze zaprojektowanego setera.
W odniesieniu do Equals / hashCode: Niemożliwe jest użycie automatycznie wygenerowanej metody Eclipse Equals / hashCode dla podmiotów uczestniczących w relacji dwukierunkowej, w przeciwnym razie będą miały okrągłe odwołanie, co spowoduje wyjątek przepełnienia stosu. Gdy spróbujesz relacji dwukierunkowej (powiedzmy OneToOne) i automatycznie wygenerujesz Equals () lub hashCode () lub nawet toString (), zostaniesz złapany na tym wyjątku przepełnienia stosu.
źródło
Interfejs jednostki
Podstawowa implementacja dla wszystkich jednostek, upraszcza implementacje Equals / Hashcode:
Entity Room impl:
Nie widzę sensu porównywanie równości podmiotów na podstawie pól biznesowych w każdym przypadku podmiotów JPA. Może to być bardziej przypadkowe, jeśli te jednostki JPA są uważane za ValueObjects oparte na domenie, zamiast encji opartych na domenie (dla których te przykłady kodu są).
źródło