194

我正在研究 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 添加一个完整的自定义方法及其实现?由于它是一个接口,我无法在那里实现该方法。

4

14 回答 14

327

您需要为您的自定义方法创建一个单独的接口:

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() { ... }
}

也可以看看:

于 2012-08-09T10:18:05.200 回答
76

除了 axtavt 的回答之外,如果您需要它来构建查询,请不要忘记您可以在自定义实现中注入实体管理器:

public class AccountRepositoryImpl implements AccountRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    public void customMethod() { 
        ...
        em.createQuery(yourCriteria);
        ...
    }
}
于 2013-03-11T14:51:40.543 回答
27

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

  • 在将自定义实现命名为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
    }
}
于 2018-07-30T22:38:03.370 回答
24

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

文档功能中所述,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;
}

仓库界面:

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

}

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

}

用法:

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

仅此而已,除了您已经拥有的 Spring Data 存储库之外,不需要任何接口。


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

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

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

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

如果 objectHobby是 Customer 的属性,那么 Spring 会自动为您定义方法。

于 2016-05-20T18:56:26.533 回答
6

我使用以下代码从我的自定义实现中访问生成的查找方法。通过 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);
    }
}
于 2015-04-24T05:13:47.080 回答
5

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

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

使要查询的表名与Entity类相同。有关进一步的实现,请查看

于 2015-05-17T14:55:30.563 回答
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);
        }
    }

}
于 2013-10-31T15:16:29.073 回答
3

这里还有一个问题需要考虑。有些人希望向您的存储库添加自定义方法会自动将它们公开为“/search”链接下的 REST 服务。不幸的是,情况并非如此。Spring 目前不支持。

这是“按设计”功能,spring data rest 显式检查方法是否是自定义方法,并且不会将其公开为 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

于 2015-07-21T08:35:03.790 回答
2

我喜欢 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);
    }
}

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

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

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

}

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

于 2020-12-09T02:07:16.290 回答
2

向所有存储库添加自定义行为:

要将自定义行为添加到所有存储库,您首先添加一个中间接口来声明共享行为。

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

在此处输入图像描述

于 2020-06-19T17:39:49.577 回答
1

我使用mongo和spring遇到了这个问题。所以假设我们使用 MongoRepository 来提供基本的 crud 操作,假设我们需要使用 mongoTemplate 实现一些自定义条件查询操作。为了实现一个接口来为 crud 和 custom 注入存储库,我们需要指定:

自定义界面:

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

UserRepository 接口“必须”首先扩展 UserCustomRepository,然后是 MongoRepository

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

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

让我们实现一些服务 - 这里我们只注入 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.
 }
}
于 2021-05-07T09:49:29.763 回答
0

我使用 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;
}
于 2020-09-08T16:53:43.523 回答
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。

于 2018-11-12T23:39:40.413 回答