With Spring Data you can make a Repository for a given entity:
#Repository
public interface MyRepo extends CrudRepository<MyEntity, Long> {...}
But what if you have a lot of custom queries not tied to a specific Entity?
None of the below work:
#Repository
public interface MyRepo {...}
#Repository
public interface MyRepo extends CrudRepository {...}
#Component
public interface MyRepo extends Repository {...}
And so on..
Essentially what I want, is to be able to encapsulate some #Querys into an injectable class or interface.
You can use a generic entity superclass instead of a concrete entity. It's very usual to have an abstract superclass to declare the id of the entities or other common stuff.
#MappedSuperclass
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Then you can create a repository like this and autowire where you need it:
public interface MyRepo extends JpaRepository<AbstractEntity, Long> {
#Query("...")
myQueryMethod();
}
That said, Spring Data interfaces are designed to work with a root entity. I think that if you want to avoid it, you should use the underlying JPA layer (that is, use the EntityManager to execute queries instead of a Spring Data Repository).
Related
I have the following repository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
Employee findByName(String name);
}
Assume Employee is your usual entity class with id, name and surname.
I wire this class to my EmployeeService and use it like this:
#Service
public class EmployeeService {
#Autowired
private EmployeeRepository repository;
public Employee saveEmployee(Employee employee) {
return repository.save(employee);
}
}
Would it make any difference to add the #Repository annotation to the repository?
Example:
import org.springframework.data.jpa.repository.JpaRepository;
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
Employee findByName(String name);
}
The #Repository annotation is not required on Spring Data repositories. Spring Boot detects repository beans based on the fact that they extend the Repository interface.
In fact, you need to suppress this behavior using #NoRepositoryBean if you want the repository bean to not be created.
The #Repository annotation is a specialization of #Component. If you were implementing repositories without the help of Spring Data, you could annotate them with #Repository to declare them as beans as well as hint at their role in your app.
I want to disable export of DELETE operation using Spring Data rest using generics. Any repository which extends from BaseResourceRepository should not export DELETE verb. I am using groovy
#NoRepositoryBean
interface BaseResourceRepository<T extends BaseEntity, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
#Override
#RestResource(exported = false)
void delete(T t)
}
#RepositoryRestResource(collectionResourceRel = "contacts", path = "contacts")
interface ContactRepository extends BaseResourceRepository<Contact, Long> {
}
I want to disable the DELETE operation for /contacts endpoint
This configuration is still allowing me to DELETE the contact resource
Any help is appreciated.
Thanks in advance
See the official documentation regarding hiding CRUD methods:
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.hiding-repository-crud-methods
As mentioned there you need to hide both methods.
Should look like this (may contain errors due to no IDE present ;))
#NoRepositoryBean
interface BaseResourceRepository<T extends BaseEntity, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
#Override
#RestResource(exported = false)
void delete(T t)
#Override
#RestResource(exported = false)
void delete(ID id);
}
Edit
An approach to give a try to
The base repository
package com.stackoverflow.generics.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
#NoRepositoryBean
public interface NoDeleteRepository <T, ID> extends Repository<T, ID> {
T findOne(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
// Define other necessary methods
}
The repository for an concrete entity
package com.stackoverflow.generics.repository;
import com.stackoverflow.generics.repository.SomeEntity;
public interface SomeNoDeleteRepository extends NoDeleteRepository<SomeEntity, Long> {
}
The repository does not expose the delete method therefore I assume it wont be exposed as REST endpoint. Can't test it as my spring does not expose all repositories as rest resource. Some misconfiguration I think.
public class SomeService {
private final SomeNoDeleteRepository repository;
public void deleteSome(Long id) {
// cannot resolve method delete
repository.delete(id);
}
}
I have an abstract entity annotated with #MappedSuperclass:
#MappedSuperclass
public abstract class BaseEntity {
public abstract T getId();
public abstract void setId(T id);
}
Then I inherit my Entities from it, defining their id in each one:
#Entity
public class EntityA {
#Id
private int id;
// ....
}
#Entity
public class EntityB {
#Id
private long id;
// ....
}
Now I want to create a generic JpaRepository that accepts any class that extends from my Base Entity:
public interface BaseRepository<T extends BaseEntity, ID extends Serializable> extends JpaRepository<T, ID> {
}
But Spring trows an exception saying BaseEntity has no ID:
java.lang.IllegalArgumentException: This class [BaseEntity] does not define an IdClass
Please, check Plog's comments in his answer. I could solve it injecting each repository type in service's contructor
As far as I know you can't make generic repositories like this. You will need to make an individual repository for each of your concrete entity classes. aggregate roots (thanks #JensSchauder).
You can however make a generic base repository that can define some common queries between these two repositories by marking it as a #NoRepositoryBean:
#NoRepositoryBean
public interface BaseRepository<T extends BaseEntity, ID extends Serializable> extends JpaRepository<T, ID> {
//common methods
}
Your concrete class repositories should then extend this instead of JpaRepository:
public interface EntityARepository extends BaseRepository<EntityA, Integer> {
}
I'm implementing the repository structure of an entity which extends a MappedSuperClass in Sping data JPA.
Base.java:
#MappedSuperclass
public abstract class Base {
public abstract Long getId();
public abstract void setId(Long id);
public abstract String getFirstName();
public abstract void setFirstName(String firstName);
}
BaseImpl.java:
#Entity
public class BaseImpl extends Base {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
...
//Default and parameterised constructors
//Getters and setters
//Equals and hashcode
//toString
}
BaseRepository.java:
#NoRepositoryBean
public interface BaseRepository<T extends Base, ID extends Serializable> extends JpaRepository<T, ID> {
}
BaseRepositoryImpl.java:
public interface BaseRepositoryImpl extends BaseRepository<BaseImpl, Long> {
}
When I try to save Base object in the following way:
#Autowired
BaseRepositoryImpl baseRepositoryImpl;
#Test
public void contextLoads() {
Base base = new BaseImpl();
base.setFirstName("Mamatha");
System.out.println(baseRepositoryImpl.save(base));
}
I do see a compile time error in the sysout line saying "The method save(Iterable<S>) in the type JpaRepository<BaseImpl,Long> is not applicable for the arguments (Base)".
In JPA, everything happens through MappedSuperClass only, except the instantiation. Am I going wrong in implementing the repository structure. Please help.
Using Spring Data nad Querydsl we can just declare repository interface and skip the implementation class. Some methods with a specific name or using #Query annotation and that's all.
But sometimes I'd like to use JPAQuery and define method's body by myself, let's say
#Repository
public class MyRepositoryImpl implements MyRepository {
#PersistenceContext
private EntityManager em;
#Override
public List<Tuple> someMethod(String arg) {
JPAQuery query = new JPAQuery(em);
...
}
but this way I would have to implement other MyRepository interface methods, which ruins all Spring Data's advantages!
I can see two options:
Declare another interface per each repository and then normally implement it (which doubles number of interfaces)
Inject EntityManager into #Service class and implement my custom methods there
I like option #2 more, but as far I as know, in #Service class we should only call repository methods, so it's not a perfect solution as well.
So how does programmers deal with it?
You should not implement the actual Spring Data repository, instead you have to declare another custom interface where you can put your custom methods.
Let's say you have a MyRepository, defined as
#Repository
public interface MyRepository extends JpaRepository<Tuple, Long> {}
Now you want to add your custom findTuplesByMyArg(), for a sake of purpose you need to create custom repository interface
public interface MyRepositoryCustom {
List<Tuple> findTuplesByMyArg(String myArg);
}
Afterwards comes the implementation of custom interface
public class MyRepositoryImpl implements MyRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Tuple> findTuplesByMyArg(String myArg) {
JPAQuery query = new JPAQuery(em);
...
}
}
And we need to change MyRepository declaration, so it extends custom repository, so that
#Repository
public interface MyRepository extends JpaRepository<Tuple, Long>, MyRepositoryCustom {}
And you can easily access your findTuplesByMyArg() by injecting MyRepository, e.g.
#Service
public class MyService {
#Autowired
private MyRepository myRepository;
public List<Tuple> retrieveTuples(String myArg) {
return myRepository.findTuplesByMyArg(myArg);
}
}
Pay attention that names are important here (you need to have Impl postfix by default configs in repo implementation).
You can find all needed information here
I would suggest a minor rectification to the answer above, which tries to use JPAQueryFactory. It is good to make use of the provided factory class.
public class MyRepositoryImpl implements MyRepositoryCustom {
#Autowired
private JPAQueryFactory factory;
#Override
public List<Tuple> findTuplesByMyArg(String myArg) {
JPAQuery query = factory.query();
...
}}
#Configuration
public class Config {
#Autowired
private EntityManager em;
#Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}