Spring Data - QueryDSL InnerJoin predicate - spring

Lets assume we have the following domain objects (partially complete to reduce code).
public class Student {
#OneToMany(mappedBy="student")
List<Assignment> assignments;
}
public class Assignment {
#ManyToOne
Student student;
#OneToOne
Implementation implementation;
}
public class Implementation {
#OneToOne
Assignment assignment;
#OneToMany(mappedBy="implementation")
List<Assessment> assessments;
}
public class Assessment {
#ManyToOne
Implementation implementation;
String grade;
}
So the query I want to perform is "Select all students whose assignment implementation has been performed (not null) and has not been assessed at all (List<Assessment>#isEmpty())
So I'm using QueryDSL and try to use the following query just to get Students with non-implemented assignments
public class MyService {
#Autowired
private StudentRepository studentRepository;
public Iterable<Student> foo() {
return studentRepository.findAll(
QStudent.student.assignments.any().implementation.isNotNull()
);
}
}
and the above query seems to get ignored.
Then I use the following to get the combination (implemented exercises and empty assessments)
studentRepository.findAll(
QStudent.student.assignments.any().implementation.isNotNull()
.and(QStudent.student.assignments.any().implementation.assessments.isEmpty())
);
Again this makes no differences. Any idea what I'm doing wrong

Have you tried this:
QAssignment assignment = QAssignment.assignment;
ListSubQuery<Assignment> subQuery = new JPASubQuery().from(assignment)
.where(assignment.implementation.isNotNull()))
.and(assignment.implementation.assessments.isEmpty())
.list(assignment);
studentRepository.findAll(
QStudent.student.assignments.contains(subQuery)
);
I'm not sure why your code is not working, but I guess it's because
any() is only a subquery exists shortcut.
See https://stackoverflow.com/a/25453708/2672352

Related

Spring Data: can I create a method in the #Repository with a name what return list of object if String equals to constant?

Guess easier if I show you my example:
#Entity
class User {
Long id;
Status status;
}
enum Status {
NEW("N"), DELETED("D")
}
I have an AttributeConverter on Status so in DB the enum is stored with one character.
In my database I have entities like:
Table user
------------
Id Status
1 N
2 N
3 D
4 N
5 D
I want a method that list the Users with Status D. Something like this:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByStatusEqualsD();
or
List<User> findByStatusEqualsDeleted();
problem is these are not working
}
I could write this:
List<User> findByStatus(Status status);
And call it as repo.findByStatus(Status.DELETED) but I want a method what returns only the deleted users.
If I call it as repo.findByStatus(Status.NEW) then it will return the new users.
I prefer to not write a #Query, I hope it is possible what I'm asking without doing it...
Thanks in advance.
Such behavior is not supported.
Method name is translated into JPQL expression (which is the same as used in #Query) with parameters in it (if needed) so you have to provide these. (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation)
If you want query parameters to be hardcoded - #Query is what you need.
Alternatively you can have default method in your repository calling the parametrized one as mentioned here JpaRepository with Enum: findAllByOfferState_ACTIVE. NoSuchElementException
Easy,
You don't need a repo for that. Create a Service instead:
public interface UserDAOService{
List<User> getAllDeletedUsers();
}
And then just implement it with hardcoded findByStatus method from repo:
#Service
public class UserDAOServiceImpl implements UserDAOService{
private final UserRepository userRepository;
public UserDAOServiceImpl(UserRepository userRepository) {
this.userRepository= userRepository;
}
#Override
public List<Author> getAllDeletedUsers();
return userRepository.findByStatus(Status.DELETED);
}

mapping entity to DTO with nested DTO

I have a relationnal (heavy) database model with a lot of table dependencies and foreign keys.
We have choosen to use DTOs in order to simplify data representation ton front and hide database mode complixity.
But we have DTO with nested DTO. And we have Mapper implementation classes to set data with small business/functional logic.
The question is if it is a good pratice that a mapper class calls mapper (etc.) or is it a best way to have a main class handling all mapper classes ? (Example 1 or2)
Example 1 :
#Component
public class ActorMapperImpl implements ActorMapper {
#Autowired
private InsurerMapper insurerMapper;
#Autowired
private PersonMapper personMapper;
#Autowired
private CorrespondentMapper correspondentMapper;
....
#Override
public ActorDto mapToDto(Acteur actor) {
final ActorDto actorDto;
if (actor != null) {
....
actorDto.setPerson(personMapper.personneToPersonDto(actor.getPersonne()));
if (actor.getInsurer() != null) {
actorDto.setInsurer(insurerMapper.entityToDto(actor.getInsurer()));
} else if (actor.getCorrespondantAssureur() != null) {
actorDto.setInsurer(correspondentMapper.correspondentToInsurerDto(actor.getCorrespondantAssureur()));
}
....
// intermediate
final Intermediaire intermediate = actor.getIntermediaire();
if (intermediate != null) {
.....
if (person != null) {
intermediateDto = personMapper.personneToPersonDto(person);
intermediateDto.setQuality(quality);
}
.....
}
.....
Example 2 :
#Service
public class FinancialSlipOrchestratorImpl implements FinancialSlipOrchestrator {
.....
#Autowired
private FinancialSlipMapper financialSlipMapper;
#Autowired
private PersonMapper personMapper;
..... some public / private methods
private FinancialSlipDto fullMapToDto(FinancialSlip financialSlip) {
.....
// Financial slip
var financialSlipDto = financialSlipMapper.mapToDto(financialSlip);
// person
financialSlipDto.setIssuerPerson(personMapper.personneToPersonDto(financialSlip.getIssuerPerson()));
....
// RIB
financialSlipDto.setRib(ribMapper.mapToDto(financialSlip.getRib()));
return financialSlipDto;
}
I would say that it's ok for one mapper to call another and think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Acteur.class)
public interface ActorDto {
#IdMapping
Long getId();
String getName();
PersonDto getPerson();
default InsurerDto getInsurer() {
return getMainInsurer() != null ? getMainInsurer(): getCorrespondantAssureur();
}
#Mapping("insurer")
InsurerDto getMainInsurer();
InsurerDto getCorrespondantAssureur();
IntermediaireDto getIntermediaire();
#EntityView(Person.class)
interface PersonDto {
#IdMapping
Long getId();
String getName();
}
#EntityView(Insurer.class)
interface InsurerDto {
#IdMapping
Long getId();
String getName();
}
#EntityView(Intermediaire.class)
interface IntermediaireDto {
#IdMapping
Long getId();
String getName();
Integer getQuality();
PersonDto getPerson();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ActorDto a = entityViewManager.find(entityManager, ActorDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
The best thing about this is, it will only fetch the data that is actually necessary.
If you use DTOs for flushing back changes as well, you will be delighted to hear that Blaze-Persistence Entity-Views also supports that in a very efficient manner. This will allow you to get rid of all those manually written mappers :)

Spring Boot JPA find, filter

As Spring jpa Provides some usefull features to find Items from a repository by defining it in the method name. e .x findByTitle(String title) then Spring is automatically searching the Title Colum for the given String. If i have an int column named numberOfCopies and i want only to find the datasets with >0 greater then null how would define such a method ?
to filter out those books with the numberOfCopies equals 0 = zero
#Entity
public class Book {
#Id
private int id;
private String title;
private int numberOfCopies;
}
can i use the Repomethod
public List findBooksByNumberOfCopies.greater then 0 ?To Use this Spring Feature without some if or for loops
First, you should use Integer, since it is better, in my opinion, to use wrapper classes than to primitives, and enforce not null requirement through annotations, e.g. #Column(nullable = false)
#Entity
public class Book {
#Id
private Integer id;
private String title;
private Integer numberOfCopies;
}
Then you can add the following two methods in your BookRepository;
List<Book> findByNumberOfCopiesGreaterThan(Integer numberOfCopies);
default List<Book> findAllAvailableBooks() {
return findByNumberOfCopiesGreaterThan(0);
}
and use the default findAllAvailableBooks method, with hardcoded 0 value which is your requirement.
you can easily use
List<Book> findByNumberOfCopiesGreaterThanEqual(int numberOfCopies);
Pretty sure this would work:
public interface BookRepo extends JpaRepository<Book, Integer> {
#Query("SELECT b FROM Book b WHERE b.numberOfCopies >= 0")
public Optional<List<Book>> getTheBooksWithMultCopies();
}
// back in your component class:
{
...
Optional<List<Book>> optionalBookList = myBookRepo.getTheBooksWithMultCopies();
if (optionalBookList.isPresent()){
List<Book> bookList = optionalBookList.get();
}
}
Note that the language within the query is called HQL, which is what is used by Hibernate internally (which is used by JPA internally). It's really not very intimidating - just, know that you the objects in your POJO, which map to your database table, rather than your database table directly.
Also, I'd recommend using Integer over int in entity classes, at least if your value is nullable. Otherwise, numberOfCopies will always default to 0, which may not be desirable and may cause exceptions that are difficult to decipher.
GreaterThanEqual takes an Integer not int
List<Book> findByNumberOfCopiesGreaterThanEqual(Integer numberOfCopies);

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 JPA Specifications - #OneToMany dependency

i have a problem with getting List from entity Person using Spring data JPA specifications (because of pagination). I need to get all notes by person but dependency between these two entities is on Person side. I don't know how to create my Predicate because Note doesn't contain any attribute related to Person.
I simply can get List with Persons getter but i can't use this way because i need returned data paginated.
#Entity
public class Person implements Serializable {
#Id
private Long personId;
#OneToMany
#JoinColumn(name = "personId")
private List<Note> notes;
}
#Entity
public class Note implements Serializable {
#Id
private Long noteId;
}
Normally, I would write something like this, but i don't have an attribute person in Note and database can't be remapped at this stage.
public static Specification<Note> notesByPerson(final Long personId) {
return new Specification<Note>() {
#Override
public Predicate toPredicate(final Root<Note> root, final CriteriaQuery<?> query,
final CriteriaBuilder builder) {
final Path<Person> per = root.<Person> get("person");
return builder.equal(per.<Long> get("personId"), personId);
}
};
}
Thank you,
Zdend
Solved..
public static Specification<Note> notesByPerson(final Long personId) {
return new Specification<Note>() {
#Override
public Predicate toPredicate(final Root<Note> noteRoot, final CriteriaQuery<?> query,
final CriteriaBuilder cb) {
final Subquery<Long> personQuery = query.subquery(Long.class);
final Root<Person> person = personQuery.from(Person.class);
final Join<Person, Note> notes = person.join("notes");
personQuery.select(notes.<Long> get("noteId"));
personQuery.where(cb.equal(person.<Long> get("personId"), personId));
return cb.in(noteRoot.get("noteId")).value(personQuery);
}
};
}
I am not sure how to do that with Predicates, as I usually dont use them, but in JPQL (or HQL, which is similar), you can do something like this:
SELECT Note n FROM Person.notes WHERE XXXX
It is basically the same thing as doing this in SQL
SELECT n.noteId FROM person as p JOIN persons_notes pn ON pn.person=p.personId JOIN notes as n ON n.noteId=pn.noteId
I would venture a guess that the Predicate method has similar abilities as described above.

Resources