Bagaimana cara menambahkan metode kustom ke Spring Data JPA

160

Saya melihat ke Spring Data JPA. Pertimbangkan contoh di bawah ini di mana saya akan mendapatkan semua fungsi crud dan finder bekerja secara default dan jika saya ingin menyesuaikan finder maka itu juga dapat dilakukan dengan mudah di antarmuka itu sendiri.

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

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

Saya ingin tahu bagaimana cara menambahkan metode kustom lengkap dengan implementasinya untuk AccountRepository di atas? Karena ini sebuah Interface, saya tidak dapat mengimplementasikan metode di sana.

Sharad Yadav
sumber

Jawaban:

290

Anda perlu membuat antarmuka terpisah untuk metode khusus Anda:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

dan menyediakan kelas implementasi untuk antarmuka itu:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

Lihat juga:

axtavt
sumber
21
Bisakah implementasi kustom ini menyuntikkan repositori yang sebenarnya, sehingga dapat menggunakan metode yang didefinisikan di sana? Secara khusus, saya ingin merujuk berbagai fungsi find * yang didefinisikan dalam antarmuka Repositori di implementasi find level yang lebih tinggi. Karena fungsi yang ditemukan * () tidak memiliki implementasi, saya tidak dapat mendeklarasikannya di antarmuka Kustom atau kelas Impl.
JBCP
18
Saya telah mengikuti jawaban ini, sayangnya sekarang Data Musim Semi mencoba menemukan properti "customMethod" pada objek "Akun" saya karena mencoba untuk secara otomatis menghasilkan kueri untuk semua metode yang ditentukan pada RekeningRepository. Ada cara untuk menghentikan ini?
Nick Foote
41
@NickFoote perhatikan bahwa nama kelas yang Anda terapkan repositori Anda harus: AccountRepositoryImpltidak :, AccountRepositoryCustomImpldll. - itu adalah konvensi penamaan yang sangat ketat.
Xeon
5
@ wired00 Saya pikir itu membuat referensi melingkar dan saya tidak bisa melihat bagaimana @JBCP membuatnya bekerja. Ketika saya mencoba dan melakukan sesuatu yang serupa saya berakhir dengan pengecualian: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
Ya, lihat komentar saya sebelumnya tentang itu tidak berfungsi jika Anda memperpanjang QueryDslRepositorySupportAnda juga harus menyuntikkan repositori melalui bidang atau penyetel penyuntikan daripada injeksi konstruktor kalau tidak itu tidak akan dapat membuat kacang. Tampaknya berhasil tetapi solusinya terasa agak 'kotor', saya tidak yakin apakah ada rencana untuk meningkatkan cara kerjanya dari tim Data Spring.
Robert Hunt
72

Selain jawaban axtavt , jangan lupa Anda bisa menyuntikkan Entity Manager dalam implementasi kustom Anda jika Anda membutuhkannya untuk membangun kueri Anda:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}
jelies
sumber
10
Terima kasih, bagaimanapun, saya ingin tahu cara menggunakan Pageable dan Page dalam implementasi kustom. Ada masukan?
Pembuat Tongkat
17

Jawaban yang diterima berfungsi, tetapi memiliki tiga masalah:

  • Ini menggunakan fitur Data Spring tidak berdokumen ketika penamaan implementasi kustom sebagai AccountRepositoryImpl. The dokumentasi jelas menyatakan bahwa mereka telah disebut AccountRepositoryCustomImpl, nama antarmuka kustom ditambahImpl
  • Anda tidak dapat menggunakan injeksi konstruktor, hanya saja @Autowired, yang dianggap praktik buruk
  • Anda memiliki ketergantungan melingkar di dalam implementasi kustom (itu sebabnya Anda tidak bisa menggunakan injeksi konstruktor).

Saya menemukan cara untuk membuatnya sempurna, meskipun bukan tanpa menggunakan fitur Data Spring tanpa dokumen lainnya:

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
    }
}
Danila Piatov
sumber
Ini berhasil. Saya ingin menekankan pentingnya nama parameter dalam konstruktor harus mengikuti konvensi dalam jawaban ini (harus accountRepositoryBasic). Kalau tidak, pegas mengeluh karena ada 2 pilihan kacang untuk injeksi ke *Implkonstruktor saya .
kambing
jadi apa gunanya AccountRepository
Kalpesh Soni
@KalpeshSoni metode dari kedua AccountRepositoryBasicdan AccountRepositoryCustomakan tersedia melalui disuntikkanAccountRepository
geg
1
Bisakah Anda memberikan cara konteksnya dibuat? Saya tidak bisa menggabungkan semuanya. Terima kasih.
franta kocourek
12

Ini terbatas dalam penggunaan, tetapi untuk metode kustom sederhana Anda dapat menggunakan metode antarmuka standar seperti:

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", "[email protected]", 679856885, "Rzeszów", "Podkarpackie", "35-061", "Zamknięta 12"),
            new Customer("Adrian", "Mularczyk", "[email protected]", 867569344, "Krosno", "Podkarpackie", "32-442", "Hynka 3/16"),
            new Customer("Kazimierz", "Dejna", "[email protected]", 996435876, "Jarosław", "Podkarpackie", "25-122", "Korotyńskiego 11"),
            new Customer("Celina", "Dykiel", "[email protected]", 947845734, "Żywiec", "Śląskie", "54-333", "Polna 29")
        };

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

EDIT:

Dalam tutorial musim semi ini tertulis:

Spring Data JPA juga memungkinkan Anda untuk menentukan metode kueri lainnya hanya dengan mendeklarasikan tanda tangan metode mereka.

Jadi bahkan mungkin untuk mendeklarasikan metode seperti:

Customer findByHobby(Hobby personHobby);

dan jika objek Hobbyadalah properti Pelanggan maka Spring akan secara otomatis menentukan metode untuk Anda.

Tomasz Mularczyk
sumber
6

Saya menggunakan kode berikut untuk mengakses metode menemukan yang dihasilkan dari implementasi kustom saya. Mendapatkan implementasi melalui pabrik kacang mencegah masalah pembuatan kacang bundar.

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);
    }
}
Peter Rietzler
sumber
5

Seperti yang dijelaskan dalam fungsionalitas yang didokumentasikan , Implakhiran memungkinkan kita untuk memiliki solusi bersih:

  • Tentukan dalam @Repositoryantarmuka, katakanlahMyEntityRepository , baik metode Data Musim Semi atau metode khusus
  • Buat kelas MyEntityRepositoryImpl( Implakhiran adalah keajaiban) di mana saja (bahkan tidak perlu berada dalam paket yang sama) yang mengimplementasikan metode khusus saja dan membubuhi keterangan kelas tersebut dengan @Component** ( @Repository tidak akan berfungsi).
    • Kelas ini bahkan dapat menyuntikkan MyEntityRepositorymelalui @Autowireduntuk digunakan dalam metode khusus.


Contoh:

Kelas entitas:

package myapp.domain.myentity;

@Entity
public class MyEntity {

    @Id
    private Long id;

    @Column
    private String comment;

}

Antarmuka repositori:

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

}

Kacang penerapan metode khusus:

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

}

Kelemahan kecil yang saya identifikasi adalah:

  • Metode kustom di Implkelas ditandai sebagai tidak digunakan oleh kompiler, dengan demikian@SuppressWarnings("unused") sarannya.
  • Anda memiliki batasan satu Implkelas. (Sedangkan dalam implementasi antarmuka fragmen biasa , dokumen menyarankan Anda dapat memiliki banyak.)
acdcjunior
sumber
Ada peringatan kecil selama pengujian. Jika Anda membutuhkannya, beri tahu saya dan saya akan memperbarui jawabannya.
acdcjunior
bagaimana cara autowire MyEntityRepositoryImpl dengan benar?
Konstantin Zyubin
@KonstantinZyubin Anda autowire MyEntityRepository, bukan *Impl.
acdcjunior
4

Jika Anda ingin dapat melakukan operasi yang lebih canggih, Anda mungkin memerlukan akses ke internal Spring Data, dalam hal ini bekerja sebagai berikut (sebagai solusi sementara saya untuk 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);
        }
    }

}
NealeU
sumber
4

Mempertimbangkan cuplikan kode Anda, harap dicatat bahwa Anda hanya dapat meneruskan objek Asli ke metode findBy ###, katakanlah Anda ingin memuat daftar akun yang termasuk pelanggan tertentu, salah satu solusinya adalah melakukan ini,

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

Buat menuntut nama tabel yang akan ditanyakan sama dengan kelas Entity. Untuk implementasi lebih lanjut, silakan lihat ini

Samba
sumber
1
Ini adalah salah ketik pada permintaan, itu harus nameoffie l , saya tidak punya hak yang tepat untuk memperbaikinya.
BrunoJCM
3

Ada masalah lain yang harus dipertimbangkan di sini. Beberapa orang berharap bahwa menambahkan metode khusus ke repositori Anda akan secara otomatis mengekspos mereka sebagai layanan REST di bawah tautan '/ cari'. Sayangnya ini tidak terjadi. Spring tidak mendukung itu saat ini.

Ini adalah fitur 'menurut desain', pegas data sisanya secara eksplisit memeriksa apakah metode adalah metode khusus dan tidak memaparkannya sebagai tautan pencarian REST:

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

Ini adalah qoute dari Oliver Gierke:

Ini dengan desain. Metode repositori kustom bukanlah metode kueri karena mereka dapat secara efektif menerapkan perilaku apa pun. Dengan demikian, saat ini mustahil bagi kami untuk memutuskan tentang metode HTTP untuk mengekspos metode di bawah. POST akan menjadi opsi teraman tetapi itu tidak sejalan dengan metode kueri umum (yang menerima GET).

Untuk detail lebih lanjut lihat masalah ini: https://jira.spring.io/browse/DATAREST-206

Lukasz Magiera
sumber
Sangat disayangkan, saya sudah membuang banyak waktu untuk mencari tahu, apa yang saya buat salah, dan akhirnya, saya mengerti bahwa tidak ada fitur seperti itu. Mengapa mereka bahkan mengimplementasikan fungsi itu? Lebih sedikit kacang? Untuk memiliki semua metode dao di satu tempat? Saya bisa mencapai itu dengan cara lain. Adakah yang tahu apa tujuan dari "menambahkan perilaku ke repositori tunggal" fitur?
Skeeve
Anda dapat mengekspos metode repositori apa pun melalui REST hanya dengan menambahkan @RestResource(path = "myQueryMethod")anotasi ke metode tersebut. Kutipan di atas hanya menyatakan bahwa Spring tidak tahu bagaimana Anda ingin memetakannya (mis. GET vs POST dll.) Sehingga terserah Anda untuk menentukannya melalui anotasi.
GreenGiant
1

Menambahkan perilaku khusus ke semua repositori:

Untuk menambahkan perilaku kustom ke semua repositori, Anda pertama-tama menambahkan antarmuka perantara untuk mendeklarasikan perilaku bersama.

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

    void sharedCustomMethod( ID id );
}

Sekarang masing-masing antarmuka repositori Anda akan memperluas antarmuka perantara ini alih-alih antarmuka Repositori untuk memasukkan fungsi yang dideklarasikan.

Selanjutnya, buat implementasi antarmuka perantara yang memperluas kelas dasar repositori spesifik teknologi persistensi. Kelas ini kemudian akan bertindak sebagai kelas basis kustom untuk proksi repositori.

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

Repositori Data Musim Semi Bagian I. Referensi masukkan deskripsi gambar di sini

Ali Yeganeh
sumber
0

Saya memperluas penyimpanan 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;
    }
}

dan menambahkan kelas ini ke @EnableJpaRepositoryries repositoryBaseClass.

Devilluminati
sumber