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

我正在寻找Spring Data JPA。考虑下面的例子,我将得到所有的crud和查找器功能默认工作,如果我想自定义一个查找器,那么也可以在界面本身轻松完成。

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


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

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

243886 次浏览

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

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

参见:

  • < a href = " https://docs.spring.io/spring-data/jpa/docs/current/reference/html/存储库。4.6 Spring数据存储库的自定义实现

  • 请注意,不同版本之间的命名方案发生了变化。详见https://stackoverflow.com/a/52624752/66686

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

public class AccountRepositoryImpl implements AccountRepositoryCustom {


@PersistenceContext
private EntityManager em;


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

如果你想做更复杂的操作,你可能需要访问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 organisationId) {
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);
}
}


}

我使用下面的代码,以访问生成的查找方法从我的自定义实现。通过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);
}
}

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

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

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

这里还有一个问题需要考虑。有些人希望将自定义方法添加到存储库中将自动将其作为'/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

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

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是Customer的属性,那么Spring将自动为你定义方法。

公认的答案是可行的,但有三个问题:

  • 它在将自定义实现命名为AccountRepositoryImpl时使用了一个未记录的Spring Data特性。文档明确指出它必须被称为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
}
}

I扩展了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中。

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

正如在记录功能中指定的那样,Impl后缀允许我们有这样的干净的解决方案:

  • 在你的常规@Repository接口中定义自定义方法,比如MyEntityRepository(除了你的Spring Data方法)
  • 在任何地方(甚至不需要在同一个包中)创建一个类MyEntityRepositoryImpl (Impl后缀是魔术),只实现自定义方法注释这样的类@Component** (@Repository 不会工作)。
    • 这个类甚至可以通过@Autowired注入MyEntityRepository以用于自定义方法。

例子:

实体类(为完整起见):

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就会加载它。

我使用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;
}

我喜欢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失败了。我不认为它期望看到Impl与PagingAndSortingRepository间接关联。

我用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.
}
}