เมื่อใดควรใช้ EntityManager.find () เทียบกับ EntityManager.getReference () กับ JPA


103

ฉันเจอสถานการณ์ (ซึ่งฉันคิดว่ามันแปลก แต่อาจจะค่อนข้างปกติ) ที่ฉันใช้ EntityManager.getReference (LObj.getClass (), LObj.getId ()) เพื่อรับเอนทิตีฐานข้อมูลแล้วส่งคืนวัตถุไปที่ คงอยู่ในตารางอื่น

โดยพื้นฐานแล้วการไหลเป็นเช่นนี้:

คลาส TFacade {

  createT (FObj, AObj) {
    T TObj = ใหม่ T ();
    TObj.setF (FObj);
    TObj.setA (AObj);
    ...
    EntityManager.persist (TObj);
    ...
    L LObj = A.getL ();
    FObj.setL (LObj);
    FFacade.editF (FObj);
  }
}

@ TransactionAttributeType.REQUIRES_NEW
class FFacade {

  editF (FObj) {
    L LObj = FObj.getL ();
    LObj = EntityManager.getReference (LObj.getClass (), LObj.getId ());
    ...
    EntityManager.merge (FObj);
    ...
    FLHFacade.create (FObj, LObj);
  }
}

@ TransactionAttributeType.REQUIRED
คลาส FLHFacade {

  createFLH (FObj, LObj) {
    FLH FLHObj = FLH ใหม่ ();
    FLHObj.setF (FObj);
    FLHObj.setL (LObj);
    ....
    EntityManager.persist (FLHObj);
    ...
  }
}

ฉันได้รับข้อยกเว้นต่อไปนี้ "java.lang.IllegalArgumentException: Unknown entity: com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0"

หลังจากดูสักพักในที่สุดฉันก็พบว่าเป็นเพราะฉันใช้เมธอด EntityManager.getReference () ทำให้ฉันได้รับข้อยกเว้นข้างต้นเนื่องจากเมธอดกำลังส่งคืนพร็อกซี

นี้ทำให้ผมสงสัยว่าเมื่อจะแนะนำให้ใช้ EntityManager.getReference () วิธีการแทน EntityManager.find () วิธี ?

EntityManager.getReference () จะพ่น EntityNotFoundException หากไม่พบเอนทิตีที่กำลังค้นหาซึ่งสะดวกมากในตัวเอง EntityManager.find () วิธีการเพียงส่งคืนค่าว่างถ้าไม่พบเอนทิตี

เกี่ยวกับขอบเขตธุรกรรมสำหรับฉันดูเหมือนว่าคุณจะต้องใช้เมธอด find () ก่อนที่จะส่งเอนทิตีที่พบใหม่ไปยังธุรกรรมใหม่ หากคุณใช้เมธอด getReference () คุณอาจจะอยู่ในสถานการณ์ที่คล้ายกับของฉันด้วยข้อยกเว้นข้างต้น


ลืมบอกไปฉันใช้ Hibernate เป็นผู้ให้บริการ JPA
SibzTer

คำตอบ:


153

ฉันมักจะใช้เมธอดgetReferenceเมื่อฉันไม่ต้องการเข้าถึงสถานะฐานข้อมูล (ฉันหมายถึงเมธอด getter) เพียงเพื่อเปลี่ยนสถานะ (ฉันหมายถึงวิธีการตั้งค่า) ดังที่คุณควรทราบ getReference ส่งคืนวัตถุพร็อกซีซึ่งใช้คุณสมบัติที่มีประสิทธิภาพที่เรียกว่าการตรวจสอบสกปรกอัตโนมัติ สมมติว่าต่อไปนี้

public class Person {

    private String name;
    private Integer age;

}


public class PersonServiceImpl implements PersonService {

    public void changeAge(Integer personId, Integer newAge) {
        Person person = em.getReference(Person.class, personId);

        // person is a proxy
        person.setAge(newAge);
    }

}

ถ้าฉันโทรหาวิธีการผู้ให้บริการ JPA เบื้องหลังจะโทรหา

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

ถ้าฉันเรียกเมธอดgetReferenceผู้ให้บริการ JPA เบื้องหลังจะโทรมา

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

แล้วรู้ไหมว่าทำไม ???

เมื่อคุณเรียก getReference คุณจะได้รับวัตถุพร็อกซี อะไรทำนองนี้ (ผู้ให้บริการ JPA ดูแลการใช้งานพร็อกซีนี้)

public class PersonProxy {

    // JPA provider sets up this field when you call getReference
    private Integer personId;

    private String query = "UPDATE PERSON SET ";

    private boolean stateChanged = false;

    public void setAge(Integer newAge) {
        stateChanged = true;

        query += query + "AGE = " + newAge;
    }

}

ดังนั้นก่อนที่จะทำธุรกรรมผู้ให้บริการ JPA จะเห็นสถานะ stateChanged เพื่ออัปเดตหรือไม่บุคคล หากไม่มีการอัปเดตแถวหลังจากคำสั่งอัพเดตผู้ให้บริการ JPA จะโยน EntityNotFoundException ตามข้อกำหนดของ JPA

ความนับถือ,


4
ฉันใช้ EclipseLink 2.5.0 และข้อความค้นหาที่ระบุข้างต้นไม่ถูกต้อง มันก็จะออกSELECTมาก่อนUPDATEไม่ว่าของที่find()/ getReference()ผมใช้ สิ่งที่แย่ไปกว่านั้นคือการSELECTข้ามผ่านความสัมพันธ์แบบ NON-LAZY (ออกใหม่SELECTS) แม้ว่าฉันต้องการอัปเดตฟิลด์เดียวในเอนทิตีเดียว
Dejan Milosevic

1
@Arthur Ronald จะเกิดอะไรขึ้นหากมีคำอธิบายประกอบเวอร์ชันในเอนทิตีที่เรียกโดย getReference
David Hofmann

ฉันมีปัญหาเดียวกันกับ @DejanMilosevic: เมื่อลบเอนทิตีที่ได้รับผ่าน getReference () SELECT จะถูกออกในเอนทิตีนั้นและข้ามผ่านความสัมพันธ์ LAZY ทั้งหมดของเอนทิตีนั้นจึงออก SELECTS จำนวนมาก (พร้อม EclipseLink 2.5.0)
Stéphane Appercel

27

ดังที่ฉันได้อธิบายไว้ในบทความนี้สมมติว่าคุณมีPostเอนทิตีแม่และลูกPostCommentตามที่แสดงในแผนภาพต่อไปนี้:

ป้อนคำอธิบายภาพที่นี่

หากคุณโทรหาfindเมื่อคุณพยายามตั้งค่าการ@ManyToOne postเชื่อมโยง:

PostComment comment = new PostComment();
comment.setReview("Just awesome!");

Post post = entityManager.find(Post.class, 1L);
comment.setPost(post);

entityManager.persist(comment);

ไฮเบอร์เนตจะเรียกใช้คำสั่งต่อไปนี้:

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM   post p
WHERE p.id = 1

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)

แบบสอบถาม SELECT ไม่มีประโยชน์ในครั้งนี้เนื่องจากเราไม่จำเป็นต้องดึงเอนทิตี Post เราต้องการตั้งค่าคอลัมน์ post_id Foreign Key เท่านั้น

ตอนนี้ถ้าคุณใช้getReferenceแทน:

PostComment comment = new PostComment();
comment.setReview("Just awesome!");

Post post = entityManager.getReference(Post.class, 1L);
comment.setPost(post);

entityManager.persist(comment);

คราวนี้ Hibernate จะออกคำสั่ง INSERT:

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)

ต่างจากที่findแสดงgetReferenceเฉพาะพร็อกซีเอนทิตีที่มีชุดตัวระบุเท่านั้น หากคุณเข้าถึง Proxy คำสั่ง SQL ที่เกี่ยวข้องจะถูกทริกเกอร์ตราบเท่าที่ EntityManager ยังคงเปิดอยู่

อย่างไรก็ตามในกรณีนี้เราไม่จำเป็นต้องเข้าถึงเอนทิตีพร็อกซี เราต้องการเผยแพร่ Foreign Key ไปยังเรกคอร์ดที่อยู่ด้านล่างเท่านั้นดังนั้นการโหลด Proxy จึงเพียงพอสำหรับกรณีการใช้งานนี้

เมื่อโหลดพร็อกซีคุณต้องทราบว่า LazyInitializationException สามารถโยนได้หากคุณพยายามเข้าถึงการอ้างอิงพร็อกซีหลังจากที่ปิด EntityManager สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการจัดการLazyInitializationExceptionตรวจสอบบทความนี้


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

3
สำหรับกรณีการใช้งานที่คุณอธิบายไว้ไฮเบอร์เนตมีhibernate.jpa.compliance.proxyคุณสมบัติการกำหนดค่าดังนั้นคุณสามารถเลือกการปฏิบัติตามข้อกำหนดของ JPA หรือประสิทธิภาพการเข้าถึงข้อมูลที่ดีขึ้น
Vlad Mihalcea

@VladMihalcea ทำไมถึงgetReferenceจำเป็นถ้ามันเพียงพอที่จะตั้งค่าอินสแตนซ์ใหม่ของโมเดลด้วยชุด PK ฉันขาดอะไรไป?
rilaby

สิ่งนี้รองรับเฉพาะใน Hibernarea เท่านั้นที่จะไม่อนุญาตให้คุณโหลดการเชื่อมโยงหากข้ามผ่าน
Vlad Mihalcea

8

เนื่องจากการอ้างอิงเป็น 'จัดการ' แต่ไม่ได้ใช้ไฮเดรตจึงทำให้คุณสามารถลบเอนทิตีด้วย ID ได้โดยไม่จำเป็นต้องโหลดลงในหน่วยความจำก่อน

เนื่องจากคุณไม่สามารถลบเอนทิตีที่ไม่มีการจัดการได้จึงเป็นเรื่องธรรมดาที่จะโหลดฟิลด์ทั้งหมดโดยใช้ find (... ) หรือ createQuery (... ) เพียงเพื่อลบทันที

MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);

7

ทำให้ฉันสงสัยว่าเมื่อใดควรใช้เมธอด EntityManager.getReference () แทนวิธี EntityManager.find ()

EntityManager.getReference()เป็นวิธีที่เกิดข้อผิดพลาดได้ง่ายและมีบางกรณีที่จำเป็นต้องใช้รหัสไคลเอ็นต์
โดยส่วนตัวฉันไม่เคยจำเป็นต้องใช้มัน

EntityManager.getReference () และ EntityManager.find (): ไม่มีความแตกต่างในแง่ของค่าใช้จ่าย

ฉันไม่เห็นด้วยกับคำตอบที่ยอมรับและโดยเฉพาะอย่างยิ่ง:

ถ้าฉันโทรหาวิธีการผู้ให้บริการ JPA เบื้องหลังจะโทรหา

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

ถ้าฉันเรียกเมธอดgetReferenceผู้ให้บริการ JPA เบื้องหลังจะโทรมา

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

ไม่ใช่พฤติกรรมที่ฉันได้รับจาก Hibernate 5 และ javadoc getReference()ไม่ได้พูดสิ่งนั้น:

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

EntityManager.getReference() สำรองแบบสอบถามเพื่อดึงข้อมูลเอนทิตีในสองกรณี:

1) ถ้าเอนทิตีถูกเก็บไว้ในบริบทความคงอยู่นั่นคือแคชระดับแรก
และลักษณะการทำงานนี้ไม่เฉพาะเจาะจงEntityManager.getReference()แต่ EntityManager.find()จะสำรองแบบสอบถามเพื่อดึงข้อมูลเอนทิตีหากเอนทิตีถูกเก็บไว้ในบริบทความคงอยู่

คุณสามารถตรวจสอบจุดแรกด้วยตัวอย่างใดก็ได้
คุณยังสามารถพึ่งพาการใช้งานไฮเบอร์เนตจริง
อันที่จริงEntityManager.getReference()อาศัยcreateProxyIfNecessary()วิธีการของorg.hibernate.event.internal.DefaultLoadEventListenerคลาสเพื่อโหลดเอนทิตี
นี่คือการใช้งาน:

private Object createProxyIfNecessary(
        final LoadEvent event,
        final EntityPersister persister,
        final EntityKey keyToLoad,
        final LoadEventListener.LoadType options,
        final PersistenceContext persistenceContext) {
    Object existing = persistenceContext.getEntity( keyToLoad );
    if ( existing != null ) {
        // return existing object or initialized proxy (unless deleted)
        if ( traceEnabled ) {
            LOG.trace( "Entity found in session cache" );
        }
        if ( options.isCheckDeleted() ) {
            EntityEntry entry = persistenceContext.getEntry( existing );
            Status status = entry.getStatus();
            if ( status == Status.DELETED || status == Status.GONE ) {
                return null;
            }
        }
        return existing;
    }
    if ( traceEnabled ) {
        LOG.trace( "Creating new proxy for entity" );
    }
    // return new uninitialized proxy
    Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
    persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
    persistenceContext.addProxy( keyToLoad, proxy );
    return proxy;
}

ส่วนที่น่าสนใจคือ:

Object existing = persistenceContext.getEntity( keyToLoad );

2) หากเราไม่จัดการเอนทิตีอย่างมีประสิทธิภาพให้สะท้อนไปยังjavadoc ที่ดึงมาอย่างเฉื่อยชา
อันที่จริงเพื่อให้แน่ใจว่าการโหลดเอนทิตีมีประสิทธิภาพจำเป็นต้องเรียกใช้วิธีการ
ดังนั้นกำไรจะเกี่ยวข้องกับสถานการณ์ที่เราต้องการโหลดเอนทิตีโดยไม่จำเป็นต้องใช้มัน? ในกรอบของการใช้งานความต้องการนี้ถือเป็นเรื่องแปลกจริงๆและนอกจากนี้getReference()พฤติกรรมนี้ยังทำให้เข้าใจผิดได้มากหากคุณอ่านส่วนถัดไป

เหตุใดจึงชอบ EntityManager.find () มากกว่า EntityManager.getReference ()

ในแง่ของค่าใช้จ่ายgetReference()ไม่ดีกว่าfind()ที่กล่าวไว้ในประเด็นก่อนหน้านี้
ทำไมต้องใช้อย่างใดอย่างหนึ่ง?

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

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

EntityManager.find()ไม่มีความทะเยอทะยานในการขว้างปาEntityNotFoundExceptionหากไม่พบเอนทิตี พฤติกรรมของมันทั้งเรียบง่ายและชัดเจน คุณจะไม่แปลกใจเลยเพราะมันจะส่งคืนเอนทิตีที่โหลดเสมอหรือnull(หากไม่พบเอนทิตี) แต่จะไม่มีเอนทิตีภายใต้รูปร่างของพร็อกซีที่อาจโหลดไม่ได้อย่างมีประสิทธิภาพ
ดังนั้นEntityManager.find()ควรเป็นที่ชื่นชอบในกรณีส่วนใหญ่


เหตุผลของคุณทำให้เข้าใจผิดเมื่อเทียบกับคำตอบที่ได้รับการยอมรับ + ​​คำตอบของ Vlad Mihalcea + ความคิดเห็นของฉันที่มีต่อ Vlad Mihalcea (อาจจะสำคัญน้อยกว่า + สุดท้ายนี้)
adrhc

1
Pro JPA2 ระบุว่า: "เมื่อพิจารณาถึงสถานการณ์ที่เฉพาะเจาะจงซึ่งสามารถใช้ getReference () ได้โปรดใช้ find () ในแทบทุกกรณี"
JL_SO

โหวตคำถามนี้เนื่องจากเป็นส่วนเสริมที่จำเป็นสำหรับคำตอบที่ยอมรับและเนื่องจากการทดสอบของฉันแสดงให้เห็นว่าเมื่อตั้งค่าคุณสมบัติของพร็อกซีเอนทิตีจะดึงมาจากฐานข้อมูลตรงกันข้ามกับคำตอบที่ยอมรับ เฉพาะกรณีที่ระบุโดย Vlad เท่านั้นที่ผ่านการทดสอบของฉัน
JoãoFé

2

ฉันไม่เห็นด้วยกับคำตอบที่เลือกและตามที่ davidxxx ชี้ให้เห็นอย่างถูกต้อง getReference ไม่ได้จัดเตรียมพฤติกรรมดังกล่าวของการอัปเดตแบบไดนามิกโดยไม่ได้เลือก ฉันถามคำถามเกี่ยวกับความถูกต้องของคำตอบนี้ให้ดูที่นี่ - ไม่สามารถปรับปรุงได้โดยไม่ต้องออกเลือกในการใช้หมาหลังจาก getReference () ของจำศีล JPA

ฉันไม่เคยเห็นใครที่ใช้ฟังก์ชันนั้นจริงๆ ได้ทุกที่ และฉันไม่เข้าใจว่าทำไมถึงโหวตมาก

ก่อนอื่นไม่ว่าคุณจะเรียกใช้อ็อบเจ็กต์พร็อกซีไฮเบอร์เนตตัวเซ็ตเตอร์หรือตัวรับ SQL จะทำงานและโหลดอ็อบเจ็กต์

แต่ฉันก็คิดว่าจะเป็นอย่างไรถ้าพร็อกซี JPA getReference () ไม่มีฟังก์ชันนั้น ฉันสามารถเขียนพร็อกซีของตัวเองได้

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

การใช้งาน

Order example = ProxyHandler.getReference(Order.class, 3);
example.setType("ABCD");
example.setCost(10);
PersistenceService.save(example);

และสิ่งนี้จะเริ่มการค้นหาต่อไปนี้ -

UPDATE Order SET type = 'ABCD' and cost = 10 WHERE id = 3;

และแม้ว่าคุณต้องการแทรกคุณยังสามารถทำ PersistenceService.save (คำสั่งใหม่ ("a", 2)); และมันจะยิงแทรกตามที่ควร

การดำเนินการ

เพิ่มสิ่งนี้ใน pom.xml ของคุณ -

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>

ทำให้คลาสนี้สร้างพร็อกซีแบบไดนามิก -

@SuppressWarnings("unchecked")
public class ProxyHandler {

public static <T> T getReference(Class<T> classType, Object id) {
    if (!classType.isAnnotationPresent(Entity.class)) {
        throw new ProxyInstantiationException("This is not an entity!");
    }

    try {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classType);
        enhancer.setCallback(new ProxyMethodInterceptor(classType, id));
        enhancer.setInterfaces((new Class<?>[]{EnhancedProxy.class}));
        return (T) enhancer.create();
    } catch (Exception e) {
        throw new ProxyInstantiationException("Error creating proxy, cause :" + e.getCause());
    }
}

สร้างอินเทอร์เฟซด้วยวิธีการทั้งหมด -

public interface EnhancedProxy {
    public String getJPQLUpdate();
    public HashMap<String, Object> getModifiedFields();
}

ตอนนี้สร้างตัวสกัดกั้นซึ่งจะช่วยให้คุณใช้วิธีการเหล่านี้กับพร็อกซีของคุณ -

import com.anil.app.exception.ProxyInstantiationException;
import javafx.util.Pair;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import javax.persistence.Id;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author Anil Kumar
*/
public class ProxyMethodInterceptor implements MethodInterceptor, EnhancedProxy {

private Object target;
private Object proxy;
private Class classType;
private Pair<String, Object> primaryKey;
private static HashSet<String> enhancedMethods;

ProxyMethodInterceptor(Class classType, Object id) throws IllegalAccessException, InstantiationException {
    this.classType = classType;
    this.target = classType.newInstance();
    this.primaryKey = new Pair<>(getPrimaryKeyField().getName(), id);
}

static {
    enhancedMethods = new HashSet<>();
    for (Method method : EnhancedProxy.class.getDeclaredMethods()) {
        enhancedMethods.add(method.getName());
    }
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    //intercept enhanced methods
    if (enhancedMethods.contains(method.getName())) {
        this.proxy = obj;
        return method.invoke(this, args);
    }
    //else invoke super class method
    else
        return proxy.invokeSuper(obj, args);
}

@Override
public HashMap<String, Object> getModifiedFields() {
    HashMap<String, Object> modifiedFields = new HashMap<>();
    try {
        for (Field field : classType.getDeclaredFields()) {

            field.setAccessible(true);

            Object initialValue = field.get(target);
            Object finalValue = field.get(proxy);

            //put if modified
            if (!Objects.equals(initialValue, finalValue)) {
                modifiedFields.put(field.getName(), finalValue);
            }
        }
    } catch (Exception e) {
        return null;
    }
    return modifiedFields;
}

@Override
public String getJPQLUpdate() {
    HashMap<String, Object> modifiedFields = getModifiedFields();
    if (modifiedFields == null || modifiedFields.isEmpty()) {
        return null;
    }
    StringBuilder fieldsToSet = new StringBuilder();
    for (String field : modifiedFields.keySet()) {
        fieldsToSet.append(field).append(" = :").append(field).append(" and ");
    }
    fieldsToSet.setLength(fieldsToSet.length() - 4);
    return "UPDATE "
            + classType.getSimpleName()
            + " SET "
            + fieldsToSet
            + "WHERE "
            + primaryKey.getKey() + " = " + primaryKey.getValue();
}

private Field getPrimaryKeyField() throws ProxyInstantiationException {
    for (Field field : classType.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(Id.class))
            return field;
    }
    throw new ProxyInstantiationException("Entity class doesn't have a primary key!");
}
}

และคลาสยกเว้น -

public class ProxyInstantiationException extends RuntimeException {
public ProxyInstantiationException(String message) {
    super(message);
}

บริการบันทึกโดยใช้พร็อกซีนี้ -

@Service
public class PersistenceService {

@PersistenceContext
private EntityManager em;

@Transactional
private void save(Object entity) {
    // update entity for proxies
    if (entity instanceof EnhancedProxy) {
        EnhancedProxy proxy = (EnhancedProxy) entity;
        Query updateQuery = em.createQuery(proxy.getJPQLUpdate());
        for (Entry<String, Object> entry : proxy.getModifiedFields().entrySet()) {
            updateQuery.setParameter(entry.getKey(), entry.getValue());
        }
        updateQuery.executeUpdate();
    // insert otherwise
    } else {
        em.persist(entity);
    }

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