如何向Spring Data JPA添加自定义方法


160

我正在研究Spring Data JPA。考虑下面的示例,默认情况下我将使所有crud和finder功能正常工作,如果我要自定义finder,那么也可以在界面本身中轻松完成。

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

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

我想知道如何为上述AccountRepository的实现添加完整的自定义方法?由于它是一个接口,所以我不能在那里实现该方法。

Answers:


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 *函数。由于这些find *()函数没有实现,因此无法在Custom接口或Impl类中声明它们。
JBCP

18
我已经回答了这个问题,不幸的是,现在Spring Data试图在我的“ Account”对象上找到属性“ customMethod”,因为它试图自动为AccountRepository上定义的所有方法生成查询。有什么办法阻止这个吗?
尼克·富特

41
@NickFoote注意,您实现存储库的类的名称应为:AccountRepositoryImplnot :,AccountRepositoryCustomImpl等等-这是非常严格的命名约定。
至强

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必须通过字段或setter注入而不是构造函数注入来注入存储库,否则它将无法创建Bean。它似乎确实有效,但是该解决方案感觉有点“肮脏”,我不确定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

可接受的答案有效,但是存在三个问题:

  • 将自定义实现命名为时,它将使用未记录的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)。否则spring抱怨有2种选择注入到我的*Impl构造函数中的bean 。
山羊

所以有什么用AccountRepository的
Kalpesh瑞里

@KalpeshSoni从两种方法AccountRepositoryBasicAccountRepositoryCustom将通过注入可用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);

如果object Hobby是Customer的属性,则Spring将自动为您定义方法。


6

我使用以下代码来访问自定义实现中生成的find方法。通过bean工厂获取实现可以防止循环bean创建问题。

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接口中定义,例如MyEntityRepositorySpring Data方法或自定义方法
  • 在仅实现自定义方法的任何地方(甚至不需要在同一包中)创建一个类MyEntityRepositoryImplImpl后缀是魔术),并用** 注释此类(不会@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);

}

定制方法实现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;
    }

}

我发现的小缺点是:

  • Impl该类中的自定义方法被编译器标记为未使用,因此@SuppressWarnings("unused")提出建议。
  • 您只能上一Impl堂课。(而在常规片段接口实现中,文档建议您可能有很多。)

测试期间有一个小警告。如果您需要它,请告诉我,我会更新答案。
acdcjunior

如何正确地自动连接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

考虑到您的代码段,请注意,您只能将本机对象传递给findBy ###方法,假设您要加载属于某些客户的帐户列表,一种解决方案是这样做,

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

使得要查询的表的名称与Entity类相同。为了进一步实现,请看看 这个


1
这是查询中的错字,应该是nameoffie l d,我没有适当的权利来解决它。
BrunoJCM

3

这里还有另一个问题要考虑。有人希望将自定义方法添加到您的存储库将自动在“ / search”链接下将它们公开为REST服务。不幸的是事实并非如此。Spring目前不支持该功能。

这是“按设计”功能,spring数据剩余部分会明确检查method是否是自定义方法,并且不会将其公开为REST搜索链接:

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 '16

您只需通过将@RestResource(path = "myQueryMethod")批注添加到方法即可通过REST公开任何存储库方法。上面的引用只是说明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
    }
}

Spring数据存储库第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。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.