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

Sha*_*dav 144 java spring-data spring-data-jpa

我正在研究Spring Data JPA.考虑下面的示例,我将默认使用所有crud和finder功能,如果我想自定义查找器,那么也可以在界面本身轻松完成.

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

  @Query("<JPQ statement here>")
  List<Account> findByCustomer(Customer customer);
}
Run Code Online (Sandbox Code Playgroud)

我想知道如何为上述AccountRepository添加一个完整的自定义方法及其实现?由于它的接口我无法在那里实现该方法.

axt*_*avt 265

您需要为自定义方法创建单独的界面:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}
Run Code Online (Sandbox Code Playgroud)

并为该接口提供实现类:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}
Run Code Online (Sandbox Code Playgroud)

也可以看看:

  • @NickFoote请注意,您实现存储库的类的名称应该是:`AccountRepositoryImpl` not:`AccountRepositoryCustomImpl`等 - 它是非常严格的命名约定. (38认同)
  • 这个自定义实现可以注入实际的存储库,所以它可以使用那里定义的方法吗?具体来说,我想在更高级别的find实现中引用Repository接口中定义的各种find*函数.由于那些find*()函数没有实现,我不能在Custom接口或Impl类中声明它们. (19认同)
  • 我已经按照这个答案,不幸的是现在Spring Data试图在我的"帐户"对象上找到属性"customMethod",因为它试图自动为AccountRepository上定义的所有方法生成查询.有办法阻止这个吗? (18认同)
  • 是的,如果您正在扩展`QueryDslRepositorySupport,请查看我之前关于它的评论不起作用.您还必须通过字段或setter注入注入存储库而不是构造函数注入,否则它将无法创建bean.它似乎确实有效,但解决方案感觉有点'脏',我不确定是否有任何计划来改善Spring Data团队的工作方式. (6认同)
  • @ wired00我认为它确实创建了一个循环引用,我无法看到@JBCP是如何使它工作的.当我尝试做类似的事情时,我最终得到一个例外:`创建名为'accountRepositoryImpl'的bean时出错:名称为'accountRepositoryImpl'的Bean已作为循环引用的一部分注入其原始版本的其他bean [accountRepository]中,但最终被包裹了 (5认同)
  • @ end-user:是的,你的impl对象可以注入存储库,没问题 (4认同)
  • *Impl文件名很重要.我有一个与Repository接口同名的实现类(在另一个包中),Spring没有启动. (2认同)
  • @JBCP,谢谢,这正是我必须解决的问题.我想扩展一个repo,我已经添加了自定义查找方法,例如`findByFooAndBar()`.我没想到只是将存储库(已经扩展了这个)注入到自定义impl中,因为感觉就像创建一个循环引用或什么?但它的工作原理...... (2认同)
  • 将存储库注入自定义实现在 Spring Boot 1.4.3 中不起作用。您将收到 Bean 循环依赖错误并且应用程序无法启动。相反,您可以注入实体管理器(感谢@jelies)和实体信息(感谢@NealeU) (2认同)
  • 通过在自定义方法实现中使用 Lazy 注释和 Autowired 注释解决了循环依赖。 (2认同)

jel*_*ies 68

除了axtavt的答案之外,请不要忘记,如果您需要构建查询,可以在自定义实现中注入Entity Manager:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 但是,谢谢,我想知道如何在自定义实现中使用Pageable和Page.有什么投入? (9认同)

acd*_*ior 16

有一个稍微修改的解决方案,不需要额外的接口。

正如文档功能中所述,Impl后缀允许我们拥有这样干净的解决方案:

  • 在您的常规@Repository接口MyEntityRepository中定义自定义方法(除了您的 Spring Data 方法)
  • 创建一个类MyEntityRepositoryImpl(该Impl后缀是魔法)的任何地方(甚至不必是在同一个包),其实现自定义的方法只和注释等类@Component**(@Repository 不会工作)。
    • 此类甚至可以注入MyEntityRepositoryvia@Autowired以在自定义方法中使用。

例子:

实体类(为了完整性):

package myapp.domain.myentity;
@Entity
public class MyEntity {
    @Id     private Long id;
    @Column private String comment;
}
Run Code Online (Sandbox Code Playgroud)

仓库接口:

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);   // custom method, code at *Impl class below

    List<MyEntity> useTheRepo(Long id);  // custom method, code at *Impl class below

}
Run Code Online (Sandbox Code Playgroud)

自定义方法实现 bean:

package myapp.infrastructure.myentity;

@Component // Must be @Component !!
public class MyEntityRepositoryImpl { // must have the exact 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

用法:

// You just autowire the the MyEntityRepository as usual
// (the Impl class is just impl detail, the clients don't even know about it)
@Service
public class SomeService {
    @Autowired
    private MyEntityRepository myEntityRepository;

    public void someMethod(String x, long y) {
        // call any method as usual
        myEntityRepository.findByCommentEndsWith(x);
        myEntityRepository.doSomeHql(y);
    }
}
Run Code Online (Sandbox Code Playgroud)

就是这样,除了您已经拥有的 Spring Data repo 之外,不需要任何接口。


我发现的唯一可能的缺点是:

  • Impl类中的自定义方法被编译器标记为未使用,因此@SuppressWarnings("unused")建议。
  • 你有一个Impl班级的限制。(而在常规片段接口实现中,文档建议您可以有很多。)
  • 如果您将Impl类放在不同的包中并且您的测试仅使用@DataJpaTest,则必须添加@ComponentScan("package.of.the.impl.clazz")到您的测试中,以便 Spring 加载它。

  • 令人惊讶的彻底、详细和有用的答案。肯定应该有更多的赞成票! (3认同)

Tom*_*zyk 10

这在使用上受到限制,但对于简单的自定义方法,您可以使用默认接口方法,如:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

这个春季教程中写道:

Spring Data JPA还允许您通过简单地声明其方法签名来定义其他查询方法.

所以甚至可以声明方法如下:

Customer findByHobby(Hobby personHobby);
Run Code Online (Sandbox Code Playgroud)

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


Dan*_*tov 10

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

  • 将自定义实现命名为时,它将使用未记录的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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 您能提供创建上下文的方式吗?我无法将所有这些放在一起。谢谢。 (2认同)

Pet*_*ler 5

我使用以下代码从我的自定义实现访问生成的查找方法.通过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);
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

考虑到您的代码片段,请注意您只能将 Native 对象传递给 findBy### 方法,假设您要加载属于某些客户的帐户列表,一种解决方案是执行此操作,

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

要求查询的表名与Entity类相同。对于进一步的实现,请看 这个


vir*_*l-g 5

我喜欢 Danila 的解决方案并开始使用它,但团队中没有其他人喜欢为每个存储库创建 4 个类。Danila 的解决方案是这里唯一一个允许您在 Impl 类中使用 Spring Data 方法的解决方案。然而,我找到了一种只用一个类就能做到这一点的方法:

public interface UserRepository extends MongoAccess, PagingAndSortingRepository<User> {

    List<User> getByUsername(String username);


    default List<User> getByUsernameCustom(String username) {
        // Can call Spring Data methods!
        findAll();

        // Can write your own!
        MongoOperations operations = getMongoOperations();
        return operations.find(new Query(Criteria.where("username").is(username)), User.class);
    }
}
Run Code Online (Sandbox Code Playgroud)

您只需要某种方式来访问您的数据库 bean(在本例中为 MongoOperations)。MongoAccess 通过直接检索 bean 提供对所有存储库的访问:

public interface MongoAccess {
    default MongoOperations getMongoOperations() {
        return BeanAccessor.getSingleton(MongoOperations.class);
    }
}
Run Code Online (Sandbox Code Playgroud)

其中 BeanAccessor 是:

@Component
public class BeanAccessor implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static <T> T getSingleton(Class<T> clazz){
        return applicationContext.getBean(clazz);
    }

    public static <T> T getSingleton(String beanName, Class<T> clazz){
        return applicationContext.getBean(beanName, clazz);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanAccessor.applicationContext = applicationContext;
    }

}
Run Code Online (Sandbox Code Playgroud)

不幸的是,你不能在界面中@Autowire。您可以将 bean 自动装配到 MongoAccessImpl 中,并在接口中提供一个方法来访问它,但 Spring Data 会崩溃。我认为它不希望看到与 PagingAndSortingRepository 间接关联的 Impl。