我正在研究 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 添加一个完整的自定义方法及其实现?由于它是一个接口,我无法在那里实现该方法。
您需要为您的自定义方法创建一个单独的接口:
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() { ... }
}
也可以看看:
4.6 Spring Data Repository 的自定义实现
请注意,命名方案在版本之间已更改。有关详细信息,请参阅 https://stackoverflow.com/a/52624752/66686。
除了 axtavt 的 answer,如果您需要它来构建查询,请不要忘记您可以在自定义实现中注入 Entity Manager:
public class AccountRepositoryImpl implements AccountRepositoryCustom {
@PersistenceContext
private EntityManager em;
public void customMethod() {
...
em.createQuery(yourCriteria);
...
}
}
有一个稍微修改过的解决方案,不需要额外的接口。
如 documented functionality 中所述,Impl
后缀允许我们拥有这样干净的解决方案:
在你的常规 @Repository 接口中定义自定义方法(除了你的 Spring Data 方法)
在仅实现自定义方法的任何地方(甚至不需要在同一个包中)创建一个 MyEntityRepositoryImpl 类(Impl 后缀是魔法),并使用 @Component** 注释此类(@Repository 不起作用)。此类甚至可以通过 @Autowired 注入 MyEntityRepository 以在自定义方法中使用。
此类甚至可以通过 @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 加载它。
MyEntityRepository
,而不是 *Impl
。
接受的答案有效,但存在三个问题:
在将自定义实现命名为 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
}
}
accountRepositoryBasic
)。否则 spring 抱怨有 2 个 bean 选择注入到我的 *Impl
构造函数中。
AccountRepositoryBasic
和 AccountRepositoryCustom
中的方法将通过注入的 AccountRepository
可用
这在使用上是有限的,但是对于简单的自定义方法,您可以使用默认接口方法,例如:
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);
}
}
}
编辑:
在 this spring 教程中它是这样写的:
Spring Data JPA 还允许您通过简单地声明其方法签名来定义其他查询方法。
因此,甚至可以只声明如下方法:
Customer findByHobby(Hobby personHobby);
如果对象 Hobby
是 Customer 的属性,那么 Spring 会自动为您定义方法。
我使用以下代码从我的自定义实现中访问生成的查找方法。通过 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);
}
}
考虑到您的代码片段,请注意您只能将 Native 对象传递给 findBy### 方法,假设您要加载属于某些客户的帐户列表,一种解决方案是这样做,
@Query("Select a from Account a where a."#nameoffield"=?1")
List<Account> findByCustomer(String "#nameoffield");
使要查询的表名与Entity类相同。如需进一步的实施,请查看 this
如果您希望能够执行更复杂的操作,您可能需要访问 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);
}
}
}
这里还有一个问题需要考虑。有些人希望向您的存储库添加自定义方法会自动将它们公开为“/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
@RestResource(path = "myQueryMethod")
注释添加到方法即可通过 REST 公开任何存储库方法。上面的引用只是说明 Spring 不知道您希望它如何映射(即 GET 与 POST 等),因此您可以通过注释指定它。
我喜欢 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。
我使用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.
}
}
我扩展了 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。
我使用 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;
}
不定期副业成功案例分享
AccountRepositoryImpl
而不是:AccountRepositoryCustomImpl
,等等 - 这是非常严格的命名约定。QueryDslRepositorySupport
,请参阅我之前关于它不起作用的评论您还必须通过字段或 setter 注入而不是构造函数注入来注入存储库,否则它将无法创建 bean。它似乎确实有效,但解决方案感觉有点“脏”,我不确定 Spring Data 团队是否有任何计划来改进它的工作方式。