Stwórz idealną jednostkę JPA [zamkniętą]

Pracuję z JPA (implementation Hibernate) już od jakiegoś czasu i za każdym razem, gdy muszę tworzyć encje, zmagam się z problemami takimi jak AccessType, immutable properties, equals / hashCode, ... .
Postanowiłem więc spróbować znaleźć ogólną najlepszą praktykę dla każdego zagadnienia i zapisać ją do użytku osobistego.
Nie miałbym jednak nic przeciwko temu, aby ktokolwiek skomentował To lub powiedział mi, gdzie się mylę.

Klasa Entity

  • Zaimplementuj Serializable

    Powód: Specyfikacja mówi, że musisz, ale niektórzy dostawcy JPA nie wymuszają tego. Hibernate jako dostawca JPA nie wymusza tego, ale może zawieść gdzieś głęboko w żołądku z ClassCastException, jeśli Serializable nie zostało zaimplementowane.

Konstruktorzy

  • Utwórz konstruktor ze wszystkimi wymaganymi polami encji

    powód: konstruktor powinien zawsze pozostawić utworzoną instancję w stanie zdrowym.

  • Oprócz tego konstruktora: mieć prywatny konstruktor domyślny pakietu

    powód: domyślny konstruktor jest wymagany, aby Hibernate zainicjował obiekt; dozwolone jest prywatne, ale do generowania proxy w trybie runtime i efektywnego pobierania danych bez oprzyrządowania kodu bajtowego wymagana jest prywatna (lub Publiczna) widoczność pakietu.

Pola / Właściwości

  • Użyj field access w ogóle I property access w razie potrzeby

    powód: jest to prawdopodobnie najbardziej dyskusyjny problem, ponieważ nie ma jasnych i przekonujących argumentów dla jednego lub drugiego (dostęp do właściwości vs dostęp do pola); jednak dostęp do pola wydaje się być ogólnie preferowany ze względu na jaśniejszy kod, lepszą hermetyzację i brak potrzeby tworzenia setterów dla niezmiennych pól

  • Pominięcie setterów dla pól niezmiennych (nie jest wymagane dla pola typu dostępu)

  • nieruchomości mogą być prywatne
    Powód: kiedyś słyszałem, że protected jest lepsze dla wydajności (Hibernate), ale jedyne co mogę znaleźć w sieci to: Hibernate może uzyskać dostęp do publicznych, prywatnych i chronionych metod dostępowych, a także bezpośrednio do pól publicznych, prywatnych i chronionych. Wybór należy do Ciebie i możesz dopasować go do projektu aplikacji.

Równa się / hashCode

  • nigdy nie używaj wygenerowanego identyfikatora, jeśli ten identyfikator jest ustawiony tylko podczas utrzymywania encji
  • według preferencji: użyj niezmiennych wartości, aby utworzyć unikalny biznes Key and use this to test equality
  • jeśli unikalny klucz biznesowy nie jest dostępny, użyj nietrwałego UUID , który jest tworzony podczas inicjalizacji podmiotu; Zobacz ten wspaniały artykuł, aby uzyskać więcej informacji.
  • nigdy nie odwołaj się do powiązanych jednostek (ManyToOne); jeśli ta jednostka (podobnie jak jednostka nadrzędna) musi być częścią klucza biznesowego, porównaj tylko identyfikator. Wywołanie getId () na serwerze proxy nie spowoduje ładowania encji, o ile używasz Typ dostępu do nieruchomości .

Przykładowy Podmiot

@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, aby dodać do tej listy są bardziej niż mile widziane...

UPDATE

Od 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
Author: RBz, 2011-05-17

4 answers

Specyfikacja JPA 2.0 stwierdza, że:

  • Klasa entity musi mieć konstruktor no-arg. Może mieć również innych konstruktorów. Konstruktor no-arg musi być publiczny lub chroniony.
  • Klasa entity musi być klasą najwyższego poziomu. Enum lub interfejs nie może być wyznaczony jako podmiot.
  • Klasa entity nie może być ostateczna. Żadne metody ani stałe zmienne instancji klasy entity nie mogą być ostateczne.
  • jeśli podmiot instancja ma być przekazywana przez wartość jako odłączony obiekt (np. poprzez zdalny interfejs), Klasa entity musi zaimplementować interfejs Serializowalny.
  • zarówno klasy abstrakcyjne, jak i konkretne mogą być bytami. Jednostki mogą rozszerzać klasy podmiotów niebędących podmiotami, a także klasy podmiotów, A klasy podmiotów niebędących podmiotami mogą rozszerzać klasy podmiotów.

Specyfikacja nie zawiera wymagań dotyczących implementacji metod equals i hashCode dla encji, tylko dla klucza podstawowego klasy i klucze do map z tego co wiem.

 124
Author: Edwin Dalorzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-28 17:02:03

Postaram się odpowiedzieć na kilka kluczowych kwestii: to jest z długiego Hibernate / persistence doświadczenie w tym kilka głównych aplikacji.

Klasa encji: implementacja Serializowalna?

Keys musi zaimplementować Serializable. Rzeczy, które mają iść w HttpSession, lub być wysyłane przez przewód przez RPC / Java EE, musi zaimplementować Serializable. Inne rzeczy: nie za bardzo. Poświęć czas na to, co ważne.

Konstruktory: Utwórz konstruktor ze wszystkimi wymagane pola podmiotu?

Konstruktor(y) dla logiki aplikacji powinien mieć tylko kilka krytycznych pól "klucz obcy" lub "Typ/Rodzaj", które zawsze będą znane podczas tworzenia encji. Reszta powinna być ustawiona przez wywołanie metod settera - po to one są.

Unikaj umieszczania zbyt wielu pól w konstruktorach. Konstruktorzy powinni być wygodni i dać obiektowi podstawową zdrową psychikę. Imię, typ i / lub rodzice są zazwyczaj przydatne.

OTOH if zasady aplikacji (dzisiaj) wymagają, aby klient miał adres, zostaw to seterowi. Jest to przykład "słabej zasady". Może w przyszłym tygodniu chcesz utworzyć obiekt klienta przed przejściem do ekranu Wprowadź szczegóły? Nie potkaj się, pozostaw możliwość podania nieznanych, niekompletnych lub" częściowo wprowadzonych " danych.

Konstruktory: również, Pakiet prywatny konstruktor domyślny?

Tak, ale używaj 'protected' zamiast 'package private'. Podkategoria rzeczy to prawdziwy ból, gdy niezbędne elementy wewnętrzne nie są widoczne.

Pola / Właściwości

Użyj dostępu do pola' property ' dla Hibernate i spoza instancji. W instancji użyj pól bezpośrednio. Powód: pozwala na działanie standardowego odbicia, najprostszej i najbardziej podstawowej metody hibernacji.

Jeśli chodzi o pola 'immutable' do aplikacji -- Hibernate nadal musi być w stanie je załadować. Możesz spróbować uczynić te metody "prywatnymi" i/lub umieścić na nich adnotację, aby zapobiegaj niepożądanemu dostępowi do kodu aplikacji.

Uwaga: podczas pisania funkcji equals (), użyj getterów dla wartości na 'drugiej' instancji! W przeciwnym razie trafisz na niezainicjowane / puste pola na instancjach proxy.

Protected jest lepszy dla (Hibernate) wydajności?

Mało prawdopodobne.

Równa Się / HashCode?

Jest to istotne przy pracy z bytami, zanim zostaną zapisane -- co jest drażliwym problemem. Hashing / comparing na niezmiennych wartościach? W większość aplikacji biznesowych, nie ma żadnych.

Klient może zmienić adres, zmienić nazwę swojej firmy, itp itd -- nie jest to powszechne, ale zdarza się. Korekty muszą być również możliwe do wykonania, gdy dane nie zostały wprowadzone poprawnie.

Kilka rzeczy, które normalnie są niezmienne, to Rodzicielstwo i być może Typ/Rodzaj -- zwykle użytkownik odtwarza rekord, zamiast go zmieniać. Ale nie identyfikują one jednoznacznie podmiotu!

Tak, długo i krótko, deklarowane "niezmienne" dane nie są tak naprawdę. Podstawowe pola klucza / identyfikatora są generowane dokładnie w celu zapewnienia takiej gwarantowanej stabilności i niezmienności.

Musisz zaplanować i rozważyć potrzebę porównywania i haszowania i przetwarzania żądań faz pracy, gdy A) praca z "zmienionymi/ powiązanymi danymi" z interfejsu użytkownika, jeśli porównujesz/hash na "rzadko zmienianych polach", lub B) praca z "niezapisanymi danymi", jeśli porównujesz/hash na ID.

Equals / HashCode -- jeśli unikalny klucz biznesowy jest nie jest dostępny, użyj niestacjonarnego identyfikatora UUID, który jest tworzony podczas inicjalizacji encji

Tak, jest to dobra strategia, gdy jest to wymagane. Należy pamiętać, że uuid nie są wolne, ale pod względem wydajności - a grupowanie komplikuje sprawy.

Equals / HashCode -- never Referer to related entities

" jeśli podmiot powiązany (taki jak podmiot dominujący) musi być częścią klucza biznesowego, Dodaj pole, które nie można wstawić, nie można zaktualizować, aby przechowywać identyfikator rodzica (o tej samej nazwie ManytoOne JoinColumn) i użyj tego id w sprawdzaniu równości "

Brzmi jak dobra rada.

Mam nadzieję, że to pomoże!

 61
Author: Thomas W,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-12 07:48:57

Moje 2 grosze do odpowiedzi to:

  1. W odniesieniu do dostępu do pól lub właściwości (poza względami wydajności) oba są legalnie dostępne za pomocą getterów i setterów, więc moja logika modelu może ustawić / pobrać je w ten sam sposób. Różnica pojawia się, gdy dostawca runtime persistence (Hibernate, EclipseLink lub else) musi persist / ustawić jakiś rekord w tabeli a, który ma klucz obcy odnoszący się do jakiejś kolumny w tabeli B. W przypadku Typ dostępu do właściwości, runtime System persistence używa metody my coded setter do przypisania komórce w kolumnie tabeli B nowej wartości. W przypadku typu field access System runtime persistence ustawia bezpośrednio komórkę w kolumnie tabeli B. Ta różnica nie ma znaczenia w kontekście relacji jednokierunkowej, ale konieczne jest użycie mojej własnej kodowanej metody setter (Typ dostępu do właściwości) dla relacji dwukierunkowej, pod warunkiem, że metoda setter jest dobrze zaprojektowana, aby uwzględnić konsekwencja. Spójność jest krytycznym problemem w przypadku relacji dwukierunkowych odnieś się do tego link dla prostego przykładu dla dobrze zaprojektowanego setera.

  2. W odniesieniu do Equals / hashCode: nie jest możliwe użycie automatycznie wygenerowanych metod Equals/hashCode Eclipse dla jednostek biorących udział w dwukierunkowej relacji, w przeciwnym razie będą one miały okrągłe odniesienie, co spowoduje wyjątek stackoverflow. Gdy spróbujesz relacji dwukierunkowej (powiedzmy OneToOne) i automatycznie Wygeneruj Equals() lub hashCode() lub nawet toString () zostaniesz złapany w tym wyjątku stackoverflow.

 12
Author: Sym-Sym,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-01 22:54:10

Entity interface

public interface Entity<I> extends Serializable {

/**
 * @return entity identity
 */
I getId();

/**
 * @return HashCode of entity identity
 */
int identityHashCode();

/**
 * @param other
 *            Other entity
 * @return true if identities of entities are equal
 */
boolean identityEquals(Entity<?> other);
}

Podstawowa implementacja dla wszystkich encji, upraszcza implementacje Equals / Hashcode:

public abstract class AbstractEntity<I> implements Entity<I> {

@Override
public final boolean identityEquals(Entity<?> other) {
    if (getId() == null) {
        return false;
    }
    return getId().equals(other.getId());
}

@Override
public final int identityHashCode() {
    return new HashCodeBuilder().append(this.getId()).toHashCode();
}

@Override
public final int hashCode() {
    return identityHashCode();
}

@Override
public final boolean equals(final Object o) {
    if (this == o) {
        return true;
    }
    if ((o == null) || (getClass() != o.getClass())) {
        return false;
    }

    return identityEquals((Entity<?>) o);
}

@Override
public String toString() {
    return getClass().getSimpleName() + ": " + identity();
    // OR 
    // return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}

Entity pokoju impl:

@Entity
@Table(name = "ROOM")
public class Room extends AbstractEntity<Integer> {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@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;
}

public Integer getId(){
    return id;
}

public Building getBuilding() {
    return building;
}

public String getNumber() {
    return number;
}


public void setCapacity(Integer capacity) {
    this.capacity = capacity;
}

//no setters for number, building nor id
}

Nie widzę sensu porównywania równości podmiotów w oparciu o pola Biznesowe w każdym przypadku podmiotów JPA. Może to być bardziej przypadek, jeśli te encje JPA są traktowane jako Domain-Driven ValueObjects, zamiast Domain-Driven encji (do których te przykłady kodu są przeznaczone).

 7
Author: ahaaman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-03 13:24:54