Mükemmel JPA varlığını oluşturun [kapalı]


422

Bir süredir JPA (Hibernate uygulaması) ile çalışıyorum ve her zaman kendimi AccessType, değişmez özellikler, eşittir / hashCode, ... gibi sorunlarla boğuşuyor buluyorum.
Bu yüzden her bir konu için genel en iyi uygulamayı bulmaya ve bunu kişisel kullanım için yazmaya karar verdim.
Ancak kimse bu konuda yorum yapmak ya da bana nerede yanlış olduğumu söylemek için sakıncası olmaz.

Varlık Sınıfı

  • Serializable uygulamak

    Sebep: Spesifikasyon yapmanız gerektiğini söylüyor, ancak bazı JPA sağlayıcıları bunu zorlamıyor. JPA sağlayıcısı olarak hazırda bekletme bunu zorlamaz, ancak Serializable uygulanmadıysa, ClassCastException ile midesinde derin bir yerde başarısız olabilir.

Kurucular

  • varlığın tüm zorunlu alanlarını içeren bir kurucu oluşturun

    Sebep: Bir kurucu daima oluşturulan örneği aklı başında bir durumda bırakmalıdır.

  • Bu kurucu yanı sıra: bir paket özel varsayılan kurucu var

    Sebep: Varsayılan yapıcı, Hazırda Bekletme öğesinin varlığı başlatmasını gerektirir; private'a izin verilir, ancak çalışma zamanı proxy üretimi ve bayt kodu araçları olmadan verimli veri alımı için paket özel (veya genel) görünürlüğü gerekir.

Alanlar / Özellikler

  • Genel olarak saha erişimini ve gerektiğinde mülk erişimini kullanın

    Sebep: Bu muhtemelen en tartışmalı konudur, çünkü biri veya diğeri için net ve ikna edici argümanlar yoktur (mülk erişimi ve saha erişimi); bununla birlikte, daha net kod, daha iyi kapsülleme ve değişmeyen alanlar için ayarlayıcılar oluşturmaya gerek olmaması nedeniyle saha erişimi genel favori olarak görünmektedir

  • Değiştirilemez alanlar için ayarlayıcıları atla (erişim türü alanı için gerekli değildir)

  • özellikler özel olabilir
    Neden: Korumalı (Hazırda Bekleme) performansı için daha iyi olduğunu duydum ama Web'de bulabildiğim tek şey: Hazırda Bekletme genel, özel ve korumalı erişimci yöntemlerinin yanı sıra genel, özel ve korumalı alanlara doğrudan erişebilir . Seçim size kalmış ve uygulama tasarımınıza uyacak şekilde eşleştirebilirsiniz.

Eşittir / hashCode

  • Bu kimlik yalnızca varlık devam ederse ayarlanmış bir kimliği asla kullanmayın
  • Tercihen: benzersiz bir İş Anahtarı oluşturmak için değişmez değerler kullanın ve bunu eşitliği test etmek için kullanın
  • benzersiz bir İş Anahtarı yoksa , varlık başlatıldığında oluşturulan geçici olmayan bir UUID kullanın ; Daha fazla bilgi için bu harika makaleye bakın .
  • asla ilgili varlıklara başvurmayın (ManyToOne); bu varlığın (üst varlık gibi) İşletme Anahtarının bir parçası olması gerekiyorsa, yalnızca kimlikleri karşılaştırın. Bir proxy'de getId () öğesini çağırmak, özellik erişim türünü kullandığınız sürece varlığın yüklenmesini tetiklemez .

Örnek Varlık

@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

}

Bu listeye eklemek için diğer öneriler hoş geldiniz daha vardır ...

GÜNCELLEME

Bu makaleyi okuduğumdan beri , eq / hC uygulama yöntemimi uyarladım:

  • değişmez basit bir iş anahtarı varsa: bunu kullanın
  • diğer tüm durumlarda: bir uuid kullanın

6
Bu bir soru değil, bir liste talebi ile inceleme isteğidir. Dahası, çok açık uçlu ve belirsiz veya farklı bir ifadeyle: Bir JPA varlığının mükemmel olup olmadığı, ne için kullanılacağına bağlıdır. Bir işletmenin ihtiyaç duyduğu her şeyi bir işletmenin olası tüm kullanımlarında listelemeli miyiz?
meriton

Özür dilediğim kesin bir soru olmadığını biliyorum. Bu gerçekten bir liste için bir istek değil, diğer öneriler bekliyoruz rağmen yorum / açıklamalar için bir istek. Bir JPA varlığının olası kullanımlarını ayrıntılı olarak açıklamaktan çekinmeyin.
Stijn Geukens

Ben de tarlaların olmasını isterdim final(pasörlerin yokluğuna bakarak, siz de yaparsınız).
Sridhar Sarnobat

Denemek zorunda kalacak ama Hibernate hala bu özelliklerin değerlerini ayarlamak gerekir beri final işe yaramaz sanmıyorum.
Stijn Geukens

Nereden notNullgeliyor?
bruno

Yanıtlar:


73

Birkaç kilit noktayı cevaplamaya çalışacağım: bu, birkaç ana uygulama da dahil olmak üzere uzun Hazırda Bekletme / kalıcılık deneyiminden.

Varlık Sınıfı: Serializable uygulamak?

Anahtarların Serializable uygulaması gerekiyor. HttpSession'a gidecek veya RPC / Java EE tarafından kablo üzerinden gönderilecek olan şeylerin Serializable'ı uygulaması gerekir. Diğer şeyler: çok değil. Önemli olana zaman ayırın.

Yapıcılar: Varlığın tüm gerekli alanlarını içeren bir kurucu oluşturulsun mu?

Uygulama mantığı için kurucu (lar), varlığı oluştururken her zaman bilinecek sadece birkaç kritik "yabancı anahtar" veya "tip / tür" alanına sahip olmalıdır. Gerisi setter yöntemlerini çağırarak ayarlanmalıdır - bunun için bunlar.

Yapıcılara çok fazla alan koymaktan kaçının. Yapıcılar uygun olmalı ve nesneye temel akıl vermelidir. İsim, Tip ve / veya Ebeveynler tipik olarak kullanışlıdır.

OTOH, uygulama kuralları (bugün) Müşterinin bir Adrese sahip olmasını gerektiriyorsa, bunu bir ayarlayıcıya bırakın. Bu bir "zayıf kural" örneğidir. Belki gelecek hafta, Ayrıntıları Gir ekranına gitmeden önce bir Müşteri nesnesi mi oluşturmak istiyorsunuz? Kendinizi açmayın, bilinmeyen, eksik veya "kısmen girilmiş" veriler için olasılık bırakmayın.

Yapıcılar: ayrıca, paket özel varsayılan kurucu?

Evet, ancak özel paket yerine 'korumalı' kullanın. Alt sınıflar, gerekli iç kısımlar görünmediğinde gerçek bir acıdır.

Alanlar / Özellikler

Hazırda Bekletme için ve örneğin dışından 'özellik' alan erişimini kullanın. Örnek içinde alanları doğrudan kullanın. Sebep: Hazırda Bekletme için en basit ve en temel yöntem olan standart yansımanın çalışmasına izin verir.

Uygulamaya 'değiştirilemez' alanlara gelince - Hazırda Bekletme'nin bunları yükleyebilmesi gerekir. Uygulama kodlarının istenmeyen erişimini önlemek için bu yöntemleri 'özel' yapmayı deneyebilir ve / veya bunlara bir açıklama ekleyebilirsiniz.

Not: equals () işlevi yazarken, 'other' örneğindeki değerler için getters kullanın! Aksi takdirde, proxy örneklerinde başlatılmamış / boş alanlara basarsınız.

Korumalı (Hazırda Beklet) performans için daha mı iyidir?

Olası olmayan.

Eşittir / HashCode?

Bu, kaydedilmeden önce varlıklarla çalışmakla ilgilidir - bu, dikenli bir konudur. Değişmez değerleri karma / karşılaştırma? Çoğu iş uygulamasında, herhangi bir uygulama yoktur.

Bir müşteri adresi değiştirebilir, işletmesinin adını değiştirebilir, vb. - yaygın değil, ama olur. Veriler doğru girilmediğinde düzeltmelerin de yapılması gerekir.

Normalde değişmez tutulan birkaç şey Ebeveynlik ve belki de Tür / Tür'tür - normalde kullanıcı bunları değiştirmek yerine kaydı yeniden oluşturur. Ancak bunlar varlığı benzersiz bir şekilde tanımlamaz!

Yani, uzun ve kısa, iddia edilen "değişmez" veriler gerçekte değildir. Birincil Anahtar / Kimlik alanları, bu tür garantili kararlılık ve değişmezlik sağlamak için kesin olarak üretilir.

A) "seyrek olarak değiştirilen alanlar" üzerinde karmayı karşılaştırdığınızda veya B) "ile çalışırken" karma veya "karma veri" ile çalışma veya B) ile çalışma kaydedilmemiş veriler ", kimliğini karşılaştırırsanız / karma yaparsanız.

Eşittir / HashCode - benzersiz bir İş Anahtarı yoksa, varlık başlatıldığında oluşturulan geçici olmayan bir UUID kullanın

Evet, gerektiğinde bu iyi bir stratejidir. UUID'lerin ücretsiz, performans açısından akıllı olmadıklarının ve kümelenmenin işleri zorlaştırdığının farkında olun.

Eşittir / HashCode - asla ilgili varlıklara başvurmayın

"İlişkili varlığın (üst varlık gibi) İş Anahtarının bir parçası olması gerekiyorsa, üst kimliği (ManytoOne JoinColumn ile aynı ada sahip) depolamak ve eşitlik denetiminde bu kimliği kullanmak için eklenemez, güncellenemez bir alan ekleyin "

Kulağa iyi bir tavsiye gibi geliyor.

Bu yardımcı olur umarım!


2
Re: yapıcılar, genellikle sadece sıfır arg (yani hiçbiri) görüyorum ve arama kodu bana biraz dağınık görünüyor setters büyük bir uzun listesi var. İhtiyacınıza uygun bir kaç kurucuya sahip olmak, arama kodunu daha özlü yapmakta herhangi bir sorun var mı?
Kasırga

özellikle ctor hakkında tamamen görüşlü. daha güzel kod nedir? objenin aklı başında bir durumunu yaratmak için hangi değerlerin (kombinasyonunun) gerekli olduğunu bilmenize izin veren bir grup farklı veya neyin ayarlanacağına ve hangi sırayla kullanıcının hatalarına eğilimli olduğuna dair hiçbir ipucu vermeyen arg-olmayan bir ctor ?
mohamnag

1
@mohamnag Bağlıdır. Dahili sistem tarafından oluşturulan veriler için kesinlikle geçerli fasulye büyüktür; ancak modern iş uygulamaları çok sayıda kullanıcı veri girişi CRUD veya sihirbaz ekranından oluşur. Kullanıcı tarafından girilen veriler, en azından düzenleme sırasında genellikle kısmen veya zayıf biçimlendirilir. Sıklıkla, daha sonra tamamlanmak üzere eksik bir durumu kaydedebilmek için iş değeri bile vardır - sigorta uygulaması yakalama, müşteri kayıtları vb. Düşünün. Sınırlamaları minimumda tutmak (örneğin, birincil anahtar, işletme anahtarı ve durum) gerçekte daha fazla esneklik sağlar iş durumları.
Thomas W

1
@ThomasW Öncelikle, etki alanı güdümlü tasarıma ve sınıf isimleri için isimler kullanmaya ve yöntemler için tam fiillere dikkat çekiyorum. Bu paradigmada, bahsettiğiniz şey, aslında DTO'lardır ve geçici veri depolama için kullanılması gereken etki alanı varlıkları değildir. Veya alan adınızı yanlış anladınız / yapılandırdınız.
mohamnag

@ThomasW Yeni başlayan olduğumu söylemeye çalıştığınız tüm cümleleri filtrelediğimde, yorumunuzda kullanıcı girişi dışında herhangi bir bilgi kalmadı. Bu bölüm, daha önce söylediğim gibi, doğrudan tüzel kişide değil, DTO'larda yapılacaktır. 50 yıl sonra Fowler gibi DDD'nin ardındaki büyük aklın% 5'i olabileceğinizi konuşalım! alkış: D
mohamnag

144

JPA 2.0 Şartname belirtmektedir:

  • Varlık sınıfı, arg-no yapıcısına sahip olmamalıdır. Başka kurucuları da olabilir. Arg olmayan yapıcı herkese açık veya korumalı olmalıdır.
  • Varlık sınıfı üst düzey bir sınıf olmalıdır. Bir numaralandırma veya arayüz varlık olarak tanımlanmamalıdır.
  • Varlık sınıfı nihai olmamalıdır. Varlık sınıfının hiçbir yöntemi veya kalıcı örnek değişkeni kesin olamaz.
  • Bir varlık örneği, ayrı bir nesne olarak değere göre geçirilecekse (örn., Uzak bir arabirim yoluyla), varlık sınıfı, Serializable arabirimini uygulamalıdır.
  • Hem soyut hem de somut sınıflar varlık olabilir. Varlıklar varlık sınıflarını olduğu kadar varlık sınıflarını da genişletebilir ve varlık sınıfları varlık sınıflarını genişletebilir.

Spesifikasyon, varlıklar için eşittir ve hashCode yöntemlerinin uygulanmasıyla ilgili hiçbir gereksinim içermez, yalnızca bildiğim kadarıyla birincil anahtar sınıfları ve harita anahtarları için.


13
Doğru, eşittir, hashcode, ... bir JPA şartı değildir, ancak elbette tavsiye edilir ve iyi uygulama olarak kabul edilir.
Stijn Geukens

6
@TheStijn Ayrılmış varlıkları eşitlik için karşılaştırmayı planlamadığınız sürece, bu muhtemelen gereksizdir. Varlık yöneticisinin, belirli bir varlığın aynı örneğini her istediğinizde iade etmesi garanti edilir. Böylece, anladığım kadarıyla, yönetilen varlıklar için kimlik karşılaştırmaları yapabilirsin. Bunun iyi bir uygulama olduğunu düşündüğünüz senaryolar hakkında biraz daha bilgi verebilir misiniz?
Edwin Dalorzo

2
Her zaman eşit / hashCode doğru bir uygulama için çalışıyoruz. JPA için gerekli değildir, ancak varlıklar veya Setlere eklendiğinde iyi bir uygulama olduğunu düşünüyorum. Eşitlikleri yalnızca Kümelere varlık ekleneceği zaman uygulamaya karar verebilirsiniz, ancak her zaman önceden biliyor musunuz?
Stijn Geukens

10
@TheStijn JPA sağlayıcısı, herhangi bir zamanda bağlamda belirli bir varlığın yalnızca bir örneğinin olmasını sağlar; bu nedenle, yalnızca yönetilen varlıkları kullanmanız koşuluyla, kümeleriniz bile eşittir / hascode uygulamadan güvenlidir. Varlıklar için bu yöntemleri uygulamak zor değildir, örneğin konu hakkındaki Hazırda Bekletme Makalesine bir göz atın . Demek istediğim, sadece yönetilen kuruluşlarla çalışıyorsanız, onlarsız daha iyi olursunuz, aksi takdirde çok dikkatli bir uygulama sağlarsınız.
Edwin Dalorzo

2
@ TheStijn Bu iyi karışık senaryodur. Başlangıçta önerdiğiniz gibi eq / hC uygulama ihtiyacını haklı çıkarır, çünkü varlıklar kalıcılık katmanının güvenliğinden vazgeçtikten sonra artık JPA standardının uyguladığı kurallara güvenemezsiniz. Bizim durumumuzda, DTO deseni en başından beri mimari olarak uygulandı. Tasarım sayesinde, kalıcılık API'mız, iş nesneleriyle etkileşimde bulunmak için herkese açık bir yol sunmaz, yalnızca DTO'ları kullanarak kalıcılık katmanımızla etkileşimde bulunmak için bir API sunar.
Edwin Dalorzo

13

Buradaki cevaplara 2 sent ekim:

  1. Alan veya Mülk erişimine (performans değerlendirmelerinden uzak) atıfta bulunarak hem alıcılar hem de ayarlayıcılar aracılığıyla yasal olarak erişilebilir, bu nedenle model mantığım bunları aynı şekilde ayarlayabilir / alabilir. Kalıcı çalışma zamanı sağlayıcısının (Hazırda Beklet, EclipseLink veya başka) Tablo B'de, Tablo B'deki bir sütuna başvuran yabancı bir anahtarı olan bazı kayıtları sürdürmesi / ayarlaması gerektiğinde fark ortaya çıkar. Bir Özellik erişim türü durumunda, kalıcılık çalışma zamanı sistemi Tablo B sütununda hücreye yeni bir değer atamak için benim kodlanmış ayarlayıcı yöntemini kullanır. Alan erişim türünde, kalıcı çalışma zamanı sistemi hücreyi doğrudan Tablo B sütununa ayarlar. Bu fark tek yönlü bir ilişki bağlamında önemli değildir, yine de, ayarlayıcı yönteminin tutarlılığı hesaba katmak için iyi tasarlanmış olması koşuluyla, iki yönlü bir ilişki için kendi kodlu ayarlayıcı yöntemimi (Özellik erişim türü) kullanmak şarttır. Tutarlılık, iki yönlü ilişkiler için kritik bir konudur.iyi tasarlanmış bir setter için basit bir örnek link .

  2. Eşittir / hashCode referansı ile: Çift yönlü bir ilişkiye katılan varlıklar için Eclipse otomatik olarak oluşturulan Eşittir / hashCode yöntemlerini kullanmak mümkün değildir, aksi takdirde yığın akışı İstisnası ile sonuçlanan dairesel bir referansa sahip olurlar. Çift yönlü bir ilişki denediğinizde (örneğin, OneToOne) ve Equals () veya hashCode () veya hatta toString () 'i otomatik olarak oluşturduğunuzda, bu stackoverflow istisnasına takılırsınız.


9

Varlık arayüzü

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);
}

Tüm Varlıklar için temel uygulama, Eşittir / Hashcode uygulamalarını basitleştirir:

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);
}
}

Oda Varlığı dahil:

@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
}

JPA Kuruluşlarının her örneğinde, işletme alanlarına göre varlıkların eşitliğini karşılaştırma noktası görmüyorum. Bu JPA varlıkları, Etki Alanına Dayalı Varlıklar yerine (bu kod örneklerine yönelik olan) Etki Alanına Dayalı ValueObjects olarak düşünülürse, bu durum daha fazla olabilir.


4
Her ne kadar kazan plakası kodunu çıkarmak için bir üst varlık sınıfı kullanmak iyi bir yaklaşım olsa da, eşittir yönteminizde DB tanımlı kimliği kullanmak iyi bir fikir değildir. Sizin durumunuzda 2 yeni varlığı karşılaştırmak NPE bile atacaktır. Güvenli kılsanız bile, 2 yeni varlık kalıcı olana kadar her zaman eşit olacaktır. Denklem / hC değişmez olmalıdır.
Stijn Geukens

2
Eşittir (), DB id null olup olmadığını kontrol ederken NPE atmaz & DB id null olursa, eşitlik yanlış olur.
ahaaman

3
Gerçekten, kod null güvenli olduğunu nasıl özledim görmüyorum. Ancak kimliği kullanan IMO hala kötü bir uygulamadır. Argümanlar: onjava.com/pub/a/onjava/2006/09/13/…
Stijn

Vaughn Vernon'un 'DDD Uygulanması' kitabında, "erken PK üretimi" kullanırsanız eşitler için id kullanabileceğiniz öne sürülür (önce bir kimlik oluşturun ve bunu veritabanının oluşturulmasına izin vermek yerine varlığın yapıcısına iletin
Varlığı sürdürdüğünüzde

veya kalıcı olmayan varlıkları eşit karşılaştırmayı planlamıyorsanız? Neden ...
Enerccio
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.