วิธีเพิ่มวิธีกำหนดเองไปยัง Spring Data JPA


160

ฉันกำลังดู JPA ของ Spring Data พิจารณาตัวอย่างด้านล่างที่ฉันจะได้รับ crud และฟังก์ชั่นการค้นหาทั้งหมดทำงานตามค่าเริ่มต้นและถ้าฉันต้องการปรับแต่งการค้นหาแล้วก็สามารถทำได้อย่างง่ายดายในอินเทอร์เฟซของตัวเอง

@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {

  @Query("<JPQ statement here>")
  List<Account> findByCustomer(Customer customer);
}

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

คำตอบ:


290

คุณต้องสร้างส่วนต่อประสานที่แยกต่างหากสำหรับวิธีการที่กำหนดเองของคุณ:

public interface AccountRepository 
    extends JpaRepository<Account, Long>, AccountRepositoryCustom { ... }

public interface AccountRepositoryCustom {
    public void customMethod();
}

และจัดเตรียมคลาสการนำไปใช้สำหรับอินเตอร์เฟสนั้น:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @Autowired
    @Lazy
    AccountRepository accountRepository;  /* Optional - if you need it */

    public void customMethod() { ... }
}

ดูสิ่งนี้ด้วย:


21
การใช้งานที่กำหนดเองนี้สามารถฉีดที่เก็บจริงเพื่อให้สามารถใช้วิธีการที่กำหนดไว้ที่นั่นได้หรือไม่? โดยเฉพาะฉันต้องการอ้างอิงฟังก์ชั่น find * ต่างๆที่กำหนดไว้ในส่วนต่อประสาน Repository ในการนำไปใช้งานระดับการค้นหาที่สูงขึ้น เนื่องจากฟังก์ชั่น find * () นั้นไม่มีการนำไปใช้ฉันจึงไม่สามารถประกาศได้ใน Custom interface หรือ Impl class
JBCP

18
ฉันได้ปฏิบัติตามคำตอบนี้แล้วขออภัย Spring Data พยายามค้นหาคุณสมบัติ "customMethod" บนวัตถุ "บัญชี" ของฉันเนื่องจากพยายามสร้างแบบสอบถามโดยอัตโนมัติสำหรับวิธีการทั้งหมดที่กำหนดไว้ใน AccountRepository มีวิธีหยุดไหม
Nick Foote

41
@NickFoote โปรดทราบว่าชื่อของคลาสที่คุณใช้พื้นที่เก็บข้อมูลของคุณควรเป็น: AccountRepositoryImplไม่ใช่: AccountRepositoryCustomImpl, ฯลฯ - เป็นหลักการตั้งชื่อที่เข้มงวดมาก
Xeon

5
@ wired00 ฉันคิดว่ามันสร้างการอ้างอิงแบบวงกลมและฉันไม่เห็นว่า @JBCP ทำงานอย่างไร เมื่อฉันลองทำสิ่งที่คล้ายกันฉันจะจบลงด้วยข้อยกเว้น:Error creating bean with name 'accountRepositoryImpl': Bean with name 'accountRepositoryImpl' has been injected into other beans [accountRepository] in its raw version as part of a circular reference, but has eventually been wrapped.
Robert Hunt

6
ใช่เห็นความคิดเห็นก่อนหน้าของฉันเกี่ยวกับมันไม่ทำงานหากคุณกำลังขยายQueryDslRepositorySupportคุณต้องฉีดพื้นที่เก็บข้อมูลผ่านสนามหรือ setter injection แทนที่จะสร้าง constructor มิฉะนั้นมันจะไม่สามารถสร้างถั่วได้ ดูเหมือนว่าจะใช้งานได้ แต่วิธีการแก้ปัญหารู้สึกค่อนข้าง 'สกปรก' ฉันไม่แน่ใจว่ามีแผนใด ๆ ที่จะปรับปรุงวิธีการทำงานของทีมงาน Spring Data
โรเบิร์ตฮันท์

72

นอกจากคำตอบของ axtavt อย่าลืมว่าคุณสามารถฉีด Entity Manager ในการนำไปใช้งานตามที่คุณต้องการหากคุณต้องการสร้างแบบสอบถามของคุณ:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}

10
อย่างไรก็ตามขอบคุณฉันต้องการทราบวิธีใช้ Pageable และ Page ในการปรับใช้แบบกำหนดเอง อินพุตใด ๆ
Wand Maker

17

คำตอบที่ยอมรับใช้งานได้ แต่มีสามปัญหา:

  • AccountRepositoryImplมันใช้คุณลักษณะฤดูใบไม้ผลิที่ไม่มีเอกสารข้อมูลเมื่อตั้งชื่อดำเนินการเองเป็น เอกสารอย่างชัดเจนระบุว่าจะต้องมีการเรียกAccountRepositoryCustomImplชื่ออินเตอร์เฟซที่กำหนดเองบวกImpl
  • คุณไม่สามารถใช้การสร้างคอนสตรัคเตอร์เท่านั้น @Autowiredซึ่งถือว่าเป็นการปฏิบัติที่ไม่ดี
  • คุณมีการพึ่งพาแบบวงกลมภายในการปรับใช้แบบกำหนดเอง (นั่นคือสาเหตุที่คุณไม่สามารถใช้การสร้างนวกรรมิกได้

ฉันพบวิธีที่จะทำให้มันสมบูรณ์แบบแม้ว่าจะไม่ได้ใช้คุณสมบัติข้อมูล Spring ที่ไม่มีเอกสารอื่น:

public interface AccountRepository extends AccountRepositoryBasic,
                                           AccountRepositoryCustom 
{ 
}

public interface AccountRepositoryBasic extends JpaRepository<Account, Long>
{
    // standard Spring Data methods, like findByLogin
}

public interface AccountRepositoryCustom 
{
    public void customMethod();
}

public class AccountRepositoryCustomImpl implements AccountRepositoryCustom 
{
    private final AccountRepositoryBasic accountRepositoryBasic;

    // constructor-based injection
    public AccountRepositoryCustomImpl(
        AccountRepositoryBasic accountRepositoryBasic)
    {
        this.accountRepositoryBasic = accountRepositoryBasic;
    }

    public void customMethod() 
    {
        // we can call all basic Spring Data methods using
        // accountRepositoryBasic
    }
}

สิ่งนี้ใช้ได้ผล ฉันต้องการเน้นความสำคัญของชื่อของพารามิเตอร์ในตัวสร้างจะต้องปฏิบัติตามแบบแผนในคำตอบนี้ (ต้องเป็นaccountRepositoryBasic) มิฉะนั้นฤดูใบไม้ผลิบ่นเกี่ยวกับการมีถั่ว 2 ทางเลือกสำหรับการฉีดเข้าไปในคอน*Implสตรัคเตอร์ของฉัน
แพะ

ดังนั้นสิ่งที่คือการใช้ AccountRepository
Kalpesh Soni

@KalpeshSoni วิธีการจากทั้งสองAccountRepositoryBasicและAccountRepositoryCustomจะสามารถใช้ได้ผ่านทางที่ฉีดAccountRepository
23490

1
คุณช่วยระบุวิธีสร้างบริบทได้ไหม? ฉันไม่สามารถรวมทั้งหมดเข้าด้วยกันได้ ขอบคุณ.
franta kocourek

12

สิ่งนี้มีข้อ จำกัด ในการใช้งาน แต่สำหรับวิธีการที่กำหนดเองง่ายๆคุณสามารถใช้วิธีการอินเทอร์เฟซเริ่มต้นเช่น:

import demo.database.Customer;
import org.springframework.data.repository.CrudRepository;

public interface CustomerService extends CrudRepository<Customer, Long> {


    default void addSomeCustomers() {
        Customer[] customers = {
            new Customer("Józef", "Nowak", "nowakJ@o2.pl", 679856885, "Rzeszów", "Podkarpackie", "35-061", "Zamknięta 12"),
            new Customer("Adrian", "Mularczyk", "adii333@wp.pl", 867569344, "Krosno", "Podkarpackie", "32-442", "Hynka 3/16"),
            new Customer("Kazimierz", "Dejna", "sobieski22@weebly.com", 996435876, "Jarosław", "Podkarpackie", "25-122", "Korotyńskiego 11"),
            new Customer("Celina", "Dykiel", "celina.dykiel39@yahoo.org", 947845734, "Żywiec", "Śląskie", "54-333", "Polna 29")
        };

        for (Customer customer : customers) {
            save(customer);
        }
    }
}

แก้ไข:

ในแบบฝึกหัดฤดูใบไม้ผลินี้มันเขียน:

Spring Data JPA ยังช่วยให้คุณกำหนดวิธีการสืบค้นอื่น ๆ โดยเพียงแค่ประกาศลายเซ็นวิธีการ

ดังนั้นจึงเป็นไปได้ที่จะประกาศวิธีการเช่น:

Customer findByHobby(Hobby personHobby);

และถ้าวัตถุHobbyเป็นสมบัติของลูกค้าสปริงจะกำหนดวิธีการให้คุณโดยอัตโนมัติ


6

ฉันใช้รหัสต่อไปนี้เพื่อเข้าถึงวิธีการค้นหาที่สร้างขึ้นจากการใช้งานที่กำหนดเองของฉัน การเรียกใช้งานผ่านโรงงานถั่วป้องกันปัญหาการสร้างถั่วกลม

public class MyRepositoryImpl implements MyRepositoryExtensions, BeanFactoryAware {

    private BrandRepository myRepository;

    public MyBean findOne(int first, int second) {
        return myRepository.findOne(new Id(first, second));
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        myRepository = beanFactory.getBean(MyRepository.class);
    }
}

5

ในฐานะที่ระบุไว้ในในการทำงานเอกสารที่Implต่อท้ายจะช่วยให้เรามีทางออกที่สะอาด

  • ระบุใน@Repositoryส่วนต่อประสานพูดMyEntityRepositoryด้วยวิธี Spring Data หรือวิธีการที่กำหนดเอง
  • สร้างคลาสMyEntityRepositoryImpl( Implคำต่อท้ายเป็นเวทย์มนตร์) ได้ทุกที่ (ไม่จำเป็นต้องอยู่ในแพ็คเกจเดียวกัน) ที่ใช้เมธอดที่กำหนดเองเท่านั้นและใส่คำอธิบายประกอบคลาสด้วย@Component** ( @Repository จะไม่ทำงาน)
    • ชั้นนี้ยังสามารถฉีดMyEntityRepositoryผ่าน@Autowiredเพื่อใช้ในวิธีการที่กำหนดเอง


ตัวอย่าง:

คลาสเอนทิตี:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

อินเตอร์เฟซที่เก็บ:

package myapp.domain.myentity;

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {

    // EXAMPLE SPRING DATA METHOD
    List<MyEntity> findByCommentEndsWith(String x);

    List<MyEntity> doSomeHql(Long id);

    List<MyEntity> useTheRepo(Long id);

}

การปรับใช้ถั่ววิธีการที่กำหนดเอง:

package myapp.infrastructure.myentity;

@Component // Must be @Component !!
public class MyEntityRepositoryImpl { // must have the repo name + Impl !!

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private MyEntityRepository myEntityRepository;

    @SuppressWarnings("unused")
    public List<MyEntity> doSomeHql(Long id) {
        String hql = "SELECT eFROM MyEntity e WHERE e.id = :id";
        TypedQuery<MyEntity> query = entityManager.createQuery(hql, MyEntity.class);
        query.setParameter("id", id);
        return query.getResultList();
    }

    @SuppressWarnings("unused")
    public List<MyEntity> useTheRepo(Long id) {
        List<MyEntity> es = doSomeHql(id);
        es.addAll(myEntityRepository.findByCommentEndsWith("DO"));
        es.add(myEntityRepository.findById(2L).get());
        return es;
    }

}

ข้อเสียเล็ก ๆ ที่ฉันระบุคือ:

  • วิธีการที่กำหนดเองในImplชั้นเรียนมีการทำเครื่องหมายว่าไม่ได้ใช้โดยคอมไพเลอร์ดังนั้น@SuppressWarnings("unused")ข้อเสนอแนะ
  • คุณ จำกัดImplคลาสได้หนึ่งคลาส (โดยที่ในส่วนของอินเตอร์เฟสการแบ่งแฟรกเมนต์ปกติเอกสารแนะนำให้คุณมีได้มากมาย)

มีข้อแม้เล็ก ๆ ระหว่างการทดสอบ หากคุณต้องการแจ้งให้เราทราบและฉันจะอัปเดตคำตอบ
acdcjunior

วิธีการอย่างถูกต้อง Autowire MyEntityRepositoryImpl?
Konstantin Zyubin

@KonstantinZyubin คุณ autowire ไม่MyEntityRepository *Impl
acdcjunior

4

หากคุณต้องการที่จะสามารถดำเนินการที่ซับซ้อนมากขึ้นคุณอาจต้องเข้าถึงภายในของ Spring Data ซึ่งในกรณีนี้จะทำงานต่อไปนี้ (เป็นโซลูชันชั่วคราวของฉันไปที่DATAJPA-422 ):

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    private JpaEntityInformation<Account, ?> entityInformation;

    @PostConstruct
    public void postConstruct() {
        this.entityInformation = JpaEntityInformationSupport.getMetadata(Account.class, entityManager);
    }

    @Override
    @Transactional
    public Account saveWithReferenceToOrganisation(Account entity, long referralId) {
        entity.setOrganisation(entityManager.getReference(Organisation.class, organisationId));
        return save(entity);
    }

    private Account save(Account entity) {
        // save in same way as SimpleJpaRepository
        if (entityInformation.isNew(entity)) {
            entityManager.persist(entity);
            return entity;
        } else {
            return entityManager.merge(entity);
        }
    }

}

4

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

 @Query("Select a from Account a where a."#nameoffield"=?1")
      List<Account> findByCustomer(String "#nameoffield");

ทำให้ชื่อของตารางที่จะสอบถามเป็นชื่อคลาส Entity สำหรับการใช้งานเพิ่มเติมโปรดดูที่ นี้


1
เป็นพิมพ์ผิดในการค้นหาก็ควรจะ nameoffie ลิตร d ผมไม่ได้มีสิทธิที่เหมาะสมที่จะแก้ไขได้
BrunoJCM

3

มีอีกเรื่องที่ต้องพิจารณาที่นี่ บางคนคาดหวังว่าการเพิ่มวิธีการที่กำหนดเองไปยังที่เก็บของคุณจะเปิดเผยโดยอัตโนมัติเป็นบริการ REST ภายใต้ลิงก์ '/ search' นี่ไม่ใช่กรณีที่น่าเสียดาย สปริงไม่สนับสนุนสิ่งนั้นในขณะนี้

นี่คือคุณลักษณะ 'ตามการออกแบบ' ข้อมูลฤดูใบไม้ผลิที่เหลือตรวจสอบอย่างชัดเจนว่าวิธีการเป็นวิธีการที่กำหนดเองและไม่เปิดเผยว่าเป็นลิงค์การค้นหาส่วนที่เหลือ:

private boolean isQueryMethodCandidate(Method method) {    
  return isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method);
}

นี่คือผลงานของ Oliver Gierke:

นี่คือโดยการออกแบบ เมธอดที่เก็บแบบกำหนดเองไม่ใช่เมธอดเคียวรีเนื่องจากสามารถใช้การทำงานได้อย่างมีประสิทธิภาพ ดังนั้นในปัจจุบันเราจึงไม่สามารถตัดสินใจเกี่ยวกับวิธี HTTP เพื่อเปิดเผยวิธีการภายใต้ POST จะเป็นตัวเลือกที่ปลอดภัยที่สุด แต่นั่นไม่สอดคล้องกับวิธีการสืบค้นทั่วไป (ซึ่งรับ GET)

สำหรับรายละเอียดเพิ่มเติมดูปัญหานี้: https://jira.spring.io/browse/DATAREST-206


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

คุณสามารถเปิดเผยวิธีการเก็บข้อมูลใด ๆ ผ่านทางส่วนที่เหลือโดยเพียงแค่เพิ่ม@RestResource(path = "myQueryMethod")คำอธิบายประกอบให้กับวิธีการ ข้อความข้างต้นเป็นเพียงการระบุว่า Spring ไม่รู้ว่าคุณต้องการให้แมปอย่างไร (เช่น GET กับ POST ฯลฯ ) ดังนั้นจึงขึ้นอยู่กับคุณที่จะระบุผ่านหมายเหตุ
GreenGiant

1

การเพิ่มพฤติกรรมที่กำหนดเองไปยังที่เก็บข้อมูลทั้งหมด:

ในการเพิ่มพฤติกรรมที่กำหนดเองไปยังที่เก็บข้อมูลทั้งหมดก่อนอื่นให้เพิ่มอินเทอร์เฟซกลางเพื่อประกาศพฤติกรรมที่ใช้ร่วมกัน

public interface MyRepository <T, ID extends Serializable> extends JpaRepository<T, ID>
{

    void sharedCustomMethod( ID id );
}

ตอนนี้อินเตอร์เฟสที่เก็บข้อมูลส่วนบุคคลของคุณจะขยายอินเทอร์เฟซกลางนี้แทนอินเทอร์เฟซ Repository เพื่อรวมฟังก์ชันการทำงานที่ประกาศไว้

ถัดไปสร้างการนำไปปฏิบัติของอินเตอร์เฟสระดับกลางที่ขยายคลาสฐานที่เก็บข้อมูลเฉพาะเทคโนโลยีการคงอยู่ คลาสนี้จะทำหน้าที่เป็นคลาสพื้นฐานที่กำหนดเองสำหรับพร็อกซีที่เก็บ

public class MyRepositoryImpl <T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID>
{

    private EntityManager entityManager;

       // There are two constructors to choose from, either can be used.
    public MyRepositoryImpl(Class<T> domainClass, EntityManager entityManager)
    {
        super( domainClass, entityManager );

        // This is the recommended method for accessing inherited class dependencies.
        this.entityManager = entityManager;
    }


    public void sharedCustomMethod( ID id )
    {
        // implementation goes here
    }
}

สปริงที่เก็บข้อมูลส่วนที่ 1 การอ้างอิง ป้อนคำอธิบายรูปภาพที่นี่


0

ฉันขยาย SimpleJpaRepository:

public class ExtendedRepositoryImpl<T extends EntityBean> extends SimpleJpaRepository<T, Long>
    implements ExtendedRepository<T> {

    private final JpaEntityInformation<T, ?> entityInformation;

    private final EntityManager em;

    public ExtendedRepositoryImpl(final JpaEntityInformation<T, ?> entityInformation,
                                                      final EntityManager entityManager) {
       super(entityInformation, entityManager);
       this.entityInformation = entityInformation;
       this.em = entityManager;
    }
}

และเพิ่มคลาสนี้ลงใน @EnableJpaRepositoryries repositoryBaseClass

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