สร้างเอนทิตี JPA ที่สมบูรณ์แบบ [ปิด]


422

ฉันได้ทำงานกับ JPA (Implementation Hibernate) มาระยะหนึ่งและทุกครั้งที่ฉันต้องสร้างเอนทิตีฉันพบว่าตัวเองกำลังดิ้นรนกับปัญหาเช่น AccessType, คุณสมบัติที่ไม่เปลี่ยนรูปแบบ, เท่ากับ / hashCode, ...
ดังนั้นฉันจึงตัดสินใจลองและหาแนวทางปฏิบัติที่ดีที่สุดโดยทั่วไปสำหรับแต่ละประเด็นแล้วจดบันทึกนี้ไว้ใช้ส่วนตัว
ฉันจะไม่รังเกียจอย่างไรก็ตามสำหรับทุกคนที่จะแสดงความคิดเห็นหรือบอกฉันว่าฉันผิด

คลาสเอนทิตี

  • ใช้ Serializable

    เหตุผล: ข้อกำหนดระบุว่าคุณต้องทำ แต่ผู้ให้บริการ JPA บางรายไม่บังคับใช้ Hibernate ในฐานะผู้ให้บริการ JPA ไม่ได้บังคับใช้สิ่งนี้ แต่สามารถล้มเหลวได้บางส่วนในท้องของมันด้วย ClassCastException หาก Serializable ไม่ได้ถูกนำมาใช้

ก่อสร้าง

  • สร้างตัวสร้างที่มีฟิลด์ที่จำเป็นทั้งหมดของเอนทิตี

    เหตุผล: คอนสตรัคควรปล่อยให้อินสแตนซ์ที่สร้างในสถานะที่มีเหตุผล

  • นอกเหนือจากตัวสร้างนี้: มีตัวสร้างค่าเริ่มต้นส่วนตัวของแพคเกจ

    เหตุผล: ตัวสร้างเริ่มต้นจะต้องมีการไฮเบอร์เนตเริ่มต้นเอนทิตี; ส่วนตัวได้รับอนุญาต แต่จำเป็นต้องมีการเปิดเผยแพคเกจส่วนตัว (หรือสาธารณะ) สำหรับการสร้างรันไทม์พร็อกซีและการดึงข้อมูลที่มีประสิทธิภาพโดยไม่ต้องใช้เครื่องมือ bytecode

ฟิลด์ / อสังหาริมทรัพย์

  • ใช้การเข้าถึงฟิลด์โดยทั่วไปและการเข้าถึงคุณสมบัติเมื่อจำเป็น

    เหตุผล: นี่อาจเป็นปัญหาที่ถกเถียงกันมากที่สุดเนื่องจากไม่มีข้อโต้แย้งที่ชัดเจนและน่าเชื่อถือสำหรับข้อใดข้อหนึ่งหรือข้ออื่น (การเข้าถึงคุณสมบัติเทียบกับการเข้าถึงฟิลด์) อย่างไรก็ตามการเข้าถึงฟิลด์ดูเหมือนจะเป็นรายการโปรดทั่วไปเนื่องจากรหัสที่ชัดเจนกว่าการห่อหุ้มที่ดีขึ้นและไม่จำเป็นต้องสร้างตัวตั้งค่าสำหรับฟิลด์ที่ไม่เปลี่ยนรูป

  • งด setters สำหรับฟิลด์ที่ไม่เปลี่ยนรูป (ไม่จำเป็นสำหรับฟิลด์ประเภทการเข้าถึง)

  • คุณสมบัติอาจเป็น
    เหตุผลส่วนตัว: ฉันเคยได้ยินว่าการป้องกันดีกว่าสำหรับประสิทธิภาพ (ไฮเบอร์เนต) แต่สิ่งที่ฉันพบได้บนเว็บคือ: ไฮเบอร์เนตสามารถเข้าถึงวิธีการเข้าถึงสาธารณะส่วนตัวและป้องกันรวมถึงฟิลด์สาธารณะโดยตรงและได้รับการป้องกันโดยตรง . ตัวเลือกขึ้นอยู่กับคุณและคุณสามารถจับคู่ให้เหมาะกับการออกแบบแอปพลิเคชันของคุณ

เท่ากับ / hashCode

  • อย่าใช้รหัสที่สร้างขึ้นหากมีการตั้งค่ารหัสนี้เฉพาะเมื่อยังคงมีเอนทิตีอยู่
  • ตามค่ากำหนด: ใช้ค่าที่ไม่เปลี่ยนรูปแบบเพื่อสร้างรหัสธุรกิจที่ไม่ซ้ำกันและใช้สิ่งนี้เพื่อทดสอบความเท่าเทียมกัน
  • หากไม่มีรหัสธุรกิจที่ไม่ซ้ำให้ใช้UUIDที่ไม่ได้ชั่วคราวซึ่งสร้างขึ้นเมื่อมีการเริ่มต้นเอนทิตี ดูบทความที่ดีนี้สำหรับข้อมูลเพิ่มเติม
  • อย่าอ้างถึงหน่วยงานที่เกี่ยวข้อง (ManyToOne) หากเอนทิตีนี้ (เช่นเอนทิตีหลัก) จำเป็นต้องเป็นส่วนหนึ่งของคีย์ธุรกิจให้เปรียบเทียบ ID เท่านั้น โทร 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

}

คำแนะนำอื่น ๆ ที่จะเพิ่มในรายการนี้เป็นมากกว่ายินดีต้อนรับ ...

UPDATE

ตั้งแต่อ่านบทความนี้ฉันได้ปรับวิธีการใช้ eq / hC:

  • ถ้ามีคีย์ธุรกิจแบบง่ายที่ไม่เปลี่ยนรูปให้ใช้: ใช้มัน
  • ในกรณีอื่น ๆ ทั้งหมด: ใช้ uuid

6
นี่ไม่ใช่คำถามมันเป็นคำขอตรวจสอบพร้อมกับขอรายการ ยิ่งไปกว่านั้นมันยังเปิดกว้างและคลุมเครือหรือแตกต่างออกไป: เอนทิตีของ JPA นั้นสมบูรณ์แบบหรือไม่นั้นขึ้นอยู่กับสิ่งที่จะใช้ เราควรจะเขียนรายการทุกสิ่งที่เอนทิตี้อาจต้องการในการใช้เอนทิตีที่เป็นไปได้ทั้งหมดหรือไม่?
Meriton

ฉันรู้ว่ามันไม่ใช่คำถามที่ชัดเจนซึ่งฉันขอโทษ มันไม่ได้เป็นคำขอสำหรับรายการจริงๆ แต่เป็นคำขอสำหรับความคิดเห็น / ข้อสังเกตถึงแม้ว่าคำแนะนำอื่น ๆ ยินดีต้อนรับ รู้สึกอิสระที่จะอธิบายอย่างละเอียดเกี่ยวกับการใช้งานเอนทิตี JPA ที่เป็นไปได้
Stijn Geukens

ฉันยังต้องการให้สาขาต่าง ๆfinal(ตัดสินโดยการงดเซทเทอร์ของคุณฉันจะเดาว่าคุณก็ทำเช่นเดียวกัน)
Sridhar Sarnobat

จะต้องลองใช้ แต่ฉันไม่คิดว่าสุดท้ายจะใช้งานได้เนื่องจาก Hibernate ยังคงต้องสามารถกำหนดค่าในคุณสมบัติเหล่านั้นได้
Stijn Geukens

ในกรณีที่ไม่notNullมาจากไหน?
bruno

คำตอบ:


73

ฉันจะพยายามตอบประเด็นสำคัญหลายข้อ: มาจากประสบการณ์ไฮเบอร์เนต / การคงอยู่ที่ยาวนานรวมถึงแอปพลิเคชันหลัก ๆ

หน่วยงานระดับ: ใช้ Serializable?

คีย์จำเป็นต้องใช้ Serializable สิ่งที่จะไปใน HttpSession หรือถูกส่งผ่านสายโดย RPC / Java EE จำเป็นต้องใช้ Serializable สิ่งอื่น ๆ : ไม่มาก ใช้เวลาของคุณกับสิ่งที่สำคัญ

ตัวสร้าง: สร้างตัวสร้างด้วยฟิลด์ที่ต้องการทั้งหมดของเอนทิตีหรือไม่

ตัวสร้างสำหรับแอปพลิเคชันลอจิคัลควรมีฟิลด์ "foreign key" หรือ "type / ชนิด" ที่สำคัญเพียงเล็กน้อยซึ่งจะรู้ได้เสมอเมื่อสร้างเอนทิตี ส่วนที่เหลือควรตั้งค่าโดยเรียกเมธอด setter - นั่นคือสิ่งที่พวกเขากำลังทำ

หลีกเลี่ยงการใส่ฟิลด์มากเกินไปลงในตัวสร้าง ตัวสร้างควรจะสะดวกและให้สติพื้นฐานกับวัตถุ ชื่อประเภทและ / หรือผู้ปกครองล้วนแล้วแต่มีประโยชน์

OTOH หากกฎการใช้งาน (วันนี้) กำหนดให้ลูกค้าต้องมีที่อยู่ให้ปล่อยไปที่ setter นั่นคือตัวอย่างของ "กฎที่อ่อนแอ" อาจเป็นสัปดาห์หน้าคุณต้องการสร้างวัตถุลูกค้าก่อนที่จะไปที่หน้าจอป้อนรายละเอียดหรือไม่ อย่าเดินทางด้วยตัวเองปล่อยให้เป็นไปได้สำหรับข้อมูลที่ไม่รู้จักไม่สมบูรณ์หรือ "ป้อนบางส่วน"

คอนสตรัคเตอร์: รวมถึงคอนสตรัคเตอร์เริ่มต้นส่วนตัวหรือไม่

ใช่ แต่ใช้ 'ป้องกัน' แทนแพ็คเกจส่วนตัว เรื่องการจัดคลาสเป็นความเจ็บปวดที่แท้จริงเมื่อมองไม่เห็นสิ่งที่อยู่ภายใน

ฟิลด์ / อสังหาริมทรัพย์

ใช้การเข้าถึงฟิลด์ 'คุณสมบัติ' สำหรับไฮเบอร์เนตและจากภายนอกอินสแตนซ์ ภายในอินสแตนซ์ให้ใช้ฟิลด์โดยตรง เหตุผล: อนุญาตการสะท้อนกลับมาตรฐานวิธีที่ง่ายที่สุด & ขั้นพื้นฐานที่สุดสำหรับไฮเบอร์เนตสามารถทำงานได้

สำหรับฟิลด์ 'ไม่เปลี่ยนรูป' ของแอปพลิเคชัน - ไฮเบอร์เนตยังคงต้องสามารถโหลดได้ คุณสามารถลองทำให้วิธีการเหล่านี้เป็นแบบ 'ส่วนตัว' และ / หรือใส่คำอธิบายประกอบไว้เพื่อป้องกันรหัสแอปพลิเคชันที่ทำให้เกิดการเข้าถึงที่ไม่พึงประสงค์

หมายเหตุ: เมื่อเขียนฟังก์ชัน equals () ให้ใช้ getters สำหรับค่าในอินสแตนซ์ 'other'! มิฉะนั้นคุณจะเข้าสู่ฟิลด์ที่ไม่ได้กำหนดค่าเริ่มต้น / ว่างเปล่าในอินสแตนซ์ของพร็อกซี

ได้รับการป้องกันดีกว่าสำหรับประสิทธิภาพ (ไฮเบอร์เนต) หรือไม่

ไม่แน่

เท่ากับ / hashCode?

สิ่งนี้เกี่ยวข้องกับการทำงานกับเอนทิตีก่อนที่จะได้รับการบันทึกซึ่งเป็นปัญหาที่ยุ่งยาก Hashing / เปรียบเทียบกับค่าที่ไม่เปลี่ยนรูปแบบ? ในแอปพลิเคชั่นธุรกิจส่วนใหญ่ไม่มีอะไรเลย

ลูกค้าสามารถเปลี่ยนที่อยู่เปลี่ยนชื่อธุรกิจ ฯลฯ ได้ - ไม่ใช่เรื่องปกติ แต่เกิดขึ้น ต้องมีการแก้ไขเมื่อต้องป้อนข้อมูลไม่ถูกต้อง

บางสิ่งที่ปกติแล้วจะไม่เปลี่ยนรูปแบบคือการเลี้ยงดูและอาจเป็นประเภท / ชนิด - โดยปกติผู้ใช้จะสร้างบันทึกใหม่แทนที่จะเปลี่ยนสิ่งเหล่านี้ แต่สิ่งเหล่านี้ไม่ได้ระบุเอกลักษณ์เฉพาะ!

ดังนั้นข้อมูลที่ "ไม่เปลี่ยนรูป" ที่อ้างว่ายาวและสั้นนั้นไม่ใช่ข้อมูลจริง คีย์หลัก / ID ฟิลด์ถูกสร้างขึ้นเพื่อจุดประสงค์ที่แม่นยำในการรับประกันความมั่นคงและความไม่แน่นอน

คุณต้องวางแผนและพิจารณาความต้องการของคุณสำหรับการเปรียบเทียบ & การแฮช & คำขอการประมวลผลขั้นตอนเมื่อ A) ทำงานกับ "ข้อมูลที่ถูกเปลี่ยนแปลง / ถูกผูกไว้" จาก UI หากคุณเปรียบเทียบ / แฮชบน "ฟิลด์ที่เปลี่ยนแปลงไม่บ่อย" หรือ B) ทำงานด้วย " ข้อมูลที่ไม่ได้บันทึก "หากคุณเปรียบเทียบ / hash กับ ID

เท่ากับ / HashCode - หากไม่มีคีย์ธุรกิจที่ไม่ซ้ำให้ใช้ UUID ที่ไม่ใช่แบบชั่วคราวซึ่งสร้างขึ้นเมื่อเริ่มต้นเอนทิตี

ใช่นี่เป็นกลยุทธ์ที่ดีเมื่อจำเป็น พึงระวังว่า UUID ไม่ได้ฟรีประสิทธิภาพที่ฉลาดและการรวมกลุ่มทำให้สิ่งต่าง ๆ ซับซ้อนขึ้น

เท่ากับ / HashCode - อย่าอ้างถึงเอนทิตีที่เกี่ยวข้อง

"ถ้าเอนทิตีที่เกี่ยวข้อง (เช่นเอนทิตีพาเรนต์) จำเป็นต้องเป็นส่วนหนึ่งของคีย์ธุรกิจให้เพิ่มฟิลด์ที่ไม่สามารถแทรกได้และไม่สามารถอัปเดตได้เพื่อจัดเก็บ ID หลัก (ที่มีชื่อเดียวกันกับ ManytoOne JoinColumn) และใช้รหัสนี้ในการตรวจสอบความเท่าเทียมกัน "

ฟังดูเหมือนคำแนะนำที่ดี

หวังว่านี่จะช่วยได้!


2
Re: constructors ฉันมักจะเห็นศูนย์หาเรื่องเท่านั้น (เช่นไม่มี) และรหัสการโทรมีรายการที่ยอดเยี่ยมของ setters ซึ่งดูเหมือนจะยุ่งเล็กน้อยสำหรับฉัน มีปัญหาจริงๆหรือไม่กับการมีคอนสตรัคเตอร์สองตัวที่เหมาะกับความต้องการของคุณทำให้รหัสการโทรสั้นลงมากขึ้น?
พายุเฮอริเคน

มีความเห็นทั้งหมดโดยเฉพาะเกี่ยวกับ ctor รหัสที่สวยงามคืออะไร เครือข่ายที่แตกต่างกันซึ่งจะช่วยให้คุณทราบว่าต้องใช้ค่าใด (รวมกัน) เพื่อสร้างสถานะที่มีสติของ obj หรือ ctor แบบไม่หาเรื่องซึ่งไม่ให้เงื่อนงำอะไรที่จะต้องถูกกำหนด ?
mohamnag

1
@mohamnag ขึ้นอยู่กับ สำหรับข้อมูลที่ระบบสร้างขึ้นภายในถั่วที่ถูกต้องแม่นยำนั้นยอดเยี่ยม อย่างไรก็ตามแอปพลิเคชันทางธุรกิจที่ทันสมัยประกอบด้วย CRUD หรือหน้าจอตัวช่วยสร้างข้อมูลผู้ใช้จำนวนมาก ข้อมูลที่ผู้ใช้ป้อนมักจะเกิดขึ้นบางส่วนหรือไม่ดีอย่างน้อยในระหว่างการแก้ไข บ่อยครั้งที่มีมูลค่าทางธุรกิจในการบันทึกสถานะที่ไม่สมบูรณ์สำหรับการดำเนินการในภายหลัง - คิดว่าการจับภาพแอปพลิเคชันประกันภัยการลงทะเบียนลูกค้า ฯลฯ การรักษาข้อ จำกัด ให้น้อยที่สุด (เช่นคีย์หลัก, คีย์ธุรกิจ & รัฐ) สถานการณ์ทางธุรกิจ
โทมัส W

1
@ThomasW ก่อนอื่นต้องบอกว่าฉันมีความเห็นอย่างยิ่งต่อการออกแบบที่ขับเคลื่อนด้วยโดเมนและใช้ชื่อสำหรับชื่อคลาสและความหมายคำกริยาสำหรับวิธีการ ในกระบวนทัศน์นี้สิ่งที่คุณอ้างถึงเป็น DTO จริง ๆ และไม่ใช่เอนทิตีโดเมนที่ควรใช้สำหรับการจัดเก็บข้อมูลชั่วคราว หรือคุณเพิ่งเข้าใจผิด / - สร้างโดเมนของคุณ
mohamnag

@ThomasW เมื่อฉันกรองประโยคทั้งหมดที่คุณพยายามจะบอกว่าฉันเป็นสามเณรไม่มีความคิดเห็นของคุณเหลืออยู่ยกเว้นการป้อนข้อมูลของผู้ใช้ ส่วนที่ฉันกล่าวก่อนหน้านี้จะต้องทำใน DTO และไม่ได้อยู่ในนิติบุคคลโดยตรง ให้เราคุยกันในอีก 50 ปีข้างหน้าเพื่อที่คุณจะกลายเป็น 5% ของความคิดที่อยู่เบื้องหลัง DDD อย่างที่ฟาวเลอร์มีประสบการณ์! ไชโย: D
mohamnag

144

JPA 2.0 ข้อมูลจำเพาะกล่าวว่า:

  • คลาสเอนทิตีต้องมีตัวสร้างแบบไม่มีอาร์กิวเมนต์ มันอาจมีตัวสร้างอื่น ๆ เช่นกัน ตัวสร้างแบบไม่มีอาร์กิวเมนต์ต้องเป็นสาธารณะหรือได้รับการคุ้มครอง
  • คลาสเอนทิตีต้องเป็นคลาสระดับบนสุด enum หรืออินเตอร์เฟสต้องไม่ถูกกำหนดเป็นเอนทิตี
  • คลาสเอนทิตีต้องไม่เป็นที่สิ้นสุด ไม่มีเมธอดหรือตัวแปรอินสแตนซ์ถาวรของคลาสเอนทิตีอาจเป็นที่สิ้นสุด
  • หากอินสแตนซ์ของเอนทิตีจะต้องถูกส่งผ่านโดยค่าเป็นวัตถุเดี่ยว (เช่นผ่านอินเทอร์เฟซระยะไกล) คลาสเอนทิตีจะต้องใช้อินเตอร์เฟส Serializable
  • ทั้งคลาสนามธรรมและคอนกรีตสามารถเป็นเอนทิตีได้ องค์กรอาจขยายคลาสที่ไม่ใช่เอนทิตีรวมถึงคลาสเอนทิตีและคลาสที่ไม่ใช่เอนทิตีอาจขยายคลาสเอนทิตี

ข้อมูลจำเพาะไม่มีข้อกำหนดเกี่ยวกับการดำเนินการตามวิธีเท่ากับและ hashCode สำหรับเอนทิตีเฉพาะสำหรับคลาสคีย์หลักและคีย์แผนที่เท่าที่ฉันรู้


13
จริง, เท่ากับ, hashcode, ... ไม่ใช่ข้อกำหนดของ JPA แต่แน่นอนว่าแนะนำและถือว่าเป็นแนวปฏิบัติที่ดี
Stijn Geukens

6
@TheStijn ถ้าคุณวางแผนที่จะเปรียบเทียบเอนทิตีที่แยกออกมาเพื่อความเท่าเทียมนั่นอาจไม่จำเป็น ผู้จัดการเอนทิตีรับประกันว่าจะส่งคืนอินสแตนซ์เดียวกันของเอนทิตีที่กำหนดทุกครั้งที่คุณร้องขอ ดังนั้นคุณสามารถทำได้ดีกับการเปรียบเทียบตัวตนสำหรับนิติบุคคลที่มีการจัดการเท่าที่ฉันเข้าใจ คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับสถานการณ์ที่คุณคิดว่าเป็นแนวปฏิบัติที่ดีได้ไหม
Edwin Dalorzo

2
ฉันมุ่งมั่นที่จะมีการใช้งานที่ถูกต้องของเท่ากับ / hashCode ไม่จำเป็นสำหรับ JPA แต่ฉันถือว่าเป็นแนวปฏิบัติที่ดีสำหรับเมื่อเอนทิตีหรือเพิ่มลงในชุด คุณสามารถตัดสินใจที่จะใช้งานเท่ากันเมื่อเอนทิตีจะถูกเพิ่มในชุด แต่คุณรู้ล่วงหน้าหรือไม่?
Stijn Geukens

10
@TheStijn ผู้ให้บริการ JPA จะตรวจสอบให้แน่ใจว่าในเวลาใดก็ตามมีเพียงอินสแตนซ์เดียวของเอนทิตีที่กำหนดในบริบทดังนั้นแม้ชุดของคุณจะปลอดภัยโดยไม่ต้องใช้เท่ากับ / hascode โดยที่คุณใช้หน่วยงานที่มีการจัดการเท่านั้น การใช้วิธีการเหล่านี้สำหรับเอนทิตีไม่เป็นปัญหาเช่นดูที่บทความไฮเบอร์เนตเกี่ยวกับเรื่องนี้ ประเด็นของฉันคือถ้าคุณทำงานกับเอนทิตีที่ได้รับการจัดการคุณจะดีกว่าหากไม่มีพวกเขามิฉะนั้นให้ดำเนินการอย่างระมัดระวังมาก
Edwin Dalorzo

2
@TheStijn นี่เป็นสถานการณ์ผสมที่ดี มันแสดงให้เห็นถึงความจำเป็นในการใช้งาน eq / hC ตามที่คุณแนะนำในตอนแรกเพราะเมื่อเอนทิตีละทิ้งความปลอดภัยของเลเยอร์การคงอยู่คุณจะไม่สามารถเชื่อถือกฎที่บังคับใช้โดยมาตรฐาน JPA อีกต่อไป ในกรณีของเรารูปแบบ DTO นั้นถูกบังคับใช้ทางสถาปัตยกรรมตั้งแต่ต้น โดยการออกแบบ API การคงอยู่ของเราไม่ได้เสนอวิธีสาธารณะในการโต้ตอบกับวัตถุธุรกิจเพียง API เพื่อโต้ตอบกับชั้นการคงอยู่ของเราโดยใช้ DTOs
Edwin Dalorzo

13

2 เซนต์ของฉันนอกจากคำตอบที่นี่คือ:

  1. ด้วยการอ้างอิงถึงการเข้าถึง Field หรือ Property (ห่างจากการพิจารณาประสิทธิภาพ) ทั้งสองถูกเข้าถึงอย่างถูกต้องตามกฎหมายโดย getters และ setters ดังนั้นตรรกะโมเดลของฉันสามารถตั้ง / รับได้ในลักษณะเดียวกัน ความแตกต่างเกิดขึ้นเมื่อผู้ให้บริการรันไทม์ (Hibernate, EclipseLink หรืออื่น ๆ ) ต้องคงอยู่ / ตั้งค่าบางระเบียนในตาราง A ซึ่งมีคีย์ต่างประเทศอ้างอิงถึงบางคอลัมน์ในตาราง B ในกรณีของประเภทการเข้าถึงคุณสมบัติการคงอยู่ ระบบรันไทม์ใช้เมธอด setter ของฉันเพื่อกำหนดเซลล์ในคอลัมน์ตาราง B เป็นค่าใหม่ ในกรณีของชนิดการเข้าถึงฟิลด์ระบบรันไทม์การคงอยู่ตั้งค่าเซลล์ในคอลัมน์ตาราง B โดยตรง ความแตกต่างนี้ไม่สำคัญในบริบทของความสัมพันธ์แบบทิศทางเดียว แต่มันเป็นสิ่งที่จะต้องใช้วิธี setter ของฉันเอง (ประเภทการเข้าถึงคุณสมบัติ) สำหรับความสัมพันธ์แบบสองทิศทางหากวิธี setter ได้รับการออกแบบมาอย่างดีเพื่อความมั่นคง ความสอดคล้องเป็นปัญหาสำคัญสำหรับความสัมพันธ์แบบสองทิศทางอ้างอิงถึงสิ่งนี้ลิงค์สำหรับตัวอย่างง่ายๆสำหรับ setter ที่ออกแบบมาอย่างดี

  2. ด้วยการอ้างอิงถึง Equals / hashCode: เป็นไปไม่ได้ที่จะใช้วิธีการ Equals / hashCode ที่สร้างขึ้นโดยอัตโนมัติสำหรับเอนทิตีที่เข้าร่วมในความสัมพันธ์แบบสองทิศทางมิฉะนั้นพวกเขาจะมีการอ้างอิงแบบวงกลม เมื่อคุณลองใช้ความสัมพันธ์แบบสองทิศทาง (พูด OneToOne) และสร้างอัตโนมัติ Equals () หรือ hashCode () หรือแม้กระทั่ง toString () คุณจะติดอยู่ในข้อยกเว้นสแต็คโอเวอร์โฟลว์นี้


9

เอนทิตีอินเตอร์เฟซ

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 Entities อาจเป็นเรื่องที่มากกว่านั้นหากเอนทิตี JPA เหล่านี้คิดว่าเป็น ValueObjects ที่ขับเคลื่อนด้วยโดเมนแทนที่จะเป็นเอนทิตีที่ขับเคลื่อนด้วยโดเมน (ซึ่งเป็นตัวอย่างรหัสเหล่านี้)


4
แม้ว่าจะเป็นวิธีการที่ดีในการใช้คลาสเอนทิตีของพาเรนต์เพื่อดึงรหัสแผ่นบอยเลอร์ออกมา แต่ก็ไม่ควรใช้ ID ที่กำหนด DB ในวิธีการของคุณ ในกรณีของคุณเมื่อเปรียบเทียบ 2 เอนทิตีใหม่จะทำให้ NPE ขว้าง แม้ว่าคุณจะทำให้มันปลอดภัยแล้วเอนทิตีใหม่ 2 รายการจะเท่ากันเสมอจนกว่าจะมีการยืนยัน Eq / hC ควรจะไม่เปลี่ยนรูป
Stijn Geukens

2
Equals () จะไม่ส่ง NPE เนื่องจากมีการตรวจสอบว่า DB id เป็นโมฆะหรือไม่ & ในกรณีที่ DB id นั้นเป็นโมฆะความเท่าเทียมจะเป็นเท็จ
อาฮามาน

3
แน่นอนฉันไม่เห็นว่าฉันพลาดรหัสนั้นปลอดภัยหรือไม่ แต่ IMO ที่ใช้รหัสยังคงเป็นวิธีที่ไม่ดี ข้อโต้แย้ง: onjava.com/pub/a/onjava/2006/09/13/…
Stijn Geukens

ในหนังสือ 'Implementing DDD' โดย Vaughn Vernon เป็นที่ถกเถียงกันอยู่ว่าคุณสามารถใช้ id สำหรับเท่ากับถ้าคุณใช้ "การสร้าง PK ยุคแรก" (สร้างรหัสแรกก่อนและส่งไปยังตัวสร้างของเอนทิตีแทนที่จะปล่อยให้ฐานข้อมูลสร้าง รหัสเมื่อคุณยืนยันเอนทิตี)
Wim Deblauwe

หรือถ้าคุณไม่ได้วางแผนที่จะเปรียบเทียบเอนทิตีที่ไม่ถาวร ทำไมคุณควร ...
Enerccio
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.