Spring Data Rest - Base Repository using Generics not working - spring-boot

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

Related

Spring Data repository: set fetch size as a parameter

I have a Spring Data repository like this:
public interface MyRepo extends JpaRepository<MyEntity, Long> {
#QueryHints(#javax.persistence.QueryHint(name="org.hibernate.fetchSize", value="${fetch.size}"))
List<MyEntity> findAll();
}
I added fetch.size=100 into application.properties but get this error:
java.lang.NumberFormatException: For input string: "${fetch.size}"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:569)
at java.lang.Integer.valueOf(Integer.java:766)
at org.hibernate.jpa.internal.util.ConfigurationHelper.getInteger(ConfigurationHelper.java:81)
at org.hibernate.query.internal.AbstractProducedQuery.setHint(AbstractProducedQuery.java:1035)
at org.hibernate.query.internal.AbstractProducedQuery.setHint(AbstractProducedQuery.java:106)
at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.setHint(CriteriaQueryTypeQueryAdapter.java:145)
at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.setHint(CriteriaQueryTypeQueryAdapter.java:59)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applyQueryHints(SimpleJpaRepository.java:766)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applyRepositoryMethodMetadata(SimpleJpaRepository.java:758)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:678)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:655)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:346)
Does Spring support this type of property injection?
The short answer is: you cannot include a dynamic value in QueryHints annotation. It is possible to do something like:
#QueryHints(#javax.persistence.QueryHint(name="org.hibernate.fetchSize", value="" + Integer.MAX_VALUE))
but, as Integer.MAX_VALUE, only constants are allowed.
So basically you have the following options:
Include it in a property file
Add the required value in your property file, as you can see here
Get internal Entity Manager
Here you have to face up two main problems:
Include desired fetchSize value into your query.
Get fetchSize value from a property file and use it in an interface.
For the second one, you can take a look a workaround here. An easier example:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class Constant {
public static int FETCH_SIZE;
#Value("${fetch.size}")
public void setFetchSizeStatic(int fetchSize){
Constant.FETCH_SIZE = fetchSize;
}
}
For the first point, more changes are required:
1.1 Create a custom Repository to have access to EntityManager:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import javax.persistence.EntityManager;
import java.io.Serializable;
#NoRepositoryBean
public interface ExtendedJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
/**
* Return the internal {#link EntityManager} to provide more functionality to the repositories
*
* #return {#link EntityManager}
*/
EntityManager getEntityManager();
}
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import javax.persistence.EntityManager;
import java.io.Serializable;
public class ExtendedJpaRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements ExtendedJpaRepository<T, ID> {
private EntityManager entityManager;
public ExtendedJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
#Override
public EntityManager getEntityManager() {
return entityManager;
}
}
1.2 Configure the new Repositories:
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#Configuration
#EnableJpaRepositories(basePackages = "PATH_TO_YOUR_REPOSITORIES", repositoryBaseClass = ExtendedJpaRepositoryImpl.class)
public class PersistenceConfiguration { }
Now it will be possible to create a custom Repository, configuring a dynamic value for a hint:
import org.hibernate.jpa.QueryHints;
#Repository
public interface MyEntityRepository extends ExtendedJpaRepository<MyEntity, Long> {
default List<MyEntity> findAll() {
return getEntityManager().createQuery("select e "
+ "from MyEntity e ", MyEntity.class)
.setHint( QueryHints.HINT_FETCH_SIZE, Constant.FETCH_SIZE )
.getResultList();
}
}

Spring Data Repository without specifying Entity

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

How to override super interface #PreAuthorize expression

I have a generic service with annotations, this is extended by every service interface. How can I override this annotation from the service interface?
I put the #PreAuthorize annotation on the service interface but it did not work, however, when I put this on the service implementation it did.
My generic service interface looks like this:
public interface GenericService<T, ID> {
#PreAuthorize(/*Generic condition*/)
T get(ID id);
}
My entity's service interface (where I want this to work)
public interface EntityService extends GenericService<Entity, Integer> {
#PreAuthorize(/*Specific condition*/)
Entity get(Integer id);
}
My entity's service implementation (where it does work)
#Service
public class EntityServiceImpl extends AbstractServiceImpl<Entity, Integer> implements EntityService {
#Override
#PreAuthorize(/*Specific condition*/)
public Entity get(Integer id) {
return super.get(id);
}
}
Slice of my abstract service implementation class:
public abstract class AbstractServiceImpl<T extends AbstractEntity<ID>, ID> implements GenericService<T, ID> {
#Override
public T get(ID id) {
return repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(id));
}
}
I don't want to put this on the implementation because this is not specific to it. It must be the same condition across every posible implementation of the service.

JpaRepository of abstract #MappedSuperclass without #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> {
}

Best practise when using Querydsl with Spring Data

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

Resources