Caching Spring Data repositories / CacheEvict not working - spring

I am using Spring Boot, Spring Data, with QueryDSL. Most of my queries are QueryDSL based. I want to implement a simple cache that stores User queries, and once a single entity is updated/saved then the entire Cache is cleared.
So here is my Repository Interface:
#Repository
#CacheConfig(cacheNames = "us")
public interface UserRepository extends JpaRepository<User, Long>, QueryDslPredicateExecutor<User> {
#Cacheable
List<User> findAll();
#Override
#Cacheable
List<User> findAll(Sort sort);
#Override
#Cacheable
List<User> findAll(Iterable<Long> longs);
#Override
#CacheEvict(allEntries = true)
<S extends User> List<S> save(Iterable<S> entities);
#Override
#CacheEvict(allEntries = true)
<S extends User> S saveAndFlush(S entity);
#Override
#Cacheable
Page<User> findAll(Pageable pageable);
#Override
#CacheEvict(allEntries = true)
<S extends User> S save(S entity);
#Override
#Cacheable
User findOne(Long aLong);
#Override
#Cacheable
Iterable<User> findAll(Predicate predicate);
#Override
#Cacheable
User findOne(Predicate predicate);
#Override
#Cacheable
Iterable<User> findAll(Predicate predicate, Sort sort);
#Override
#Cacheable
Page<User> findAll(Predicate predicate, Pageable pageable);
}
As you can see I have cached all "Find" methods and using Save methods to Evict all entries. Usually the ones used are:
Page<User> findAll(Predicate predicate, Pageable pageable);
<S extends User> S save(S entity);
The Caching is set exclusively on the repositories, nothing on Services/Controllers etc.
Here is my config:
#Bean
public CacheManager cacheManager() {
GuavaCacheManager manager = new GuavaCacheManager();
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder()
.removalListener(new RemovalListener<Object, Object>() {
#Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
LOG.info("Cache evicted: " + notification);
}
})
.maximumSize(100);
manager.setCacheBuilder(builder);
return manager;
}
#Bean
public KeyGenerator keyGenerator() {
return new SimpleKeyGenerator();
}
I should also add that the User entity has a couple of subclasses but I am querying everything with the UserRepository
The problem is that even though my Search requests are successfully cached the cache is never evicted when I perform a save.
Anything I'm doing wrong?

Related

Custom repository and specification

in a spring boot 3, i created a custom repository
#Repository
public class BookRepositoryCustomImpl implements BookRepositoryCustom {
}
public interface BookRepositoryCustom {
List<Book> searchBook(SearchBook searchBook);
}
#Repository
#Transactional(readOnly = true)
public interface BookRepository extends JpaRepository<Book, Long> {
}
I search to use specification in searckBook method
Edit with spring 2.x it was possible
#Repository
public class AirportRepositoryImpl extends SimpleJpaRepository<Airport, Integer>
implements AirportRepositoryCustom {
#Autowired
public AirportRepositoryImpl(EntityManager em) {
super(Airport.class, em);
}
#Override
public Page<Airport> advancedSearch(AirportSearch search, Pageable page) {
Specification<Airport> specification = (Root<Airport> root, CriteriaQuery<?> cq,
CriteriaBuilder cb) -> {
Predicate p = cb.conjunction();
...
return p;
};
return this.findAll(specification, page);
}
}
You can't use the JpaSpecificationExecutor from within your custom method implementation.
But a Specification just creates a Predicate from a Root, a CriteriaQuery, and a CriteriaBuilder.
You can inject the EntityManager into BookRepositoryCustomImpl, and use the Criteria API to create a query including using the Predicate from a Specification.

Mockito Page<> test return null

I'm testing controller using mockito. Even though I stubbed about the getBoardList, It doesn't initiate the method.
This is the controller. getBoardList() doesn't initiate when I checked in debug mode.
#GetMapping
public String getBoardListView(#Valid #Nullable BoardDto.Request request,
#PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.ASC) Pageable pageable,
ModelMap map) {
Page<BoardDto.Response> boardList = postService.getBoardList(request, pageable);
map.addAttribute("boardList", boardList);
return "board/index";
}
This is the controllerTest
#MockBean private PostService postService;
#Test
void getBoardListView() throws Exception {
Page<BoardDto.Response> mock = Mockito.mock(Page.class);
when(postService.getBoardList(eq(null), any(Pageable.class))).thenReturn(mock);
mockMvc.perform(get("/board").with(csrf()))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
.andExpect(model().attributeExists("boardList"))
.andExpect(view().name("board/index"));
then(postService).should().getBoardList(any(BoardDto.Request.class), any(Pageable.class));
}
This is PostService interface.
public interface PostService {
Page<BoardDto.Response> getBoardList(BoardDto.Request request, Pageable pageable);
}
This is PostServiceImpl
#RequiredArgsConstructor
#Transactional(readOnly = true)
#Service
public class PostServiceImpl implements PostService {
private final PostRepository postRepository;
#Override
public Page<BoardDto.Response> getBoardList(BoardDto.Request request, Pageable pageable) {
return postRepository.findBoardList(request, pageable).map(BoardDto.Response::from);
}
}
Instead of:
when(postService.getBoardList(eq(null) ...
try:
when(postService.getBoardList(any(BoardDto.Request.class)
If you want to match a null argument, use ArgumentMatchers#isNull, not eq(null):
when(postService.getBoardList(isNull(), any(Pageable.class))).thenReturn(mock);

How to secure REST APIs and not JpaRepository

I am trying to have unsecured DAOs and secured REST APIs using Spring Data, Spring Data JPA and Spring Data Rest.
For example, I have the DAO repository which should not be secured since I want to call its methods from anywhere without having to provide an authentication:
#RepositoryRestResource(exported = false, path = "persons")
public interface UserRepository extends JpaRepository<User, Long> {
int countByUsername(String username);
void deleteByUsername(String username);
Optional<User> findByUsername(String username);
}
And I have the REST repository, which must be secured using authorizations of the current user:
#RepositoryRestResource(path = "persons")
#PreAuthorize("hasAuthority('FETCH_USER')")
public interface UserRestRepository extends PagingAndSortingRepository<User, Long> {
#PreAuthorize("hasAuthority('DELETE_USER')")
#Override
void delete(User user);
#PreAuthorize("hasAuthority('DELETE_USER')")
#Override
void deleteAll();
#PreAuthorize("hasAuthority('DELETE_USER')")
#Override
void deleteAll(Iterable<? extends User> iterable);
#PreAuthorize("hasAuthority('DELETE_USER')")
#Override
void deleteById(Long id);
#Override
Iterable<User> findAll();
#Override
Page<User> findAll(Pageable pageable);
#Override
Iterable<User> findAll(Sort sort);
#Override
Iterable<User> findAllById(Iterable<Long> iterable);
#Override
Optional<User> findById(Long id);
#PreAuthorize("hasAuthority('SAVE_USER')")
#Override
<S extends User> S save(S s);
#PreAuthorize("hasAuthority('SAVE_USER')")
#Override
<S extends User> Iterable<S> saveAll(Iterable<S> iterable);
}
The problem here is that it seems to automatically use the JpaRepository to create and configure the REST service, so I am forced to add exported = false on it to only expose methods of the REST repository, BUT there's still a problem, if I want to set change path (to persons) on the REST service, it doesn't work, I still need to duplicate it on the JpaRepository...
Am I not supposed to do like this ?
Is it not possible to create a separate JpaRepository and PagingAndSortingRepository, one for the DAO and the other for the REST ?
Secure the endpoints (URLs) using Spring Security rather than the
methods. javabullets.com/spring-security-spring-data-rest
See Alan Hays comment

How to prevent some HTTP methods from being exported from my MongoRepository?

I'm using spring-data-rest and I have a MongoRepository like this:
#RepositoryRestResource
interface MyEntityRepository extends MongoRepository<MyEntity, String> {
}
I would like to allow the GET methods but disable PUT, POST, PATCH and DELETE (read only web service).
According to http://docs.spring.io/spring-data/rest/docs/2.2.2.RELEASE/reference/html/#repository-resources.collection-resource I should be able to do that like this:
#RepositoryRestResource
interface MyEntityRepository extends MongoRepository<MyEntity, String> {
#Override
#RestResource(exported = false)
public MyEntity save(MyEntity s);
#Override
#RestResource(exported = false)
public void delete(String id);
#Override
#RestResource(exported = false)
public void delete(MyEntity t);
}
It doesn't seem to work as I can still do PUT, POST, PATCH and DELETE requests.
Thanks to Oliver, here are the methods to override:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
// Prevents GET /people/:id
#Override
#RestResource(exported = false)
public Person findOne(String id);
// Prevents GET /people
#Override
#RestResource(exported = false)
public Page<Person> findAll(Pageable pageable);
// Prevents POST /people and PATCH /people/:id
#Override
#RestResource(exported = false)
public Person save(Person s);
// Prevents DELETE /people/:id
#Override
#RestResource(exported = false)
public void delete(Person t);
}
This is late reply, but if you need to prevent the global http method for a entity, try it.
#Configuration
public class DataRestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getExposureConfiguration()
.forDomainType(Person.class)
.withItemExposure(((metdata, httpMethods) -> httpMethods.disable(HttpMethod.PUT, HttpMethod.POST, ... )))
.withCollectionExposure((metdata, httpMethods) -> httpMethods.disable(HttpMethod.PUT, HttpMethod.POST, ...));
}
}
Why not just use like this?
#Configuration
public class SpringDataRestConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration restConfig) {
restConfig.disableDefaultExposure();
}
}

How to add cache feature in Spring Data JPA CRUDRepository

I want to add "Cacheable" annotation in findOne method, and evict the cache when delete or happen methods happened.
How can I do that ?
virsir, there is one more way if you use Spring Data JPA (using just interfaces). Here what I have done, genereic dao for similar structured entities:
public interface CachingDao<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
#Cacheable(value = "myCache")
T findOne(ID id);
#Cacheable(value = "myCache")
List<T> findAll();
#Cacheable(value = "myCache")
Page<T> findAll(Pageable pageable);
....
#CacheEvict(value = "myCache", allEntries = true)
<S extends T> S save(S entity);
....
#CacheEvict(value = "myCache", allEntries = true)
void delete(ID id);
}
I think basically #seven's answer is correct, but with 2 missing points:
We cannot define a generic interface, I'm afraid we have to declare every concrete interface separately since annotation cannot be inherited and we need to have different cache names for each repository.
save and delete should be CachePut, and findAll should be both Cacheable and CacheEvict
public interface CacheRepository extends CrudRepository<T, String> {
#Cacheable("cacheName")
T findOne(String name);
#Cacheable("cacheName")
#CacheEvict(value = "cacheName", allEntries = true)
Iterable<T> findAll();
#Override
#CachePut("cacheName")
T save(T entity);
#Override
#CacheEvict("cacheName")
void delete(String name);
}
Reference
I solved the this in the following way and its working fine
public interface BookRepositoryCustom {
Book findOne(Long id);
}
public class BookRepositoryImpl extends SimpleJpaRepository<Book,Long> implements BookRepositoryCustom {
#Inject
public BookRepositoryImpl(EntityManager entityManager) {
super(Book.class, entityManager);
}
#Cacheable(value = "books", key = "#id")
public Book findOne(Long id) {
return super.findOne(id);
}
}
public interface BookRepository extends JpaRepository<Book,Long>, BookRepositoryCustom {
}
Try provide MyCRUDRepository (an interface and an implementation) as explained here: Adding custom behaviour to all repositories. Then you can override and add annotations for these methods:
findOne(ID id)
delete(T entity)
delete(Iterable<? extends T> entities)
deleteAll()
delete(ID id)

Resources