I'm working with Crud and Spring MVC, and get stuck with duplicating the same code with different repositories;
f.e. if i use Customers, Items and Logins i have
public interface CustomerRepository extends CrudRepository<Customer, Long>{
}
but with Item i have the same stuff:
public interface ItemRepository extends CrudRepository<Item, Long>{
}
Furthermore there are same methods in repositories, like findAll(), findDistinctBy...(), deleteAll(), etc.
As I know the good practice is escaping of code duplication, but where i should do it? In service, using special service interface and implement it by special class, and then specialise the service for each entity? Or I should do it in the my custom repository as they said in official Spring Data JPA reference documentation spring data reference $4.3.1?
My variant for code with service is below.
Common interface for all entities
import org.springframework.data.repository.CrudRepository;
public interface MyCrudService<T>{
<S extends CrudRepository<T, Long>> Iterable<T> findAll(S s);
}
Common class that realises the service
import org.springframework.data.repository.CrudRepository;
public class MyCrudServiceImpl<T> implements MyCrudService<T>{
#Override
public <S extends CrudRepository<T, Long>> Iterable<T> findAll(S s) {
return s.findAll();
}
}
Special service class for Unit entity
#Service
#Transactional
#AllArgsConstructor
public class MyUnitServiceTwo{
UnitRepository unitRepository;
MyCrudServiceImpl crudService;
public List<Unit> findAll1() {
return Lists.newArrayList(crudService.findAll(unitRepository));
}
}
Related
I had a slow performance of default save method in spring data jpa. So, i decided to make save method work asynchronously cause i did not need response in this specific method.
#Repository
public interface WorkplaceRepo extends JpaRepository<Workplace, Long> {
#Async
public <S extends Workplace> S save(S workplaceE);
It caused the problem that all save methods in whole project started to call this asynchronous method. The question is: how to use both save methods without losing one of them(default version and async version)
i thought to create custom insert method using native query, but the entity have so many columns and foreign keys, and i am not sure that it would work correctly.
I would suggest creating new repository WorkplaceRepoAsync with one save method something like this:
#Repository
public interface WorkplaceRepoAsync extends Repository<Workplace, Long> {
#Async
public <S extends Workplace> S save(S workplaceE);
}
UPDATE
Much better solution will be create WorkplaceService with #Async logic method
#Repository
public interface WorkplaceRepo extends JPARepository<Workplace, Long> {}
#Service
public class WorkplaceService {
#Inject
private WorkplaceRepo workspaceRepo;
#Async
public Workspace saveAsync(Workspace workspace) {
workspaceRepo.save(workplace);
}
}
You can try with the following code to have both methods available:
#Repository
public interface WorkplaceRepo extends JpaRepository<Workplace, Long> {
#Async
default <S extends Workplace> S saveAsync(S workplaceE) {
return this.save(workplaceE);
}
}
This way the inherited not async method save(S entity) from JpaRepository would still be available to be called from your WorkplaceRepo.
For those who faced same issue i would recommend to read this article. It gave good understanding of how spring data works inside, and solution of my problem.
https://medium.com/#sridharmcakec/refactor-jpa-save-method-to-improve-application-performance-b7a77db2794c
My solution:
public interface CustomWorkplaceRepo {
void saveAsync (Workplace workplace);
}
Then I implemented CustomWorkplaceRepo
public class CustomWorkplaceRepoImpl implements CustomWorkplaceRepo {
#PersistenceContext
private EntityManager entityManager;
#Override
#Async
#Transactional
public void saveAsync(Workplace workplace) {
entityManager.persist(workplace);
}
}
The last step was adding to extends new interface in my old Repo
public interface WorkplaceRepo extends JpaRepository<Workplace, Long>, CustomWorkplaceRepo {
That's it. Now, in service when I want to save Workplace Entity it is possible to call "saveAsync" method
Let's say I want to create a REST API which performs basic CRUD operations on several entities. For that I've created generic interface:
public interface CrudService<T>{
//generic CRUD methods
}
And its implementation for Foo entity:
#Entity
public class Foo {
}
#Repository
public interface FooRepository extends JpaRepository<Foo, Long>{
}
#Service
#Transactional
public class FooCrudServiceImpl implements CrudService{
#Autowired
private FooRepository repository;
//CRUD methods implementation
}
#RestController
class FooController{
#Autowired
private CrudService<Foo> crudService;
//Controller methods
}
What I want to avoid now is creating service implementation for each entity with basically the same logic. So I tried to create a generic service class which can be called from multiple controllers(FooController, BarController etc.):
#Service
#Transactional
class GenericCrudServiceImpl<T> implements CrudService{
#Autowired
private JpaRepository<T, Long> repository;
//generic CRUD methods implementation
}
and pass that service class to each controller where the entity type would be specified. The problem is that there will be multiple repository beans that could be injected into GenericCrudServiceImpl (FooRepository, BarRepository etc.) and just by specifying the type of JpaRepository Spring still doesn't know which bean to inject. I don't want to call repository beans directly from controller classes to maintain seperation of responsibilities.
Additionally, for some reason this problem doesn't occur on controller level where I inject CrudService interface and Spring understands which bean should it choose, which messes with my whole understanding of dependency injection.
Is there a way to create such a generic service class? Other posts on stackoverflow didn't provide me with an answer.
Bonus question: what's the difference between using a #Qualifier annotation and injecting a specific implementation (in this example FooCrudServiceImpl instead of CrudService in controller class)? In both cases pointing to different use implementation requires changing one line of code.
What about that:
#Transactional
public class GenericCrudServiceImpl<T> implements CrudService{
private JpaRepository<T, Long> repository;
public GenericCrudServiceImpl(JpaRepository<T, Long> repository) {
this.repository = repository;
}
}
And Spring configuration:
#Configuration
public PersistanceConfiguration {
#Bean
public JpaRepository<Foo, Long> fooJpaRepository() {
...
}
#Bean
public JpaRepository<Foo, Long> booJpaRepository() {
...
}
#Bean
public CrudService<Foo> fooService(JpaRepository<Foo, Long> fooJpaRepository) {
return new GenericCrudServiceImpl(fooJpaRepository);
}
#Bean
public CrudService<Foo> booService(JpaRepository<Foo, Long> booJpaRepository) {
return new GenericCrudServiceImpl(booJpaRepository);
}
}
And Controller
#RestController
class FooController{
// Injection by bean name 'fooService'
#Autowired
private CrudService<Foo> fooService;
//Controller methods
}
Using spring data, I have two tables that shares the same structure.
The two tables are represented by two different entities, that inherit from the same class :
#MappedSuperclass
public abstract class SuperEntity<T extends SuperEntity> {
// ...
}
#Table(name = "FOO")
#Entity
public class Foo extends SuperEntity<Foo> {
// ...
}
#Table(name = "BAR")
#Entity
public class Bar extends SuperEntity<Bar> {
// ...
}
I also have a generic repository, that I would like to use to factorize to requesting logic, and two sub-repository : one for each table.
public interface GenericEvtRepository <T extends SuperEntity<?>> extends JpaRepository<T, String> { }
public interface FooRepository extends GenericEvtRepository<Foo> {}
public interface BarRepository extends GenericEvtRepository<Bar> {}
I would like to add an actual query implementation to this repository (i.e. using EntityManager / Criteria).
Therefore I tried to adapt the custom repository strategy to my generic case
#Repository
public class GenericEvtRepositoryImpl<T extends SuperEntity<?>> extends SimpleJpaRepository<T, String> implements GenericEvtRepository<T> {
#PersistenceContext
EntityManager entityManager;
// Some logic using entityManager
public SuperEntity myCustomRequest() { /*...*/ }
}
However my application doesn't start, with the exception :
org.springframework.data.mapping.PropertyReferenceException: No property myCustomRequest found for type Foo!
Not sure what I'm doing wrong, but Spring seems to think that myCustomRequest is an attribute from my entities, instead of a method.
I'm using spring-boot 1.5.6 and spring-data-jpa 1.11.6.
Minimal reproductible exemple
Luckily I was able to reproduce your issue,
How spring recommends custom repository implementation is specified here in spring docs.
So, you can do something like below,
public interface CustomEntityRepository<T extends SuperTag<?>>
public interface FooRepository extends JpaRepository<Foo, Integer>, CustomEntityRepository<Foo>
public interface BarRepository extends JpaRepository<Bar, Integer>, CustomEntityRepository<Bar>
And define common implementation for CustomEntityRepository<T extends SuperTag<?>> as below,
#Repository
// NOTE: Implementation name must follow convension as InterfaceName + 'Impl'
public class CustomEntityRepositoryImpl<T extends SuperTag<?>> implements
CustomEntityRepository<T> {
#PersistenceContext
private EntityManager entityManager;
// Generic custom implementation here
}
Spring automatically detects implementation of Custom Interface CustomEntityRepository based on implementation class naming convention.
I have a repository interface that extends CrudRepository to automatically give me all of the basic crud repository functionality.
public interface CustomerRepository extends CrudRepository<Customer, Integer> {
}
Am I still able to add custom repository functions and implement this interface?
Yes for sure.
There is section in the official documentation:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
The idea is to have an interface for your custom code like this:
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
Then you need an implementation that ends with Impl:
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
And finally Spring Data Repo that extends from the custom repo:
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
You can also write you own queries directly in the interface if you extend from JpaRepository<User, Long> by using the annotation #Query
public interface UserRepository extends JpaRepository<User,Long> {
#Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
Spring doc: Query annotation
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.