Cách thêm phương thức tùy chỉnh vào Spring Data JPA


160

Tôi đang xem xét dữ liệu mùa xuân JPA. Hãy xem xét ví dụ dưới đây, nơi tôi sẽ làm cho tất cả các chức năng tìm kiếm và tìm kiếm hoạt động theo mặc định và nếu tôi muốn tùy chỉnh một công cụ tìm thì điều đó cũng có thể được thực hiện dễ dàng trong chính giao diện.

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

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

Tôi muốn biết làm thế nào tôi có thể thêm một phương thức tùy chỉnh hoàn chỉnh với việc triển khai nó cho AccountRep repository ở trên? Vì nó là một Giao diện nên tôi không thể thực hiện phương thức ở đó.

Câu trả lời:


290

Bạn cần tạo một giao diện riêng cho các phương thức tùy chỉnh của mình:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

và cung cấp một lớp thực hiện cho giao diện đó:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

Xem thêm:


21
Việc thực hiện tùy chỉnh này có thể tiêm kho lưu trữ thực tế, vì vậy nó có thể sử dụng các phương thức được xác định ở đó không? Cụ thể, tôi muốn tham khảo các hàm find * khác nhau được xác định trong giao diện Kho lưu trữ ở mức triển khai tìm cao hơn. Vì các hàm find * () không có triển khai, tôi không thể khai báo chúng trong giao diện Tùy chỉnh hoặc lớp Impl.
JBCP

18
Tôi đã theo dõi câu trả lời này, thật không may là bây giờ Spring Data đang cố gắng tìm thuộc tính "customMethod" trên đối tượng "Tài khoản" của tôi vì nó đang cố gắng tự động tạo truy vấn cho tất cả các phương thức được xác định trên AccountRep repository. Có cách nào để ngăn chặn điều này?
Nick Foote

41
@NickFoote lưu ý rằng tên của lớp mà bạn triển khai kho lưu trữ của bạn phải là: AccountRepositoryImplnot : AccountRepositoryCustomImpl, v.v. - đó là quy ước đặt tên rất nghiêm ngặt.
Xeon

5
@ Wired00 Tôi nghĩ rằng nó tạo ra một tham chiếu vòng tròn và tôi không thể thấy @JBCP đã làm cho nó hoạt động như thế nào. Khi tôi thử và làm một cái gì đó tương tự, tôi kết thúc bằng một ngoại lệ: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
Vâng, hãy xem nhận xét trước đây của tôi về việc nó không hoạt động nếu bạn mở rộng QueryDslRepositorySupportBạn cũng phải tiêm kho lưu trữ thông qua trường tiêm hoặc setter tiêm thay vì tiêm constructor nếu không nó sẽ không thể tạo bean. Nó dường như hoạt động nhưng giải pháp cảm thấy hơi 'bẩn', tôi không chắc có kế hoạch nào để cải thiện cách thức hoạt động của nhóm Spring Data hay không.
Robert Hunt

72

Ngoài câu trả lời của axtavt , đừng quên bạn có thể đưa Trình quản lý thực thể vào triển khai tùy chỉnh của mình nếu bạn cần nó để xây dựng các truy vấn của mình:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

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

10
Tuy nhiên, cảm ơn, tôi muốn biết cách sử dụng Trang có thể hiển thị và Trang trong triển khai tùy chỉnh. Bất kỳ đầu vào?
Wand Maker

17

Câu trả lời được chấp nhận hoạt động, nhưng có ba vấn đề:

  • Nó sử dụng một tính năng Spring Data không có giấy tờ khi đặt tên cho việc thực hiện tùy chỉnh là AccountRepositoryImpl. Các tài liệu nêu rõ rằng nó đã được gọi là AccountRepositoryCustomImpl, tên giao diện tùy chỉnh cộngImpl
  • Bạn không thể sử dụng tiêm constructor, chỉ @Autowired, được coi là thực hành xấu
  • Bạn có một phụ thuộc vòng tròn bên trong triển khai tùy chỉnh (đó là lý do tại sao bạn không thể sử dụng hàm tạo của hàm tạo).

Tôi đã tìm thấy một cách để làm cho nó hoàn hảo, mặc dù không phải là không sử dụng một tính năng Spring Data không có giấy tờ khác:

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

Điều này đã làm việc. Tôi muốn nhấn mạnh tầm quan trọng của tên của tham số trong hàm tạo phải tuân theo quy ước trong câu trả lời này (phải là accountRepositoryBasic). Nếu không, mùa xuân phàn nàn về việc có 2 lựa chọn đậu để tiêm vào nhà *Implxây dựng của tôi .

vậy việc sử dụng
AccountRep Storage

@KalpeshSoni các phương pháp từ cả hai AccountRepositoryBasicAccountRepositoryCustomsẽ có sẵn thông qua một tiêmAccountRepository
geg

1
Bạn có thể vui lòng cung cấp cách tạo bối cảnh không? Tôi không thể đặt tất cả lại với nhau. Cảm ơn bạn.
franta kescentrek

12

Điều này bị hạn chế trong cách sử dụng, nhưng đối với các phương thức tùy chỉnh đơn giản, bạn có thể sử dụng các phương thức giao diện mặc định như:

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

BIÊN TẬP:

Trong hướng dẫn mùa xuân này, nó được viết:

Spring Data JPA cũng cho phép bạn xác định các phương thức truy vấn khác bằng cách khai báo chữ ký phương thức của họ.

Vì vậy, thậm chí có thể chỉ cần khai báo phương thức như:

Customer findByHobby(Hobby personHobby);

và nếu đối tượng Hobbylà tài sản của Khách hàng thì Spring sẽ tự động xác định phương thức cho bạn.


6

Tôi đang sử dụng mã sau đây để truy cập các phương thức tìm được tạo từ triển khai tùy chỉnh của tôi. Việc thực hiện thông qua các nhà máy đậu ngăn ngừa các vấn đề tạo đậu tròn.

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

Như được chỉ định trong chức năng được ghi lại , Implhậu tố cho phép chúng ta có một giải pháp rõ ràng:

  • Xác định trong @Repositorygiao diện, giả sử MyEntityRepository, phương thức Spring Data hoặc phương thức tùy chỉnh
  • Tạo một lớp MyEntityRepositoryImpl( Implhậu tố là ma thuật) ở bất cứ đâu (thậm chí không cần phải ở trong cùng một gói) chỉ thực hiện các phương thức tùy chỉnhchú thích lớp đó với @Component** ( @Repository sẽ không hoạt động).
    • Lớp này thậm chí có thể tiêm MyEntityRepositoryqua @Autowiredđể sử dụng trong các phương thức tùy chỉnh.


Thí dụ:

Lớp thực thể:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Giao diện kho lưu trữ:

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

}

Phương thức thực hiện tùy chỉnh bean:

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

}

Những nhược điểm nhỏ tôi đã xác định là:

  • Các phương thức tùy chỉnh trong Impllớp được đánh dấu là không được sử dụng bởi trình biên dịch, do đó, @SuppressWarnings("unused")gợi ý.
  • Bạn có một giới hạn của một Impllớp. (Trong khi thực hiện các giao diện phân đoạn thông thường, các tài liệu cho thấy bạn có thể có nhiều.)

Có một cảnh báo nhỏ trong quá trình thử nghiệm. Nếu bạn cần nó, hãy cho tôi biết và tôi sẽ cập nhật câu trả lời.
acdcjunior

Làm thế nào để Autowire MyEntityRep repositoryImpl đúng cách?
Konstantin Zyubin

@KonstantinZyubin Bạn tự động MyEntityRepositorychứ không phải *Impl.
acdcjunior

4

Nếu bạn muốn có thể thực hiện các thao tác phức tạp hơn, bạn có thể cần quyền truy cập vào các phần bên trong của Spring Data, trong trường hợp đó, hoạt động sau đây (như giải pháp tạm thời của tôi cho 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

Xem xét đoạn mã của bạn, xin lưu ý rằng bạn chỉ có thể chuyển các đối tượng gốc sang phương thức findBy ###, giả sử bạn muốn tải danh sách các tài khoản thuộc về một số chi phí nhất định, một giải pháp là làm điều này,

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

Làm cho tên của bảng được yêu cầu được gọi là lớp Thực thể. Để thực hiện thêm xin vui lòng xem cái này


1
Đây là một lỗi đánh máy trên truy vấn, nó phải là nameoffie l d, tôi không có quyền sửa nó.
BrunoJCM

3

Có một vấn đề khác cần được xem xét ở đây. Một số người mong đợi rằng việc thêm phương thức tùy chỉnh vào kho lưu trữ của bạn sẽ tự động hiển thị chúng dưới dạng dịch vụ REST trong liên kết '/ search'. Thật không may, đây không phải là trường hợp. Mùa xuân hiện tại không hỗ trợ.

Đây là tính năng 'theo thiết kế', phần còn lại của dữ liệu sẽ kiểm tra xem phương thức có phải là phương thức tùy chỉnh không và hiển thị dưới dạng liên kết tìm kiếm REST:

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

Đây là một tuyến đường của Oliver Gierke:

Đây là do thiết kế. Các phương thức kho lưu trữ tùy chỉnh không có phương thức truy vấn vì chúng có thể thực hiện hiệu quả bất kỳ hành vi nào. Do đó, hiện tại chúng tôi không thể quyết định về phương thức HTTP để hiển thị phương thức theo. POST sẽ là tùy chọn an toàn nhất nhưng không phù hợp với các phương thức truy vấn chung (nhận GET).

Để biết thêm chi tiết, xem vấn đề này: https://jira.spring.io/browse/DATAREST-206


Điều đó thật đáng tiếc, tôi đã lãng phí quá nhiều thời gian để cố gắng tìm hiểu, những gì tôi đã làm sai, và cuối cùng, tôi hiểu rằng không có tính năng như vậy. Tại sao họ thậm chí sẽ thực hiện chức năng đó? Để có ít đậu? Để có tất cả các phương pháp dao ở một nơi? Tôi có thể đạt được điều đó theo những cách khác. Có ai biết mục tiêu "thêm hành vi vào các kho duy nhất" là gì không?
Skeeve 8/2/2016

Bạn có thể hiển thị bất kỳ phương thức lưu trữ nào thông qua REST bằng cách thêm @RestResource(path = "myQueryMethod")chú thích vào phương thức. Câu trích dẫn ở trên chỉ nói rằng Spring không biết bạn muốn nó được ánh xạ như thế nào (tức là NHẬN so với POST, v.v.) nên bạn phải chỉ định nó thông qua chú thích.
GreenGiant

1

Thêm hành vi tùy chỉnh vào tất cả các kho lưu trữ:

Để thêm hành vi tùy chỉnh vào tất cả các kho lưu trữ, trước tiên bạn thêm giao diện trung gian để khai báo hành vi được chia sẻ.

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

    void sharedCustomMethod( ID id );
}

Bây giờ các giao diện kho lưu trữ riêng lẻ của bạn sẽ mở rộng giao diện trung gian này thay vì giao diện Kho lưu trữ để bao gồm các chức năng được khai báo.

Tiếp theo, tạo một triển khai của giao diện trung gian mở rộng lớp cơ sở lưu trữ dành riêng cho công nghệ bền bỉ. Lớp này sau đó sẽ hoạt động như một lớp cơ sở tùy chỉnh cho các proxy kho lưu trữ.

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

Kho dữ liệu mùa xuân Phần I. Tham khảo nhập mô tả hình ảnh ở đây


0

Tôi mở rộng SimpleJpaRep repository:

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

và thêm lớp này vào @EnableJpaRep repositoryries repositoryBaseClass.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.