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




hibernate equals (5)

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

فئة الكيان: تنفيذ Serializable؟

مفاتيح تحتاج إلى تنفيذ Serializable. الأشياء التي سوف تذهب في HttpSession ، أو يتم إرسالها عبر السلك من قبل RPC / Java EE ، تحتاج إلى تنفيذ Serializable. أشياء أخرى: ليس كثيرا. قضاء وقتك على ما هو مهم.

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

يجب أن يكون للمُنشئ (مُنشِئات) لمنطق التطبيق ، عدد قليل من الحقول "المفتاح الخارجي" أو "النوع / النوع" الهامة التي ستُعرف دائمًا عند إنشاء الكيان. يجب تعيين الباقي عن طريق استدعاء أساليب الضبط - وهذا ما هم.

تجنب وضع العديد من الحقول في المنشئات. يجب أن يكون المهندسون ملائمين ويعطون العقلانية الأساسية للجسم. الاسم والنوع و / أو الآباء جميعهم عادة ما تكون مفيدة.

OTOH إذا كانت قواعد الطلب (اليوم) تتطلب أن يكون للعميل عنوان ، اترك ذلك للضوابط. هذا مثال على "قاعدة ضعيفة". ربما في الأسبوع المقبل ، تريد إنشاء كائن عميل قبل الانتقال إلى شاشة "إدخال التفاصيل"؟ لا تحمّل نفسك ، اترك إمكانية الاطلاع على بيانات غير معروفة أو غير كاملة أو "تم إدخالها جزئيًا".

المبادرين: أيضا ، حزمة منشئ الافتراضية الخاصة؟

نعم ، ولكن استخدم كلمة "محمية" بدلاً من الحزمة الخاصة. الأشياء subclassing هي ألم حقيقي عندما لا تكون مرئية الداخلية اللازمة.

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

استخدم حق الوصول إلى حقل "الخاصية" للإسبات ، ومن خارج المثيل. داخل المثال ، استخدم الحقول مباشرة. السبب: يسمح بانعكاس معياري ، أبسط وأبسط طريقة لسبت ، للعمل.

أما بالنسبة للحقول "غير قابلة للتغيير" للتطبيق - لا يزال الإسبات بحاجة إلى تحميلها. يمكنك محاولة جعل هذه الطرق "خاصة" ، و / أو وضع تعليق عليها ، لمنع رمز التطبيق من الوصول غير المرغوب فيه.

ملاحظة: عند كتابة دالة equals () ، استخدم getters للقيم على مثيل 'other'! وإلا ، فستضرب حقولًا غير مهيأة / فارغة على مثيلات الوكيل.

محمية أفضل لأداء (السبات)؟

من غير المرجح.

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

هذا مهم للعمل مع الكيانات ، قبل أن يتم حفظها - وهي قضية شائكة. تجزئة / المقارنة على القيم الثابتة؟ في معظم تطبيقات الأعمال ، لا توجد أي.

يمكن للعميل تغيير العنوان وتغيير اسم شركته وما إلى ذلك - ليس شائعًا ، ولكنه يحدث. كما يجب أن تكون التصحيحات ممكنة ، عندما لا يتم إدخال البيانات بشكل صحيح.

الأشياء القليلة التي عادة ما تكون غير قابلة للتغيير ، هي الأبوة والأمومة وربما النوع / النوع - عادةً ما يقوم المستخدم بإعادة إنشاء السجل ، بدلاً من تغييره. لكن هذه لا تحدد الكيان بشكل فريد!

إذاً ، طويلة وقصيرة ، فإن البيانات "غير القابلة للتغيير" المزعومة ليست فعلاً. يتم إنشاء حقول المفتاح / الهوية الأساسية لغرض محدد ، من حيث توفير الاستقرار والثبات المضمون.

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

Equals / HashCode - إذا كان مفتاح العمل الفريد غير متاح ، فاستخدم UUID غير عابر والذي يتم إنشاؤه عند تهيئة الكيان

نعم ، هذه إستراتيجية جيدة عند الحاجة. كن على علم أن UUIDs ليست حرة ، على الرغم من الأداء-الحكمة وتجميع يعقد الأمور.

يساوي / HashCode - لا تشير أبدا إلى الكيانات ذات الصلة

"إذا كان الكيان المرتبط (مثل كيان أساسي) بحاجة إلى أن يكون جزءًا من مفتاح العمل ، فقم بإضافة حقل غير قابل للإدراج ، غير قابل للتحديث لتخزين المعرف الأصلي (بنفس الاسم مثل JointoOne JoinColumn) واستخدم هذا المعرف في التحقق من المساواة "

يبدو وكأنه نصيحة جيدة.

أتمنى أن يساعدك هذا!

لقد كنت أعمل مع 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 للكيانات ، فقط لفئات المفتاح الأساسي ومفاتيح الخريطة بقدر ما أعرف.


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

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

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


واجهة الكيان

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


السبات يولد الأعمدة بالترتيب الأبجدي . وفقا لهذا المنصب يتم إعطاء السبب على النحو التالي:

يتم فرزها لتأمين الطلب الحتمية عبر المجموعات.

لا يمكننا الاعتماد على جهاز vm لإرجاع الأساليب بنفس الترتيب في كل مرة لذلك كان علينا القيام بشيء ما.

على ما يبدو كان في ترتيب حدوث ولكن هذا تغير بين 3.2.0 GA و 3.2.1 GA.

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

لا يوجد حل لهذا بخلاف حل تسمية الأعمدة بطريقة تخرج بالترتيب الصحيح (لا ، أنا لا أمزح).







java hibernate jpa equals