How to add custom method to Spring repository? - spring

Currently, here is my way to add custom method to one Spring Repository:
- firstly, create 1 interfaces extended from JPARepository along with my interface like this
interface MyRepository extends extends JpaRepository<Model, ID>, QueryDslPredicateExecutor<Model.class>, MyRepositoryCustom
then to create MyRepositoryCustom interface and its implementations
intefaces MyRepositoryCustom{
void myMethodHere();
}
class MyRepositoryImpl implement MyRepositoryCustom {
void myMethodHere(){
// do somethong
}
}
The problem is now that I want to reduce the amount of class, so is there any way to add custom methods with only 1 interface and 1 implement class.

If you want to implement the same custom method for all repositories in your app you have to:
Create base Repository interface by extending Repository Interface
Implement method in base Repository interface
Implement Custom RepositoryFactoryBean
Specify custom RepositoryFactoryBean in #EnableJpaRepositories annotation in configuration.
More details, for example, you can find here: http://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-adding-custom-methods-into-all-repositories/

Related

Spring Boot 2.5 and Spring Data: #NoRepositoryBean unexpected behaviour in multi-module project

I'm facing the following issue in a legacy code that I can't change. I have a multi module project which defines in the commons module a Spring Data interface as below:
package commons;
...
#NoRepositoryBean
public interface MyCustomRepository<P, I extends Number> extends JpaRepository<MyEntity, Integer>
{
MyEntity getOneAndCheck();
}
In another module I extend this interface as follows:
package data;
...
#Repository
public interface MyRepository extends MyCustomRepository<MyEntity, Integer>
{
...
}
So, the idea is that I don't want that Spring Data generates any implementation for the MyEntity getOneAndCheck() method 'cause it is implemented like this:
package data;
...
public class MyCustomRepositoryImpl implements MyCustomRepository
{
...
#Override
public MyEntity getOneAndCheck()
{
...
}
...
}
However, when I'm starting the application, I get the following exception:
...
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract MyEntity commons.MyCustomRepository.getOneAndCheck()! No property getOne found for type MyEntity!
...
So what it seems to happen is that Spring Data tries to generate a Query for the MyEntity getOneAndCheck() method, despite the #NoRepositoryBean annotation. This works as expected in the application I'm gonna migrate from Spring 3 with Spring Data to Spring Boot 2.5.
Not sure if the described behavior has anything to do with the fact that there are multiple Maven modules and that the repositories, the entities and the DTOs are in different modules. Not sure neither if there should be any difference between the way it runs currently with Spring and the one with Spring Boot. But the result is that all of the dozens of repositories in this legacy application are failing with the mentioned exception.
It might be important to mention that the main class needs to use annotations in order to tune the scanning:
#SpringBootApplication(scanBasePackages = "...")
#EnableJpaRepositories(basePackages={"...", "..."})
#EntityScan(basePackages= {"...", "..."})
public class MyApp
{
public static void main(String[] args)
{
SpringApplication.run(MyApp.class, args);
}
}
Not sure whether these annotations are supposed to change anything from the point of view of #NoRepositoryBean but the issue appeared as soon as I added this Spring Boot main class. It worked okay previously without Spring Boot.
Any suggestion please ?
Many thanks in advance.
Kind regards,
Seymour
There are two things that play together:
Spring Data's default custom implementation
Repository fragments
None of these apply because:
The default custom implementation follows the name of the actual repository. In your case, the implementation is named MyCustomRepositoryImpl whereas the repository name is MyRepository. Renaming the implementation to MyRepositoryImpl would address the issue
Since Spring Data 2.0, the repository detection considers interfaces defined at the repository level as fragment candidates where each interface can contribute a fragment implementation. While the implementation name follows the fragment interface name (MyCustomRepository -> MyCustomRepositoryImpl), only interfaces without #NoRepositoryBean are considered.
You have three options:
extracting your custom method into its own fragment interface and providing an implementation class that follows the fragment name:
interface MyCustomFragement {
MyEntity getOneAndCheck();
}
class MyCustomFragementImpl implements MyCustomFragement {
public MyEntity getOneAndCheck() {…}
}
public interface MyRepository extends MyCustomRepository<MyEntity, Integer>, MyCustomFragment {…}
Set the repositoryBaseClass via #EnableJpaRepositories(repositoryBaseClass = …) to a class that implements the custom method.
If you cannot change the existing code, you could implement a BeanPostProcessor to inspect and update the bean definition for the JpaRepositoryFactoryBean by updating repositoryFragments and adding the implementation yourself. This path is rather complex and requires the use of reflection since bean factory internals aren't exposed.

Why do most of people create an Interface in order to create a service?

I tried to create a service directly through a class without implementing a customized interface. And it works ! So I wonder why do most of people spend time on creating an interface in order to create a service ?
I can point 2 reasons:
It helps decoupling. (Of course it is still possible to create decoupled classes without an interface.)
You added spring in the question tag, so this reason is specific: in many cases Spring needs an interface to properly create a jdk proxy (this is needed when using AOP). It is possible to create proxies without an interface (spring will use CGLIG instead of JDK), but there are some differences "under the hood". Check here.
First of all we are using service layer in order to wrap some business logic to our application.
To make our service layer more abstract from the client we will first create service Interface that will contains some of the abstract methods similar to CURD repository.
Then we need to write logic for our abstract methods in service interface by creating new class like ServiceImplementation.
So the control flow will be Controller to ServiceImplementation.
class StudentController {
ServiceImplementation serviceImpl;
// Endpoints implementation.
GET;
POST;
PUT;
DELETE;
}
interface Service {
List<StudentInfo> getAllStudents();
StudentInfo addStudent(Student std);
StudentInfo updateStudent(Student student);
StudentInfo findStudentById(String id);
boolean deleteStudent(String id);
}
public class ServiceImplementation implements Service{
private StudentRepository studentRepository;
// Implement the abstract methods
}
public interface StudentRepository extends JpaRepository<Student, String>{
}
This is the standard pattern we will follow.

Custom repository

I have in my project a few entity that have the same property (for sample 'name') so, it's possible to create a repository with a custom select to use in that's entities? so instead I extend my repository from JpaRepository I extend MyCustomJpaRepository and the MyCustomJpaRepository extends the JpaRepository to grant the basic functions from JpaRepository too?
tks
Yes you can define a common interface repository which extends JpaRepository by marking it with the annotation #NoRepositoryBean:
#NoRepositoryBean
public interface BaseRepository<T extends BaseEntity, ID extends Serializable> extends JpaRepository<T, ID> {
//common methods
}
However, you still must have a dedicated interface for each of your concrete entities which extend this custom interface.

How does Annotation #Autowire for interface instantiate in spring

I have seen many examples of Spring boot wherein we declare an interface which extends crudrepository and declare the CRUUD methods. Post which in Service CLass we autowire the interface and invoke the crud methods from Service class.
Since we are not implementing this interface(which extends CrudRepo in example PersonRepo ) anywhere ,how come spring instantaties it ?
Ex :
interface PersonRepo extends CRUDRepository {
// method declaration
}
#Service
class PersonService{
#Autowire
PersonRepo perrepo;
void insert(){
perrepo.methodname();
}
}
Spring introduces dynamic proxy.
In fact it scans package and get all interfaces which extend Crud Repository interface. For each of them a Dynamic Proxy instance is created. On each call the proxy checks which method is called and uses appropriate annotation. The proxies are used as beans so can be autowired.

Spring Boot detects 2 identical repository beans

I am using Spring Boot with Spring Data JPA, there is only one #SpringBootApplication. And I have also a repository classes, for example:
package com.so;
public interface SORepository {
//methods
}
And impl
#Repository("qualifier")
#Transactional(readOnly = true)
public class SORepositoryImpl implements SORepository {
//methods
}
The proplem is, when I start the application, I get following error:
Parameter 0 of constructor in com.so.SomeComponent required a single bean, but 2 were found:
- qualifier: defined in file [path\to\SORepositoryImpl.class]
- soRepositoryImpl: defined in file [path\to\SORepositoryImpl.class]
So, as you see, somehow 2 beans of one repository class are created. How can I fix this?
You can use Spring Data JPA methods having created Proxy element and than inject it into public class SORepositoryImpl:
public interface Proxy() extends JpaRepository<Element, Long>{
Element saveElement (Element element); //ore other methods if you want}
And than:
#Repository
#Transactional(readOnly = true)
public class SORepositoryImpl implements SORepository {
#Autowired
private Proxy proxy;
//end realisation of methods from interface SORepository
}
Try taking the #Repository annotation off the SORepositoryImpl class
e.g.
#Transactional(readOnly = true)
public class SORepositoryImpl implements SORepository {
//methods
}
The error message is implying you have two beans, one named "qualifier" and one named "soRepositoryImpl", which is probably in a Config class.
I guess you should share your SomeComponent class supposing you have no extra configuration class/xml. My take is that you are injecting as 'soRepositoryImpl' there where you have defined as 'qualifier'. Having two options them. I would say to just remove the annotation parameter 'qualifier' and it should work.
Moreover, unless you want do specify an custom DAO implementation you can avoid #Repository at all (That's an annotation you use to make it injectable for your services). You can just create an interface extending Spring interface and define methods for queries.
For example:
public interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
Then you can just inject it in your services/controller directly.
private final PersonRepository personRepository;
public PersonController(final PersonRepository personRepository) {
this.personRepository = personRepository;
}
check samples:
https://spring.io/guides/gs/accessing-data-jpa/
http://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
OK, I've found the issue.
I just couldn't understand, how Spring creates the second bean (soRepositoryImpl), because I've never told it, neither explicitly nor in config classes. But I figured out that the second bean us created during the instantiation of my another SORepository (which is in the different package com.another and which extends JpaRepository).
So, when Spring tries to resolve all dependencies of com.another.SORepository it somehow finds my com.so.SORepositoryImpl (which has nothing familiar with com.another.SORepository - not extending\implementing, not jpa stuff, only similar names!).
Well it seems like a Spring bug to me, because it doesn't check the real inheritance of dependent classes of repositories, only name + Impl (even in different package) suits for him.
The only thing that I should do is to rename `com.so.SORepositoryImpl and that it, no 2 beans anymore.
Thanks everyone for answers!

Resources