可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
@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? Since its an Interface I cannot implement the method there.
回答1:
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
AccountRepository accountRepository; /* Optional - if you need it */
public void customMethod() { ... }
}
See also:
回答2:
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:
public class AccountRepositoryImpl implements AccountRepositoryCustom {
@PersistenceContext
private EntityManager em;
public void customMethod() {
...
em.createQuery(yourCriteria);
...
}
}
回答3:
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.
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.
回答4:
Im using the following code in order to access generated find methods from my custom implementation. Getting the implementation through the bean factory prevents circular bean creation problems.
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);
}
}
回答5:
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,
@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.
For further implementations please take a look at this
回答6:
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):
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);
}
}
}
回答7:
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. This is unfortunately not the case. Spring doesn\'t support that currently.
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:
private boolean isQueryMethodCandidate(Method method) {
return isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method);
}
This is a qoute of 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. POST would be the safest option but that\'s not in line with the
generic query methods (which receive GET).
For more details see this issue: https://jira.spring.io/browse/DATAREST-206
回答8:
The accepted answer works, but has three problems:
- It uses an undocumented Spring Data feature when naming the custom implementation as
AccountRepositoryImpl
. The documentation clearly states that it has to be called AccountRepositoryCustomImpl
, the custom interface name plus Impl
- You cannot use constructor injection, only
@Autowired
, that are considered bad practice
- 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:
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
}
}
回答9:
I extends the 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.