Applying groupBy to Spring specification query - spring

I'm building a simple search engine using Spring specification-driven repositories. The problem is that i make joinful queries which end in duplicating records, and i need to apply a groupBy restriction, which isn't applied to resulting query if i call .groupBy in my specification. How can i achieve such grouping?
The minimal example may be specified as follows:
Repository
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface EntityRepository implements JpaSpecificationExecutor<Entity> {}
Specification
import org.springframework.data.jpa.domain.Specification;
public class DummyEntitySpecification implements Specification<Entity> {
public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> criteriaQuery,
CriteriaBuilder builder) {
// So from here i get results with repeated main entity data, but actually
// i need to filter results that has presence of related subentities of
// some sort
From join = root.join("SubEntity");
// though i call groupBy here, Spring internally uses another query
// instance, which doesn't inherit grouping
criteriaQuery.groupBy(root.get("id"));
return criteriaBuilder.ge(join.get("id"), 1);
}
}
Call
public class Anyclass {
#Autowired
private EntityRepository entityRepository;
public void uselessSearch() {
// this may return several entities with id = 1, for example
entityRepository.findAll(new DummyEntitySpecification());
}
}

Related

REST with Spring: Where to place logic for mapping from frontend DTO to JPA Specification and Pageable?

I want to use a rather complex DTO (from the frontend) which contains filtering and pagination parameters to map/build a Specification and a Pageable to use in query in the underlying JPA Repository. The call stack is as follows: controller -> view -> service -> repository.
What would be conceptually the cleanest approach to place the DTO-to-Specification/Pageable method(s)?
For comparison: so far, I placed it as a private method in the service class, like so (not an actual code snippet). Is that architecturally clean or would e.g. a separate class (like a FooSpecificationBuilder) be more appropriate?
#Service
public class FooService {
private final FooRepository repository;
public Page<Foo> getFooPage(FrontendSearchDto searchDto) {
Specification<Foo> spec = buildSpec(searchDto);
Pageable pageable = buildPageable(searchDto);
return repository.findAll(spec, pageable);
}
private Specification<Foo> buildSpec(FrontendSearchDto searchDto) {
// do the mapping
}
private Pageable buildPageable(FrontendSearchDto searchDto) {
// do the mapping
}
}

Spring Data - Build where clause at runtime

In Spring Data, how can I append more conditions to an existing query?
For example, I have the CrudRepository below:
#RepositoryRestResource
public interface MyRep extends CrudRepository<MyObject, Long> {
#Query("from MyObject mo where mo.attrib1 = :attrib1")
List<MyObj> findMyObjects(String attrib1, String conditions);
}
At runtime, I will need to call "findMyObjects" with two params. The first param is obviously the value of attrib1. the second param will be a where clause that would be determined at runtime, for example "attrib2 like '%xx%' and attrib3 between 'that' and 'this' and ...". I know this extra where condition will be valid, but I don't know what attributes and conditions will be in it. Is there anyway to append this where clause to the query defined in the #Query annotation?
Unfortunately, no. There is no straightforward way to achieve that.
You'll want to use custom reporistory methods where you'll be able to inject an EntityManager and interact with EntityManager.createQuery(...) directly.
Alternatively, you can build dynamic queries using Specifications or QueryDsl.
I ended up injecting an EntityManager that I obtained in the rest controller. Posting what I did here for criticism:
The repository code:
#RepositoryRestResource
public interface MyRepo extends CrudRepository<MyObject, Long> {
default List<MyObject> findByRuntimeConditions(EntityManager em, String runtimeConditions) {
String mySql = "<built my sql here. Watch for sql injection.>";
List<MyObject> list = em.createQuery(mySql).getResultList();
return list
}
}
The Rest controller code:
#RestController
public class DataController {
#Autowired
EntityManager em;
// of course watch for sql injection
#RequestMapping("myobjects/{runtimeConditions}")
public List<MyObject> getMyObjects(#PathVariable String runtimeConditions) {
List<MyObject> list = MyRepo.findByRuntimeConditions(em, runtimeConditions);
return list;
}
}

Multi-Column Search with Spring JPA Specifications

I want to create a multi field search in a Spring-Boot back-end. How to do this with a Specification<T> ?
Environment
Springboot
Hibernate
Gradle
Intellij
The UI in the front end is a Jquery Datatable. Each column allows a single string search term to be applied. The search terms across more than one column is joined by a and.
I have the filters coming from the front end already getting populated into a Java object.
Step 1
Extend JPA Specification executor
public interface SomeRepository extends JpaRepository<Some, Long>, PagingAndSortingRepository<Some, Long>, JpaSpecificationExecutor {
Step2
Create a new class SomeSpec
This is where I am lost as to what the code looks like it and how it works.
Do I need a method for each column?
What is Root and what is Criteria Builder?
What else is required?
I am rather new at JPA so while I don't need anyone to write the code for me a detailed explanation would be good.
UPDATE
It appears QueryDSL is the easier and better way to approach this. I am using Gradle. Do I need to change my build.gradle from this ?
If you don't want to use QueryDSL, you'll have to write your own specifications. First of all, you need to extend your repository from JpaSpecificationExecutor like you did. Make sure to add the generic though (JpaSpecificationExecutor<Some>).
After that you'll have to create three specifications (one for each column), in the Spring docs they define these specifications as static methods in a class. Basically, creating a specification means that you'll have to subclass Specification<Some>, which has only one method to implement, toPredicate(Root<Some>, CriteriaQuery<?>, CriteriaBuilder).
If you're using Java 8, you can use lambdas to create an anonymous inner class, eg.:
public class SomeSpecs {
public static Specification<Some> withAddress(String address) {
return (root, query, builder) -> {
// ...
};
}
}
For the actual implementation, you can use Root to get to a specific node, eg. root.get("address"). The CriteriaBuilder on the other hand is to define the where clause, eg. builder.equal(..., ...).
In your case you want something like this:
public class SomeSpecs {
public static Specification<Some> withAddress(String address) {
return (root, query, builder) -> builder.equal(root.get("address"), address);
}
}
Or alternatively if you want to use a LIKE query, you could use:
public class SomeSpecs {
public static Specification<Some> withAddress(String address) {
return (root, query, builder) -> builder.like(root.get("address"), "%" + address + "%");
}
}
Now you have to repeat this for the other fields you want to filter on. After that you'll have to use all specifications together (using and(), or(), ...). Then you can use the repository.findAll(Specification) method to query based on that specification, for example:
public List<Some> getSome(String address, String name, Date date) {
return repository.findAll(where(withAddress(address))
.and(withName(name))
.and(withDate(date));
}
You can use static imports to import withAddress(), withName() and withDate() to make it easier to read. The where() method can also be statically imported (comes from Specification.where()).
Be aware though that the method above may have to be tweaked since you don't want to filter on the address field if it's null. You could do this by returning null, for example:
public List<Some> getSome(String address, String name, Date date) {
return repository.findAll(where(address == null ? null : withAddress(address))
.and(name == null ? null : withName(name))
.and(date == null ? null : withDate(date));
}
You could consider using Spring Data's support for QueryDSL as you would get quite a lot without having to write very much code i.e. you would not actually have to write the specifictions.
See here for an overview:
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
Although this approach is really convenient (you don’t even have to
write a single line of implementation code to get the queries
executed) it has two drawbacks: first, the number of query methods
might grow for larger applications because of - and that’s the second
point - the queries define a fixed set of criterias. To avoid these
two drawbacks, wouldn’t it be cool if you could come up with a set of
atomic predicates that you could combine dynamically to build your
query?
So essentially your repository becomes:
public interface SomeRepository extends JpaRepository<Some, Long>,
PagingAndSortingRepository<Some, Long>, QueryDslPredicateExecutor<Some>{
}
You can also get request parameters automatically bound to a predicate in your Controller:
See here:
https://spring.io/blog/2015/09/04/what-s-new-in-spring-data-release-gosling#querydsl-web-support
SO your Controller would look like:
#Controller
class SomeController {
private final SomeRepository repository;
#RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model,
#QuerydslPredicate(root = Some.class) Predicate predicate,
Pageable pageable) {
model.addAttribute("data", repository.findAll(predicate, pageable));
return "index";
}
}
So with the above in place it is simply a Case of enabling QueryDSL on your project and the UI should now be able to filter, sort and page data by various combinations of criteria.

how to get tuple with average(xxx) by spring data jpa specification?

I want to get the List<Map<String,Object>>returned by query like this:
[{production:{the object}, avgscore:123}, {production:{other object},avgscore:456}, ...]
I can get it by this :
public interface ProductionRepository extends PagingAndSortingRepository<Production, Long> {
#Query("SELECT p as productions,avg(c.score) as avgscore FROM ......")
List<Map<String,Object>> findSorted(......);
}
but now I try to use Specification, and I done the specification:
Specification specification = new Specification() {
#Override
public Predicate toPredicate(Root root1, CriteriaQuery query, CriteriaBuilder cb) {
.......
.......
if (date2 != null) {
Predicate endTimeP = cb.lessThanOrEqualTo(root.get(Production_.productDate), date2);
predicates.add(endTimeP);
}
query.groupBy(root.get(Production_.id)).orderBy(cb.desc(cb.avg(commentJoin.get(Comment_.score))));
Predicate[] predicates1 = new Predicate[predicates.size()];
predicates.toArray(predicates1);
query.multiselect(root.alias("production"),
cb.avg(commentJoin.get(Comment_.score)).as(Double.class).alias("avgscore"));
return cb.and(predicates1);
}
and then I use this to findAll:
return specificationRepo.findAll(specification);
the repository is:
#Repository
public interface SpecificationRepository extends PagingAndSortingRepository<Production, Long>,
JpaSpecificationExecutor {
List<Map<String,Object>> findAll(Specification specification);
}
BUT, the findAll() only return List<Production> , no matter how I change the return type, it always return List<production> , not List<Map<String,Object>> I wanted.
it it bind to the Production in PagingAndSortingRepository<Production, Long>. If I delete this and only have JpaSpecificationExecutor, spring will not seem it as a #Repository; The Production position must be a #Entity, I can not change it to a custom java thing having Production and avgscore.
I have searched long time, but still didn't find how to solve.

how to get unique values from a column using queryDsl predicate

I am trying to get a unique value from a column say "designation" from a table "employee_register". I dont know how to acheive this using the query Dsl predicate. Can anyone help me with this
You can call distinct() on the Query object. For example (JPA + QueryDSL):
#Test
#Transactional
public void testQueryDSLDistinct() throws Exception {
log.debug("testQueryDSLDistinct started");
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
QEmployeeRegister er = QEmployeeRegister.employeeregister;
List<EmployeeRegister> tuples = queryFactory.select(
Projections.bean(EmployeeRegister.class, er.designation)).distinct()
.from(er).limit(10).fetch();
for (EmployeeRegister record: tuples) {
System.out.println(record.getDesignation());
}
}
I've found this while going through this link
http://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-part-four-jpa-criteria-queries .A similar question was raised by a viewer called raghu and below is the author's answer to the question.May be this one would be helpful to others
Author's answer
You have two options for implementing this:
Use the DISTINCT keyword of JPQL when you are creating query by using the #NamedQuery or #Query annotation.
Call the disctinct() method of the CriteriaQuery class in your specification builder method (The toPredicate() method of the Specification interface gets a reference of the CriteriaQuery object as a parameter).
JPQL Example:
SELECT DISTINCT p FROM Person p WHERE...
Criteria API with Specification Builder:
public class PersonSpecifications {
public static Specification lastNameIsLike(final String searchTerm) {
return new Specification () {
#Override
public Predicate toPredicate(Root personRoot, CriteriaQuery< ?> query,CriteriaBuilder cb) {
query.distinct(true);
//Build Predicate
}
};
}
}
In your case, I would add the following method to the CustomerRepository interface (or whatever your repository interface is):
#Query("SELECT DISTINCT c.lastName FROM Customer c")
public List<String> findLastNames();

Resources