java - إنشاء كيان JPA مثالي



hibernate equals (3)

بعد الإعراب عن إعجابي بقائمة Stijns شبه الشاملة ، هناك تصويبتان هما:

  1. بالإشارة إلى الوصول إلى المجال أو الملكية (بعيدًا عن اعتبارات الأداء) ، يتم الوصول إلى كلاهما بشكل شرعي عن طريق المستفيدين والمستأجرين ، وبالتالي ، يمكن لمنطقتي النموذجية تعيين / الحصول عليها بنفس الطريقة. يأتي الفرق للعب عندما يحتاج مزود وقت التشغيل المستمر (Hibernate أو EclipseLink أو غير ذلك) إلى الاستمرار / تعيين سجل ما في الجدول A الذي يحتوي على مفتاح خارجي يشير إلى بعض الأعمدة في الجدول B. في حالة نوع الوصول إلى الخاصية ، فإن المثابرة يستخدم نظام وقت التشغيل طريقة تعيين الشفرة الخاصة بي لتعيين الخلية في العمود B الجدول قيمة جديدة. في حالة نوع الوصول إلى المجال ، يقوم نظام وقت التشغيل المستمر بتعيين الخلية في العمود "ب" مباشرة. هذا الاختلاف ليس له أهمية في سياق علاقة أحادية الاتجاه ، ولكن يجب أن نستخدم طريقة الضبط المشفرة الخاصة بي (نوع الوصول إلى الخاصية) لعلاقة ثنائية الاتجاه بشرط أن تكون طريقة الضبط مصممة بشكل جيد لمراعاة الاتساق . الاتساق هو مسألة حاسمة بالنسبة للعلاقات ثنائية الاتجاه الرجوع إلى هذا link لمثال بسيط لواضع مصممة تصميما جيدا.

  2. بالإشارة إلى Equals / hashCode: من المستحيل استخدام أساليب Equals / hashCode التي تم إنشاؤها تلقائيًا في Eclipse للكيانات المشاركة في علاقة ثنائية الاتجاه ، وإلا سيكون لها مرجع دائري ينتج عنه استثناء stackoverflow. بمجرد تجربة علاقة ثنائية الاتجاه (قل OneToOne) وإنشاء تلقائي Equals () أو hashCode () أو حتى toString () ، فسوف يتم اكتشافك في هذا الاستثناء stackoverflow.

https://code.i-harness.com

لقد كنت أعمل مع JPA (تنفيذ Hibernate) لبعض الوقت الآن وفي كل مرة أحتاج إلى إنشاء كيانات أجد نفسي أواجه مشاكل مع AccessType، خصائص غير قابلة للتغيير، equals / hashCode، ....
لذلك قررت أن أحاول العثور على أفضل الممارسات العامة لكل قضية وأكتبها للاستخدام الشخصي.
لا مانع من أن يعلق أحد على ذلك أو يخبرني أين أخطئ.

فئة الكيان

  • تنفيذ Serializable

    السبب: تقول المواصفة أنه يجب عليك ، لكن بعض مزودي JPA لا يفرضون ذلك. السبات كموفر JPA لا يفرض هذا ، ولكنه قد يفشل في مكان عميق في معدته مع ClassCastException ، إذا لم يتم تنفيذ Serializable.

الصانعين

  • إنشاء منشئ مع جميع الحقول المطلوبة للكيان

    السبب: يجب أن يترك المنشئ دومًا المثيل الذي تم إنشاؤه في حالة معقولة.

  • إلى جانب هذا المنشئ: لديك حزمة منشئ افتراضي خاص

    السبب: مطلوب منشئ افتراضي لجعل إسبات تهيئة الكيان ؛ مسموح بخاصية ولكن مطلوب رؤية حزمة خاصة (أو عامة) لتوليد الوكيل في وقت التشغيل وكفاءة استرجاع البيانات دون الأجهزة bytecode.

الحقول / عقارات

  • استخدام الوصول الميداني بشكل عام والوصول إلى الملكية عند الحاجة

    السبب: ربما تكون هذه هي القضية الأكثر إثارة للجدل حيث لا توجد حجج واضحة ومقنعة لواحد أو آخر (الوصول إلى خاصية مقابل وصول ميداني) ؛ ومع ذلك ، يبدو أن الوصول الميداني مفضل بشكل عام نظرًا لوجود كود أكثر وضوحًا وتغليفًا أفضل ولا حاجة لإنشاء مستوطنين للحقول القابلة للتغيير

  • حذف المستقرين للحقول الثابتة (غير مطلوب في حقل نوع الوصول)

  • خصائص قد تكون خاصة
    السبب: سمعت ذات مرة أن الحماية محمية لأداء (Hibernate) ولكن كل ما يمكنني العثور عليه على الويب هو: يمكن لـ Hibernate الوصول إلى أساليب الملاءمة العامة والخاصة والمحمية ، بالإضافة إلى الحقول العامة والخاصة والمحمية مباشرة. الاختيار متروك لك ويمكنك مطابقة ذلك لتتناسب مع تصميم التطبيق الخاص بك.

يساوي / شفرة التجزئة

  • لا تستخدم مطلقًا تم إنشاؤه مطلقًا إذا تم تعيين هذا المعرّف فقط عند الاستمرار في الكيان
  • حسب الأفضلية: استخدم القيم الثابتة لتكوين مفتاح عمل فريد واستخدم ذلك لاختبار المساواة
  • إذا كان مفتاح العمل الفريد غير متاح ، استخدم UUID غير عابر والذي يتم إنشاؤه عند تهيئة الكيان ؛ انظر هذا المقال الرائع لمزيد من المعلومات.
  • لا تشير أبداً إلى الكيانات ذات الصلة (ManyToOne) ؛ إذا كان هذا الكيان (مثل الكيان الرئيسي) يحتاج إلى أن يكون جزءًا من مفتاح العمل ، فقم بمقارنة المعرّف فقط. لن يؤدي استدعاء getId () على الخادم الوكيل إلى تحميل الكيان ، طالما أنك تستخدم نوع الوصول إلى الموقع .

كيان المثال

@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

}

اقتراحات أخرى للإضافة إلى هذه القائمة أكثر من موضع ترحيب ...

تحديث

منذ قراءة هذا المقال قمت بتكييف طريقي لتطبيق eq / hC:

  • إذا كان مفتاح العمل البسيط غير قابل للتغيير متاحًا: استخدم ذلك
  • في جميع الحالات الأخرى: استخدم uuid

تنص مواصفات JPA 2.0 على ما يلي:

  • يجب أن يكون لفئة الكيان مُنشئ بدون أرج. قد يكون لها منشئات أخرى كذلك. يجب أن يكون مُنشئ no-arg عامًا أو محميًا.
  • يجب أن تكون فئة الكيان من الفئة العليا. يجب عدم تخصيص التعداد أو الواجهة ككيان.
  • يجب ألا تكون فئة الكيان نهائية. لا توجد طرق أو متغيرات حالة ثابتة لفئة الكيان قد تكون نهائية.
  • إذا كان سيتم تمرير مثيل كيان حسب القيمة ككائن منفصل (على سبيل المثال ، من خلال واجهة بعيدة) ، يجب أن تقوم فئة الكيان بتطبيق واجهة Serializable.
  • يمكن أن تكون كلتا الفئتين التجريبيتين والخرسانة كيانات. يجوز للكيانات أن تمدد فصول غير الكيانات وكذلك فئات الكيانات ، وقد تعمل الطبقات غير الكيانية على توسيع فئات الكيانات.

لا تحتوي المواصفة على أي متطلبات حول تنفيذ مساويتي equal و hashCode للكيانات ، فقط لفئات المفتاح الأساسي ومفاتيح الخريطة بقدر ما أعرف.


واجهة الكيان

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

التنفيذ الأساسي لجميع الكيانات ، يبسّط عمليات 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
@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. قد يكون هذا أكثر من حالة إذا تم اعتبار هذه الكيانات JPA على أنها قيم ValueObjects مدفوعة بالمجال ، بدلاً من الكيانات التي تحركها النطاقات (والتي تستخدم هذه الأمثلة البرمجية).





equals