How to add custom method to Spring Data JPA - java

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.

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:
4.6 Custom Implementations for Spring Data Repositories
Note that the naming scheme has changed between versions. See https://stackoverflow.com/a/52624752/66686 for details.

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

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:
Define in you regular #Repository interface, say MyEntityRepository the custom methods (in addition to your Spring Data methods)
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).
This class can even inject MyEntityRepository via #Autowired for use in the custom methods.
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:
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.
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.
You have a limit of one Impl class. (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.

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

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.

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

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

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

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

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's solution is the only one here that let's you use the Spring Data methods in the Impl class. 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). MongoAccess provides that access to all of your repositories by retrieving the bean directly:
public interface MongoAccess {
default MongoOperations getMongoOperations() {
return BeanAccessor.getSingleton(MongoOperations.class);
}
}
Where BeanAccessor is:
#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. You could autowire the bean into a MongoAccessImpl and provide a method in the interface to access it, but Spring Data blows up. I don't think it expects to see an Impl associated even indirectly with PagingAndSortingRepository.

I faced with this using mongo and 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. To achieve one interface to inject repository for crud and custom we need to specify:
Custom interface:
public interface UserCustomRepository {
List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest);
}
UserRepository interface 'must' first extends UserCustomRepository and then MongoRepository
#Repository
public interface UserRepository extends UserCustomRepository, MongoRepository<User, ObjectId> {
}
UserRepositoryImpl must have the same name as what crud interface with *Impl suffix.
#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.
#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.
}
}

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.

I use SimpleJpaRepository as the base class of repository implementation and add custom method in the interface,eg:
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;
}

Related

Problems with creating Spring JPA custom repository [duplicate]

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.
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:
4.6 Custom Implementations for Spring Data Repositories
Note that the naming scheme has changed between versions. See https://stackoverflow.com/a/52624752/66686 for details.
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);
...
}
}
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:
Define in you regular #Repository interface, say MyEntityRepository the custom methods (in addition to your Spring Data methods)
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).
This class can even inject MyEntityRepository via #Autowired for use in the custom methods.
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:
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.
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.
You have a limit of one Impl class. (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.
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
}
}
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.
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);
}
}
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
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 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);
}
}
}
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
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's solution is the only one here that let's you use the Spring Data methods in the Impl class. 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). MongoAccess provides that access to all of your repositories by retrieving the bean directly:
public interface MongoAccess {
default MongoOperations getMongoOperations() {
return BeanAccessor.getSingleton(MongoOperations.class);
}
}
Where BeanAccessor is:
#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. You could autowire the bean into a MongoAccessImpl and provide a method in the interface to access it, but Spring Data blows up. I don't think it expects to see an Impl associated even indirectly with PagingAndSortingRepository.
I faced with this using mongo and 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. To achieve one interface to inject repository for crud and custom we need to specify:
Custom interface:
public interface UserCustomRepository {
List<User> findAllUsersBySomeCriteria(UserCriteriaRequest criteriaRequest);
}
UserRepository interface 'must' first extends UserCustomRepository and then MongoRepository
#Repository
public interface UserRepository extends UserCustomRepository, MongoRepository<User, ObjectId> {
}
UserRepositoryImpl must have the same name as what crud interface with *Impl suffix.
#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.
#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.
}
}
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.
I use SimpleJpaRepository as the base class of repository implementation and add custom method in the interface,eg:
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;
}

JPA Interface extending JpaRepository only, with use of EntityManager in default methods

First of all, for those who wanted to know how to use an EntityManager in an interface extending a JpaRepository.
Here is a potential solution.
But !!! I'm aware this is maybe a baaaad practice, and experienced people in Spring and JPA will maybe try to kill me ;) apologizes...
So, after this BIG warning, here is my question :
I would like to use only interfaces without a Custom implementation and this is what I did :
#Repository
#Transactional
public interface BookingDao extends JpaRepository<Booking, Long> {
Booking findByCodeId(String codeId);
void deleteByCodeId(String codeId);
default Integer findCountOfBookingOldDay() {
EntityManager entityManager = ApplicationContextProvider.getEntityManager();
Query query = entityManager.createNativeQuery("XXXX");
Number result = (Number) query.getSingleResult();
return result.intValue();
}
}
#Component(value = "applicationContextProvider")
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
public static EntityManager getEntityManager() {
return EntityManagerFactoryUtils.getTransactionalEntityManager(context.getBean(LocalContainerEntityManagerFactoryBean.class).getObject());
return context.getBean(EntityManager.class);
}
#Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
context = ac;
}
}
I'm aware this is maybe not the best practice, and the best way would be to use #PersistenceContext (takes care to create a unique EntityManager for every thread) in a class... But what would be in my case the best alternative :
this :
return EntityManagerFactoryUtils.getTransactionalEntityManager(context.getBean(LocalContainerEntityManagerFactoryBean.class).getObject());
or this :
return context.getBean(EntityManager.class)
or maybe another better way, still by using only interface.
Thank you for your remarks/advices.
PS: "EntityManagerFactoryUtils" is not doing the same as PersistenceContext ?
For what you want to do I'd annotate a method in the interface by #Query("<your native select counting what you need>", native=true).
When I need any fancy logic, I usually create a service, which is using the repository like this:
#Service
#Transactional
public class MyService {
private final MyRepository repository;
#Autowired
public MyService (MyRepository repository) {
this.repository = repository;
}
public int myMethod(){
//code using repository
}
}

SimpleJpaRepository argument compilation error

I try to extends SimpleJpaRepository and I have got an error :
[ERROR] constructor org.springframework.data.jpa.repository.support.SimpleJpaRepository.SimpleJpaRepository(org.springframework.data.jpa.repository.support.JpaEntityInformation<com.bnpparibas.dsibddf.ap00437.successione2e.domain.entity.gedeo.Contrat,?>,javax.persistence.EntityManager) is not applicable
[ERROR] (actual and formal argument lists differ in length)
[ERROR] constructor org.springframework.data.jpa.repository.support.SimpleJpaRepository.SimpleJpaRepository(java.lang.Class<com.bnpparibas.dsibddf.ap00437.successione2e.domain.entity.gedeo.Contrat>,javax.persistence.EntityManager) is not applicable
[ERROR] (actual and formal argument lists differ in length)
I tried the #enableJpaRepositories solution like described here :
Extend SimpleJpaRepository
But it does not work.
I put the annotation in the repository class.
Is there something else I can do?
#Configuration
#EnableJpaRepositories(repositoryBaseClass = ContratRepository.class , basePackages = {"com.bnpparibas.dsibddf.ap00437.successione2e.infrastructure.repository"})
#Repository
public class ContratRepository extends SimpleJpaRepository<Contrat,Integer> implements IContratRepository {
#Override
public Contrat save(Contrat contrat) {
return null;
}
}
if I had as suggested by eclipse the constructor 2:
public ContratRepository(Class<Contrat> domainClass, EntityManager em) {
super(domainClass, em);
}
Contrat class
package com.bnpparibas.dsibddf.ap00437.successione2e.domain.entity.gedeo;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
public class Contrat {
#Id
#GeneratedValue(strategy = GenerationType. IDENTITY )
private String idContratTech;
private String contractExternalId;
private String code;
private String libelle;
private String contractStatus;
public String getIdContratTech() {
return idContratTech;
}
public void setIdContratTech(String idContratTech) {
this.idContratTech = idContratTech;
}
public String getContractExternalId() {
return contractExternalId;
}
public void setContractExternalId(String contractExternalId) {
this.contractExternalId = contractExternalId;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getLibelle() {
return libelle;
}
public void setLibelle(String libelle) {
this.libelle = libelle;
}
public String getContractStatus() {
return contractStatus;
}
public void setContractStatus(String contractStatus) {
this.contractStatus = contractStatus;
}
public String getNature() {
return nature;
}
public void setNature(String nature) {
this.nature = nature;
}
private String nature;
public void foo() {
}
}
I have got the error:
NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.Class<com.bnpparibas.dsibddf.ap00437.successione2e.domain.entity.gedeo.Contrat>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
that I don't understand
thanks for your help
First of all, You should not put #Repository and #Configuration on the same class.
Secondly, if you are to extend a SimpleJpaRepository, you need to create matching constructors and call the parent constructors.
Thirdly, I would like to note that customizing a SimpleJpaRepository is NOT ADVISED FOR A SINGLE ENTITY (e.g. Contrat). In that case, use a regular repository implementation! If you do customize your SimpleJpaRepository to be only compatible with a single entity, you will not be able to create other repositories extending SimpleJpaRepository!
1. About beans and configuration
In Spring, a Repository is a type of bean. This means that it this class can be automatically instantiated by the Spring framework when you need an instance of it (ever noticed that you never need to do new MyRepository()?). This is done through a process called autowiring (the general principle of this is called Dependency Injection. You can read more about beans and autowiring in spring here. Other typical types of beans are #Component, #Controller and #Service.
A Configuration class on the other hand is a whole different kind of beast. Configuration classes are used to provide logic about how certain beans are to be created.
An example could for instance be that you want to configure a bean that holds a database connection, and in order to create that bean you need variables that are read from the environment (Environment Variables). In that example the configuration class would be the one reading out the environment variables and creating the database connection bean through a #Bean method.
In essence configuration classes are thus used to instantiate beans that either 1) are not able to be instantiated automatically or 2) need special bean configuration.
2. About matching constructors
Spring needs to be able to instantiate the parent class SimpleJpaRepository. Therefore, you should assure that you implement matching constructors.
Furthermore, it is a general best practice to split your repository in a Repository and a RepositoryImpl.
So for the constructor matching, this gives:
CustomSimpleRepositoryImpl
public class CustomSimpleRepositoryImpl(<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> extends CustomSimpleRepository<T, ID> {
private EntityManager em;
private JpaEntityInformation<T, ?> ei;
protected CustomSimpleRepositoryImpl(#NonNull Class<T> entityClass, EntityManager em) {
super(domainClass, em);
this.em = em;
this.ei= JpaEntityInformationSupport.getEntityInformation(entityClass, em);
}
public CustomSimpleRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.em = entityManager;
this.ei = entityInformation;
}
/* Whatever you want to put in your repository */
}
For the split:
CustomSimpleRepository
#NoRepositoryBean
public interface CustomJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
/* Whatever you want to put in your repository */
}
3. Customizing repositories
Note that in the above example I did not use #Repository whatsoever. Instead I used #NoRepositoryBean. This is because in my example I implemented a generic SimpleJpaRepository replacement. This repository is not to be instantiated on its own!
3.1 A plain, custom repository
If you rather want to customize a regular Repository, please only use an interface CustomRepository and an implementation called CustomRepositoryImpl.
Example implementations can be found in 3.2.
3.2 A custom repository extending SimpleJpaRepository
This is a difficult one: you cannot implement an interface without providing all of its unimplemented methods. So if you really want a repository where you can have both:
JPA methods, e.g. findById, findByFieldContainsText(String text)...
Custom methods, e.g. findAllCountsGroupByParentId(), ...
It is not possible by creating one single repository interface or class. Instead, you need to create a custom repository where you offload your "custom methods" (2) and a repository that extends SimpleJpaRepository where you define your "JPA methods" (1):
CustomJpaRepository.java
#Repository
public interface CustomJpaRepository
extends JpaRepository<CustomEntity, Long>
extends CustomRepository {
/* Your desired JPA methods, e.g. */
// findByNameContainsText(String text);
}
CustomRepository.java
public interface CustomRepository {
/* Your desired custom methods, e.g. */
// findAllCountsWhereContractFinishedGroupByBusinessUnitId();
}
CustomRepositoryImpl.java
#Repository
public class CustomRepositoryImpl {
protected EntityManager em;
CustomRepositoryImpl(final EntityManager em) {
this.em = em;
}
/* Your desired custom methods, e.g. */
// public findAllCountsWhereContractFinishedGroupByBusinessUnitId() {
// ...
// }
}
Since your CustomJpaRepository extends JpaRepository, you can use the JPA methods.
Since it also extends CustomRepository, it inherits the custom methods.
Result: you can call both kinds of methods (custom and jpa) combined in CustomJpaRepository.

Why do I need a fragment interface for repositories that stand on their own?

Quoting https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
To enrich a repository with custom functionality, you must first define a fragment interface and an implementation for the custom functionality
And Example 32 shows that this is even the case for repositories that do not extend CrudRepository (as in Example 31) or merge multiple repository interfaces (as in Example 33).
It also notes that
The most important part of the class name that corresponds to the fragment interface is the Impl postfix
But forgoes any explanation as to why.
How do custom repositories / repository fragments work, and why isn't an #Repository annotation on an ordinary bean sufficient?
You have basically two options. The first one is to provide an implementation of each repository interface. This can be seen in your examples. In this case you can override the default implementation of a repository for one entity repo. This can be useful to enhance a special entity repository, e.g. for reportings, etc.
The other option is when you want to provide a base implementation for ALL repositories then you need to use base repository fragments. Repository fragments can be very useful when you want to provide only one base implementation for every custom repository. Let me show you an example:
CustomReadRepository:
#NoRepositoryBean
public interface CustomReadRepository<T> extends Repository<T, String> {
public List<T> someMethod();
public List<T> someHighPerformanceMethod();
}
Now let's say we want one interface which combines different repositories and call it DataRepository.
DataRepository:
#NoRepositoryBean
public interface DataRepository<T>
extends CrudRepository<T, String>, CustomReadRepository<T> {
}
The following interface is now the specific repository for an entity which extends the overall repository
#Repository
public interface UserRepository extends DataRepository<UserEntity> {
}
or
#Repository
public interface OrderRepository extends DataRepository<OrderEntity> {
}
And now we want to provide only ONE implementation for all specific repositories (OrderRepository, UserRepository, etc.).
public class CustomReadRepositoryImpl<T> implements CustomReadRepository<T> {
private static final EntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager entityManager;
private final Querydsl entityManager;
public CustomReadRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager) {
this.entityManager = entityManager;
this.entityInformation = entityInformation;
}
#Override
public List<T> someMethod() {
//do something and return result
}
#Override
public List<T> someHighPerformanceMethod() {
// do some high performance querying here
}
}
So what you basically can do here is to enhance your repositories and provide only one implementation which can be useful e.g. to override Spring's default implementations (in order to make it faster or whatever reason). You also can provide new custom repos and implementations.
To make your fragment implementation accessible in Spring you have to do the following:
CustomRepositoryFactoryBean:
public class CustomRepositoryFactoryBean<T extends Repository<S, I>, S, I>
extends JpaRepositoryFactoryBean<T, S, I> {
public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomRepositoryFactory(entityManager);
}
this bean should used in the Main class:
Main Class:
#EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class App {
}
and the implementation of the factory:
CustomRepositoryFactory:
public class CustomRepositoryFactory extends JpaRepositoryFactory {
private final EntityManager entityManager;
public CustomRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
#Override
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
RepositoryFragments fragments = super.getRepositoryFragments(metadata);
if (CustomReadRepository.class.isAssignableFrom(
metadata.getRepositoryInterface())) {
JpaEntityInformation<?, Serializable> entityInformation =
getEntityInformation(metadata.getDomainType());
Object customRepoFragment = getTargetRepositoryViaReflection(
CustomReadRepositoryImpl.class, entityInformation, entityManager);
fragments = fragments.append(RepositoryFragment.implemented(customRepoFragment));
}
return fragments;
}
If you want to see a kind of real example have a look on the question here.

Spring Data JpaRespositories consolidation

I write a web application with Spring Boot and now I'm facing with the following problem:
I have a following Service class:
#Service
class ExampleService {
#Autowired
ARepository aRepository;
#Autowired
BRepository bRepository;
#Autowired
CRepository cRepository;
}
All repository interfaces extends
JpaRepository<MatchingClass, Integer>
Now I would like to perform following crud operations for each repository:
public List<AClass> getAll() {
List<AClass> aElements = new List<>();
aRepository.findAll().forEach(x->aElements.add(x));
return aElements;
}
public AClass getOne(Integer id) { return aRepository.getOne(id);}
public void addOne(AClass aClass) { aRepository.save(aClass);}
public void deleteOne(Integer id) {aRepository.delete(id);}
}
How can I achieve it without repeating methods with different parameter types? I have a basic knowledge about generics in java, but I'm not sure using it is permitted in spring data and, actually how to accomplish it properly.
If your repository interfaces are already extending JpaRepository<T, ID> then you don't need the methods deleteOne, addOne, getOne you could use the methods in JpaRepository direclty.
For example simply call from your service the methods delete, save, findOne, etc:
aRepository.save(aClassEntity);
Check org.springframework.data.repository.CrudRepository.

Categories