Як додати користувацький метод до JPA Spring Data


160

Я дивлюся у JPA Spring Data. Розглянемо нижченаведений приклад, де я заробляю, що всі функції сурових і шукальних функцій працюють за замовчуванням, і якщо я хочу налаштувати пошук, то це також можна легко зробити в самому інтерфейсі.

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

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

Мені хотілося б знати, як я можу додати повний спеціальний метод з його реалізацією для вищевказаного облікового запису? З його інтерфейсу я не можу реалізувати метод там.

Відповіді:


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
Чи може ця спеціальна реалізація ввести фактичне сховище, щоб воно могло використовувати методи, визначені там? Зокрема, я хотів би посилатись на різні функції пошуку *, визначені в інтерфейсі репозиторію, для реалізації пошуку більш високого рівня. Оскільки функції знаходження * () не мають реалізації, я не можу оголосити їх у користувацькому інтерфейсі або Impl-класі.
JBCP

18
Я дотримувався цієї відповіді, на жаль, зараз Spring Data намагається знайти властивість "customMethod" на моєму об'єкті "Account", оскільки він намагається автоматично генерувати запит для всіх методів, визначених у AccountRepository. Будь-який спосіб зупинити це?
Нік Фут

41
@NickFoote зауважте, що назва класу, який ви реалізуєте у вашому сховищі, має бути: AccountRepositoryImplnot: 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.
Роберт Хант

6
Так, дивіться, що мій попередній коментар про це не працює, якщо ви продовжуєте. QueryDslRepositorySupportВи також повинні вводити сховище через ін'єкцію поля чи сетера, а не введення конструктора, інакше він не зможе створити боб. Це, здається, працює, але рішення виглядає трохи "брудно", я не впевнений, чи є якісь плани покращити, як це працює від команди Spring Data.
Роберт Хант

72

На додаток до axtavt в відповідь , не забудьте , що ви можете вводити Entity менеджер в призначеній для користувача реалізації , якщо вам це потрібно , щоб побудувати свої запити:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

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

10
Однак, завдяки, я хочу знати, як використовувати Pageable та Page у користувацькій реалізації. Будь-які входи?
Maker Wand Maker

17

Прийнята відповідь працює, але має три проблеми:

  • Він використовує функцію незадокументованих Spring Data при іменуванні користувацької реалізації як AccountRepositoryImpl. У документації чітко зазначено, що її потрібно викликати AccountRepositoryCustomImpl, ім'я користувальницького інтерфейсу плюсImpl
  • Ви не можете використовувати лише введення конструктора @Autowired, які вважаються поганою практикою
  • У вас є кругова залежність всередині користувацької реалізації (саме тому ви не можете використовувати введення конструктора).

Я знайшов спосіб зробити його ідеальним, хоча не без використання іншої недокументованої функції Spring Data:

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). Інакше весна поскаржилася, що у моєму *Implконструкторі було 2 варіанти бобів для ін’єкцій .
козел

так у чому полягає використання
облікового записуReRepository

@KalpeshSoni методи з обох AccountRepositoryBasicі AccountRepositoryCustomбудуть доступні через ін'єкціюAccountRepository
geg

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є власністю Замовника, тоді Spring автоматично визначить метод для вас.


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?
Костянтин Зюбін

@KonstantinZyubin Ви автопровід 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

Враховуючи фрагмент коду, зауважте, що ви можете передавати Native-об'єкти лише методу findBy ###, скажімо, що ви хочете завантажити список облікових записів, який належить певним клієнтам. Одне рішення - це зробити,

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

Зробити позов на ім'я таблиці, яку потрібно запитувати, це те саме, що і клас Entity. Для подальшого впровадження, будь ласка, подивіться на це


1
Це помилка запиту, це має бути nameoffie l d, я не маю належного права це виправляти.
BrunoJCM

3

Тут слід розглянути ще одне питання. Деякі люди очікують, що додавання користувальницького методу у ваше сховище автоматично відкриє їх як послуги REST за посиланням "/ search". На жаль, це не так. Наразі весна не підтримує це.

Ця функція "за задумом", весняний відпочинок даних явно перевіряє, чи метод є спеціальним методом, і не виставляє його як REST-посилання пошуку:

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

Це цитата Олівера Герке:

Це за дизайном. Спеціальні методи сховища не є методами запитів, оскільки вони можуть ефективно реалізувати будь-яку поведінку. Таким чином, наразі нам неможливо визначитися з методом HTTP для викриття методу. POST був би найбезпечнішим варіантом, але це не відповідає загальним методам запитів (які отримують GET).

Детальніше дивіться у цьому номері: https://jira.spring.io/browse/DATAREST-206


Це прикро, я витратив стільки часу, намагаючись з’ясувати, що я зробив неправильно, і, нарешті, розумію, що такої особливості немає. Чому вони взагалі реалізують цю функціональність? Щоб було менше квасолі? Щоб усі методи дао були в одному місці? Я міг би досягти цього іншими способами. Хтось знає, яка мета "додати поведінку до одиночних сховищ"?
Skeeve

Ви можете відкрити будь-які методи сховища за допомогою REST, просто додавши @RestResource(path = "myQueryMethod")анотацію до методу. Цитата вище лише зазначає, що Spring не знає, як ви хочете її відобразити (наприклад, GET vs POST тощо), тож вам слід вказати це за допомогою анотації.
GreenGiant

1

Додавання користувацької поведінки до всіх сховищ:

Щоб додати користувацьку поведінку до всіх сховищ, спочатку додайте проміжний інтерфейс, щоб оголосити спільну поведінку.

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

    void sharedCustomMethod( ID id );
}

Тепер ваші індивідуальні інтерфейси сховища розширять цей проміжний інтерфейс замість інтерфейсу сховища, щоб включити заявлену функціональність.

Далі створіть реалізацію проміжного інтерфейсу, який розширює базовий клас сховища для конкретних технологій. Потім цей клас буде діяти як спеціальний базовий клас для проксі-серверів сховища.

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

Весняні сховища даних, частина I. Довідка введіть тут опис зображення


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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.