简体   繁体   English

如何将自定义方法添加到 Spring 数据 JPA

[英]How to add custom method to Spring Data JPA

I am looking into Spring Data JPA. Consider the below example where I will get all the crud and finder functionality working by default and if I want to customize a finder then that can be also done easily in the interface itself.我正在查看 Spring 数据 JPA。请考虑下面的示例,其中我将默认使用所有 crud 和查找器功能,如果我想自定义查找器,那么也可以在界面本身中轻松完成。

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

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

I would like to know how can I add a complete custom method with its implementation for the above AccountRepository?我想知道如何为上述 AccountRepository 添加一个完整的自定义方法及其实现? Since its an Interface I cannot implement the method there.由于它是一个接口,我无法在那里实现该方法。

You need to create a separate interface for your custom methods:您需要为您的自定义方法创建一个单独的接口:

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

public interface AccountRepositoryCustom {
    public void customMethod();
}

and provide an implementation class for that interface:并为该接口提供一个实现类:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

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

    public void customMethod() { ... }
}

See also:也可以看看:

In addition to axtavt's answer , don't forget you can inject Entity Manager in your custom implementation if you need it to build your queries:除了 axtavt 的回答之外,如果您需要它来构建查询,请不要忘记您可以在自定义实现中注入实体管理器:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

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

The accepted answer works, but has three problems:接受的答案有效,但存在三个问题:

  • It uses an undocumented Spring Data feature when naming the custom implementation as AccountRepositoryImpl .在将自定义实现命名为AccountRepositoryImpl时,它使用未记录的 Spring Data 功能。 The documentation clearly states that it has to be called AccountRepositoryCustomImpl , the custom interface name plus Impl 文档明确指出它必须被称为AccountRepositoryCustomImpl ,自定义接口名称加上Impl
  • You cannot use constructor injection, only @Autowired , that are considered bad practice您不能使用构造函数注入,只能使用@Autowired ,这被认为是不好的做法
  • You have a circular dependency inside of the custom implementation (that's why you cannot use constructor injection).您在自定义实现中有循环依赖(这就是您不能使用构造函数注入的原因)。

I found a way to make it perfect, though not without using another undocumented Spring Data feature:我找到了一种使其完美的方法,尽管不是不使用另一个未记录的 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
    }
}

There's a slightly modified solution that does not require additional interfaces.有一个稍微修改的解决方案,不需要额外的接口。

As specificed in the documented functionality , the Impl suffix allows us to have such clean solution:正如文档功能中所述, Impl后缀允许我们拥有这样干净的解决方案:

  • Define in you regular @Repository interface, say MyEntityRepository the custom methods (in addition to your Spring Data methods)你经常定义@Repository接口,说MyEntityRepository自定义方法(除了你的Spring数据的方法)
  • Create a class MyEntityRepositoryImpl (the Impl suffix is the magic) anywhere (doesn't even need to be in the same package) that implements the custom methods only and annotate such class with @Component ** ( @Repository will not work).创建一个类MyEntityRepositoryImpl (在Impl后缀是魔法)的任何地方(甚至不必是在同一个包),其仅实现自定义方法注释等类@Component **( @Repository将无法正常工作)。
    • This class can even inject MyEntityRepository via @Autowired for use in the custom methods.此类甚至可以通过@Autowired注入MyEntityRepository以在自定义方法中使用。

Example:例子:

Entity class (for completeness):实体类(为了完整性):

package myapp.domain.myentity;
@Entity
public class MyEntity {
    @Id     private Long id;
    @Column private String comment;
}

Repository interface:仓库接口:

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

}

Custom methods implementation bean:自定义方法实现 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;
    }

}

Usage:用法:

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

And that's all, no need for any interfaces other than the Spring Data repo one you already have.就是这样,除了您已经拥有的 Spring Data repo 之外,不需要任何接口。


The only possible drawbacks I identified are:我发现的唯一可能的缺点是:

  • The custom methods in the Impl class are marked as unused by the compiler, thus the @SuppressWarnings("unused") suggestion. Impl类中的自定义方法被编译器标记为未使用,因此@SuppressWarnings("unused")建议。
  • You have a limit of one Impl class.你有一个Impl类的限制。 (Whereas in the regular fragment interfaces implementation the docs suggest you could have many.) (而在常规片段接口实现中,文档建议您可以有很多。)
  • If you place the Impl class at a different package and your test uses only @DataJpaTest , you have to add @ComponentScan("package.of.the.impl.clazz") to your test, so Spring loads it.如果您将Impl类放在不同的包中并且您的测试仅使用@DataJpaTest ,则必须将@ComponentScan("package.of.the.impl.clazz")到您的测试中,以便 Spring 加载它。

This is limited in usage, but for simple custom methods you can use default interface methods like:这在使用上受到限制,但对于简单的自定义方法,您可以使用默认接口方法,例如:

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

EDIT:编辑:

In this spring tutorial it is written:这个春季教程中,它是这样写的:

Spring Data JPA also allows you to define other query methods by simply declaring their method signature. Spring Data JPA 还允许您通过简单地声明它们的方法签名来定义其他查询方法。

So it is even possible to just declare method like:所以甚至可以像这样声明方法:

Customer findByHobby(Hobby personHobby);

and if object Hobby is a property of Customer then Spring will automatically define method for you.如果对象Hobby是 Customer 的一个属性,那么 Spring 将自动为您定义方法。

Im using the following code in order to access generated find methods from my custom implementation.我使用以下代码从我的自定义实现中访问生成的 find 方法。 Getting the implementation through the bean factory prevents circular bean creation problems.通过 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);
    }
}

Considering your code snippet, please note that you can only pass Native objects to the findBy### method, lets say you want to load a list of accounts that belongs certain costumers, one solution is to do this,考虑到您的代码片段,请注意您只能将 Native 对象传递给 findBy### 方法,假设您要加载属于某些客户的帐户列表,一种解决方案是执行此操作,

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

Make sue the name of the table to be queried is thesame as the Entity class.要求查询的表名与Entity类相同。 For further implementations please take a look at this对于进一步的实现,请看这个

If you want to be able to do more sophisticated operations you might need access to Spring Data's internals, in which case the following works (as my interim solution to DATAJPA-422 ):如果您希望能够执行更复杂的操作,您可能需要访问 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);
        }
    }

}

There is another issue to be considered here.这里还有一个问题需要考虑。 Some people expect that adding custom method to your repository will automatically expose them as REST services under '/search' link.有些人希望将自定义方法添加到您的存储库会自动将它们公开为 '/search' 链接下的 REST 服务。 This is unfortunately not the case.不幸的是,情况并非如此。 Spring doesn't support that currently. Spring 目前不支持。

This is 'by design' feature, spring data rest explicitly checks if method is a custom method and doesn't expose it as a REST search link:这是“按设计”功能,spring data rest 显式检查方法是否是自定义方法,并且不会将其公开为 REST 搜索链接:

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

This is a qoute of Oliver Gierke:这是 Oliver Gierke 的一句话:

This is by design.这是设计使然。 Custom repository methods are no query methods as they can effectively implement any behavior.自定义存储库方法不是查询方法,因为它们可以有效地实现任何行为。 Thus, it's currently impossible for us to decide about the HTTP method to expose the method under.因此,我们目前无法决定公开该方法的 HTTP 方法。 POST would be the safest option but that's not in line with the generic query methods (which receive GET). POST 将是最安全的选项,但这与通用查询方法(接收 GET)不一致。

For more details see this issue: https://jira.spring.io/browse/DATAREST-206有关更多详细信息,请参阅此问题: https : //jira.spring.io/browse/DATAREST-206

Adding custom behavior to all repositories :向所有存储库添加自定义行为:

To add custom behavior to all repositories, you first add an intermediate interface to declare the shared behavior.要将自定义行为添加到所有存储库,您首先要添加一个中间接口来声明共享行为。

public interface MyRepository <T, ID extends Serializable> extends JpaRepository<T, ID>
{
    
    void sharedCustomMethod( ID id );
}

Now your individual repository interfaces will extend this intermediate interface instead of the Repository interface to include the functionality declared.现在,您的各个存储库接口将扩展此中间接口而不是 Repository 接口以包含声明的功能。

Next, create an implementation of the intermediate interface that extends the persistence technology-specific repository base class.接下来,创建一个中间接口的实现,该接口扩展了持久化技术特定的存储库基类。 This class will then act as a custom base class for the repository proxies.此类将充当存储库代理的自定义基类。

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 Data Repositories Part I. Reference Spring Data Repositories Part I. 参考

在此处输入图片说明

I extends the SimpleJpaRepository:我扩展了 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;
    }
}

and adds this class to @EnableJpaRepositoryries repositoryBaseClass.并将此类添加到@EnableJpaRepositoryries repositoryBaseClass。

Side note: 边注:

When creating Custom Implementations for Spring Data Repositories: 为Spring Data Repositories创建自定义实现时:

The most important part of the class name that corresponds to the fragment interface is the Impl postfix. 与片段接口对应的类名最重要的部分是Impl后缀。

I use SimpleJpaRepository as the base class of repository implementation and add custom method in the interface,eg:我使用 SimpleJpaRepository 作为存储库实现的基类并在接口中添加自定义方法,例如:

public interface UserRepository  {
    User FindOrInsert(int userId);
}

@Repository
public class UserRepositoryImpl extends SimpleJpaRepository implements UserRepository {

    private RedisClient redisClient;

    public UserRepositoryImpl(RedisClient redisClient, EntityManager em) {
        super(User.class, em);
        this.redisClient = redisClient;
    }


@Override
public User FindOrInsert(int userId) {

    User u = redisClient.getOrSet("test key.. User.class, () -> {
        Optional<User> ou = this.findById(Integer.valueOf(userId));
        return ou.get();
    });
    …………
    return u;
}

I liked Danila's solution and started using it but nobody else on the team liked having to create 4 classes for each repository.我喜欢 Danila 的解决方案并开始使用它,但团队中没有其他人喜欢为每个存储库创建 4 个类。 Danila's solution is the only one here that let's you use the Spring Data methods in the Impl class. Danila 的解决方案是这里唯一一个让您在 Impl 类中使用 Spring Data 方法的解决方案。 However, I found a way to do it with just a single class:但是,我找到了一种方法,只需一个类即可:

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

You just need some way of getting access to your db bean (in this example, MongoOperations).您只需要某种方式来访问您的 db bean(在本例中为 MongoOperations)。 MongoAccess provides that access to all of your repositories by retrieving the bean directly: MongoAccess 通过直接检索 bean 提供对所有存储库的访问:

public interface MongoAccess {
    default MongoOperations getMongoOperations() {
        return BeanAccessor.getSingleton(MongoOperations.class);
    }
}

Where BeanAccessor is: 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;
    }

}

Unfortunately, you can't @Autowire in an interface.不幸的是,您不能在界面中使用 @Autowire。 You could autowire the bean into a MongoAccessImpl and provide a method in the interface to access it, but Spring Data blows up.您可以将 bean 自动装配到 MongoAccessImpl 并在接口中提供一个方法来访问它,但 Spring Data 会崩溃。 I don't think it expects to see an Impl associated even indirectly with PagingAndSortingRepository.我认为它不希望看到与 PagingAndSortingRepository 间接关联的 Impl。

I faced with this using mongo and spring.我使用 mongo 和 spring 来解决这个问题。 So let's assume we use MongoRepository to provided base crud operations, and let's say we need to implement some custom criteria query operation using mongoTemplate.因此,假设我们使用 MongoRepository 来提供基本的 crud 操作,并且假设我们需要使用 mongoTemplate 实现一些自定义条件查询操作。 To achieve one interface to inject repository for crud and custom we need to specify:要实现一个接口来为 crud 和 custom 注入存储库,我们需要指定:

Custom interface:自定义界面:

public interface UserCustomRepository {
 List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest);
}

UserRepository interface 'must' first extends UserCustomRepository and then MongoRepository UserRepository 接口“必须”首先扩展 UserCustomRepository 然后是 MongoRepository

@Repository
public interface UserRepository extends UserCustomRepository, MongoRepository<User, ObjectId> {
}

UserRepositoryImpl must have the same name as what crud interface with *Impl suffix. UserRepositoryImpl 必须与带有 *Impl 后缀的 crud 接口具有相同的名称。

@Component
@NoArgsConstructor
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class UserRepositoryImpl implements UserCustomRepository {

 private MongoTemplate mongoTemplate;

 @Override
 public List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest){
  //some impl
 }
}

Let's impl some service - here we inject only UserRepository interface and use methods from crud repository and custom class impl.让我们实现一些服务——这里我们只注入 UserRepository 接口并使用来自 crud 存储库和自定义类 impl 的方法。

@Service
@NoArgsConstructor
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class UserService {

 private UserRepository userReposityry;

 public List<User> getUserByCriteria(UserCriteriaRequest request) {
   userRepository.findById(request.getUserId); // Crud repository method
   userRepository.findAllUsersBySomeCriteria(request); // custom method.
 }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM