Spring #Cacheable evict by SPEL or String pattern Key - spring

I'm caching a Pageable result:
#Cacheable(value="TasksByCategory", key = "{#categoryId, #pageable.offset, #pageable.pageSize}")
#Transactional(readOnly = true)
public Page<Task> getTasksByCategory(UUID categoryId, Pageable pageable){
return taskRepository.findByCategoryId(categoryId, pageable);
}
And I want to evict the Cache when a new Entry is added in the way smth like (I don't to evict all the cache entries):
#CacheEvict(value="TasksByCategory", key = "{#task.categoryId** }")
#Transactional
public Task createTask(Task task){
return taskRepository.save(task);
}
But I'm not sure that's possible.

Related

Oracle Parallel Hint on Spring Data JPA Specification

Is it possible to use parallel hint on the spring data jpa specification? org.hibernate.jpa.QueryHints package does not contain PARALLEL hint.
I added a comment like in the example below. But I couldn't see the running query.
Calling find method with specifications:
CarSpecifications carSpecifications = new CarSpecifications(car);
List<Car> carList = carRepository.findAll(carSpecifications, pageable);
Repository:
#Override
public Page<T> findAll(Specification<T> spec, Pageable pageable) {
TypedQuery<T> query = getQuery(spec, pageable);
query.setHint("org.hibernate.comment", "Example Comment");
return pageable == null ? new PageImpl<T>(query.getResultList()) : readPage(query, pageable, spec);
}
or in the interface
#QueryHints({ #QueryHint(name = "org.hibernate.comment", value ="Example Comment") })
Page<T> findAll(Specification<T> spec, Pageable pageable);
Thanks.

Multiple aliases with QuerydslBinderCustomizer

I'm using QuerydslPredicate in my RestController on an entity which has a date object, I want to be able to query for a date before/after/between given dates, hoping to have something like
GET /problems?createdOnAfter=XXX
GET /problems?createdOnBefore=YYY
GET /problems?createdOnAfter=XXX&createdOnBefore=YYY
My entity has the date field createdOn and I was hoping I could customise bindings for an entity path using multiple aliases i.e. adding aliases createdOnAfter & createdOnBefore - it doesn't look like I can create multiple aliases though, e.g.
#Repository
public interface ProblemRepository extends JpaRepository<Problem, String>, QueryDslPredicateExecutor<Problem>,
QuerydslBinderCustomizer<QProblem> {
....
#Override
default void customize(QuerydslBindings bindings, QProblem root) {
bindings.bind(root.createdOn).as("createdOnAfter").first(TemporalExpression::after);
bindings.bind(root.createdOn).as("createdOnBefore").first(TemporalExpression::before);
}
}
The before alias is obviously overwriting the after one.
What's the correct approach to avoid having to manually create the predicates?
Why not using QueryDSL Predicate ? You could do :
#GetMapping("/problems")
#Timed
public ResponseEntity<List<ProblemDTO>> getAllProblems(
#RequestParam(required = false) LocalDateTime createdOnAfter,
#RequestParam(required = false) LocalDateTime createdOnBefore,
#ApiParam Pageable pageable) {
BooleanBuilder where = new BooleanBuilder();
if (startDate != null) {
where = where.and(problem.createdOn.after(createdOnAfter));
}
if (endDate != null) {
where = where.and(problem.createdOn.before(createdOnBefore));
}
Page<Donnee> page = problemRepository.findAll(where, pageable);
return new ResponseEntity<>(problemMapper.toDTO(page.getContent())), null, HttpStatus.OK);
}
Hope it helps,
Regards
How about adding new Entity properties and marking those as transient like this:
#Transient
private Instant createdOnBefore;
#Transient
private Instant createdOnAfter;
and then customise your repository class like this:
#Override
default void customize(final QuerydslBindings bindings, final QProblem root) {
bindings.bind(root.createdOnBefore).first((path, value) -> root.createdOn.goe(value));
bindings.bind(root.createdOnAfter).first((path, value) -> root.createdOn.loe(value));
}

Spring rest controller and paging

I use spring 4.2 and rest and I would like to use paging.
What is the way to use paging with spring rest controller?
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public Page<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, Pageable pageable) {
return paymentService.getPaymentByMemberId(memberId, pageable);
}
Is it a good way to manage this?
If for some area in the application, we don't want to use paging, We need to create another url?
if I want all payments for a member, I will do:
/members/{memberId}/payments
and for the paging, it's there a way to said to spring to do something like:
/members/{memberId}/payments?pageNumber=1&PageSize=10
One way to do this is:
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public List<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, #RequestParam(value = "pageNumber", required = false) final Integer pageNumber,#RequestParam(value = "pageSize", required = false) final Integer pageSize) {
PageRequest pageReq = new PageRequest((pageNumber == null ? 0 : pageNumber), (pageSize == null ? 0 : pageSize));
Page<PaymentDto> page = paymentService.getPaymentByMemberId(memberId, pageReq);
return page.getContent();
}
You need write annotation #RestController for your controller
#RestController
public class PaymentController {
...
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public Page<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, Pageable pageable) {
return paymentService.getPaymentByMemberId(memberId, pageable);
}
}
Request example: /members/12345/payments?page=0&size=50

Spring Data JPA. How to get only a list of IDs from findAll() method

I have a very complicated model. Entity has a lot relationship and so on.
I try to use Spring Data JPA and I prepared a repository.
but when I invoke a method findAll() with specification for the object a have a performance issue because objects are very big. I know that because when I invoke a method like this:
#Query(value = "select id, name from Customer ")
List<Object[]> myFindCustomerIds();
I didn't have any problems with performance.
But when I invoke
List<Customer> findAll();
I had a big problem with performance.
The problem is that I need to invoke findAll method with Specifications for Customer that is why I cannot use method which returns a list of arrays of objects.
How to write a method to finding all customers with specifications for Customer entity but which returns only an IDs.
like this:
List<Long> findAll(Specification<Customer> spec);
I cannot use in this case pagination.
Please help.
Why not using the #Query annotation?
#Query("select p.id from #{#entityName} p")
List<Long> getAllIds();
The only disadvantage I see is when the attribute id changes, but since this is a very common name and unlikely to change (id = primary key), this should be ok.
This is now supported by Spring Data using Projections:
interface SparseCustomer {
String getId();
String getName();
}
Than in your Customer repository
List<SparseCustomer> findAll(Specification<Customer> spec);
EDIT:
As noted by Radouane ROUFID Projections with Specifications currently doesn't work beacuse of bug.
But you can use specification-with-projection library which workarounds this Spring Data Jpa deficiency.
I solved the problem.
(As a result we will have a sparse Customer object only with id and name)
Define their own repository:
public interface SparseCustomerRepository {
List<Customer> findAllWithNameOnly(Specification<Customer> spec);
}
And an implementation (remember about suffix - Impl as default)
#Service
public class SparseCustomerRepositoryImpl implements SparseCustomerRepository {
private final EntityManager entityManager;
#Autowired
public SparseCustomerRepositoryImpl(EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
public List<Customer> findAllWithNameOnly(Specification<Customer> spec) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> tupleQuery = criteriaBuilder.createTupleQuery();
Root<Customer> root = tupleQuery.from(Customer.class);
tupleQuery.multiselect(getSelection(root, Customer_.id),
getSelection(root, Customer_.name));
if (spec != null) {
tupleQuery.where(spec.toPredicate(root, tupleQuery, criteriaBuilder));
}
List<Tuple> CustomerNames = entityManager.createQuery(tupleQuery).getResultList();
return createEntitiesFromTuples(CustomerNames);
}
private Selection<?> getSelection(Root<Customer> root,
SingularAttribute<Customer, ?> attribute) {
return root.get(attribute).alias(attribute.getName());
}
private List<Customer> createEntitiesFromTuples(List<Tuple> CustomerNames) {
List<Customer> customers = new ArrayList<>();
for (Tuple customer : CustomerNames) {
Customer c = new Customer();
c.setId(customer.get(Customer_.id.getName(), Long.class));
c.setName(customer.get(Customer_.name.getName(), String.class));
c.add(customer);
}
return customers;
}
}
Unfortunately Projections does not work with specifications. JpaSpecificationExecutor return only a List typed with the aggregated root managed by the repository ( List<T> findAll(Specification<T> var1); )
An actual workaround is to use Tuple. Example :
#Override
public <D> D findOne(Projections<DOMAIN> projections, Specification<DOMAIN> specification, SingleTupleMapper<D> tupleMapper) {
Tuple tuple = this.getTupleQuery(projections, specification).getSingleResult();
return tupleMapper.map(tuple);
}
#Override
public <D extends Dto<ID>> List<D> findAll(Projections<DOMAIN> projections, Specification<DOMAIN> specification, TupleMapper<D> tupleMapper) {
List<Tuple> tupleList = this.getTupleQuery(projections, specification).getResultList();
return tupleMapper.map(tupleList);
}
private TypedQuery<Tuple> getTupleQuery(Projections<DOMAIN> projections, Specification<DOMAIN> specification) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<DOMAIN> root = query.from((Class<DOMAIN>) domainClass);
query.multiselect(projections.project(root));
query.where(specification.toPredicate(root, query, cb));
return entityManager.createQuery(query);
}
where Projections is a functional interface for root projection.
#FunctionalInterface
public interface Projections<D> {
List<Selection<?>> project(Root<D> root);
}
SingleTupleMapper and TupleMapper are used to map the TupleQuery result to the Object you want to return.
#FunctionalInterface
public interface SingleTupleMapper<D> {
D map(Tuple tuple);
}
#FunctionalInterface
public interface TupleMapper<D> {
List<D> map(List<Tuple> tuples);
}
Example of use :
Projections<User> userProjections = (root) -> Arrays.asList(
root.get(User_.uid).alias(User_.uid.getName()),
root.get(User_.active).alias(User_.active.getName()),
root.get(User_.userProvider).alias(User_.userProvider.getName()),
root.join(User_.profile).get(Profile_.firstName).alias(Profile_.firstName.getName()),
root.join(User_.profile).get(Profile_.lastName).alias(Profile_.lastName.getName()),
root.join(User_.profile).get(Profile_.picture).alias(Profile_.picture.getName()),
root.join(User_.profile).get(Profile_.gender).alias(Profile_.gender.getName())
);
Specification<User> userSpecification = UserSpecifications.withUid(userUid);
SingleTupleMapper<BasicUserDto> singleMapper = tuple -> {
BasicUserDto basicUserDto = new BasicUserDto();
basicUserDto.setUid(tuple.get(User_.uid.getName(), String.class));
basicUserDto.setActive(tuple.get(User_.active.getName(), Boolean.class));
basicUserDto.setUserProvider(tuple.get(User_.userProvider.getName(), UserProvider.class));
basicUserDto.setFirstName(tuple.get(Profile_.firstName.getName(), String.class));
basicUserDto.setLastName(tuple.get(Profile_.lastName.getName(), String.class));
basicUserDto.setPicture(tuple.get(Profile_.picture.getName(), String.class));
basicUserDto.setGender(tuple.get(Profile_.gender.getName(), Gender.class));
return basicUserDto;
};
BasicUserDto basicUser = findOne(userProjections, userSpecification, singleMapper);
I hope it helps.

Spring Data Pageable breaking Spring Data JPA OrderBy

I have a simple JpaRepository with a finder that returns records ordered by a property named "number" in descending order. The "number" property is also the #Id of my entity. This works just fine, however there's thousands of records, so I want to return a Page instead of a List.
#Repository
public interface ReceiptRepository extends JpaRepository<Receipt, BigDecimal> {
#Query
public List<Receipt> findByStorerOrderByNumberDesc(String storer);
}
If I change my finder to something like the following, the sorting no longer works. I've tried using the sort capability of the Pageable argument, but it didn't work. Also removed the OrderByNumberDesc, but same result.
#Repository
public interface ReceiptRepository extends JpaRepository<Receipt, BigDecimal> {
#Query
public Page<Receipt> findByStorerOrderByNumberDesc(String storer, Pageable pageable);
}
EDIT - added controller method
The following is my controller method.
#RequestMapping(method = RequestMethod.GET, produces = {"application/json"})
public PagedResources<Receipt> receipts(Pageable pageable, PagedResourcesAssembler assembler) {
Page<Receipt> receipts = receiptRepository.findByStorer("003845", pageable);
return assembler.toResource(receipts, receiptResourceAssembler);
}
I feel I'm missing something very basic here.
I'm using Spring Data JPA 1.5.2 and Commons 1.7.2.
Thanks :)
Add the sort to your Pageable when you create it:
e.g.
Pageable pageable ...;
pageable.getSort().and(new Sort(Direction.ASC, "prop1", "prop1"));
or in Spring MVC you can do:
#RequestMapping(method = RequestMethod.GET, produces = {"application/json"})
public PagedResources<Receipt> receipts(#PageableDefaults(sort = { "x",
"y", "z" }, value = 10)Pageable pageable, PagedResourcesAssembler assembler) {
Page<Receipt> receipts = receiptRepository.findByStorer("003845", pageable);
return assembler.toResource(receipts, receiptResourceAssembler);
}

Resources