Spring Data JPA Projection with select distinct - spring

I have a database table which holds Metadata for documents. My task now is to get a list with documenttypes. The documenttypes are not unique in the database table but of course I want them to be in my list. The sql is very simple:
SELECT DISTINCT groupname, group_displayorder
FROM t_doc_metadata
ORDER BY group_displayorder;
I have learned that I can use projections to get a subset of fields from my entity DocMetadata. I solved this as follows. My Entity:
#Entity
#Table(name="T_DOC_METADATA")
#Data
public class DocMetadata {
..............
#Column(nullable=false)
private String displayname;
#Column(nullable=false)
private Integer displayorder;
#Column(nullable=false)
private String groupname;
#Column(name="GROUP_DISPLAYORDER",
nullable=false)
private Integer groupDisplayorder;
#Column(name="METADATA_CHANGED_TS",
nullable=false,
columnDefinition="char")
private String metadataChangedTimestamp;
..........
}
My inteface for projection:
public interface GroupnameAndOrder {
String getGroupname();
Integer getGroupDisplayorder();
void setGroupname(String name);
void setGroupDisplayorder(int order);
}
Now I thought I'd be extraordinary clever by adding these lines to my repository:
#Query("select distinct d.groupname, d.groupDisplayorder from DocMetadata d order by d.groupDisplayorder")
public List<GroupnameAndOrder> findSortedGroupnames();
Sadly, when iterating over the result list and calling getGroupname() the result is null.
So I changed the lines in my repository according to the documentation:
public List<GroupnameAndOrder> findBy();
Now I get the groupnames but of course they are not unique now. So it doesn't solve my problem.
Is there any way to receive a ordered list with unique groupnames?

You are trying to be too clever. Instead just write the proper find method and return the GroupnameAndOrder. Spring Data JPA will then only retrieve what is needed for the projection.
Something like this should do the trick.
List<GroupnameAndOrder> findDistinctByOrderByGroupDisplayorder();

Related

Spring hibernate orderBy on list element

#Entity
class Person{
private int id;
#OneToMany(mappedBy=owner)
private List<Pet> pets;
}
#Entity
class Pet{
private name;
private ZonedDateTime birthDate;
#ManyToOne
#JoinColumn(name="owner_id")
private Person owner;
}
I want to find all the persons and order them by their oldest pet birthday
The only way I can solve this is through #Formula , something like
#Entity
class Person{
private int id;
private List<Pet> pets;
#Formula("(SELECT p.birth_date FROM pet p WHERE p.owner_id = id order by p.birth_date ASC LIMIT 1)")
private ZonedDateTime oldestPetBirthday;
}
then
public List<Person> findPersonByOrderByOldestPetBirthdayAsc
But I don't want to touch raw sql, I am looking for something like
public List<Person> findPersonByOrderByPetsTop1OrderByBirthDateAsc
OR by using pageable something like:
PageRequest.of(page,pageSize,Sort.by(ASC, "pets.sort(BirthDateComparator).get(0)"))
is that possible?
Try to use #OrderBy annotation from #javax.persistence.OrderBy package on your one to many collection object.
#OrderBy("birthDate")
private List<Pet> pets;
Your solution with the formula is ok but suffers from some issues. Anyway, since you don't want to write SQL, you will have to use something like 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(Person.class)
public interface PersonDto {
#IdMapping
Long getId();
#Limit(limit = "1", order = "birthDate desc)
#Mapping("pets")
OldestPetDto getOldestPet();
#EntityView(Pet.class)
interface OldestPetDto {
#IdMapping
Long getId();
ZonedDateTime getBirthDate();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
PersonDto a = entityViewManager.find(entityManager, PersonDto.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
Page<PersonDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
Also, you can add a Sort for oldestPet.birthDate and it will work just like you would like it to!

Not able to search data in redis cache using spring crud repository by passing list of values for a property of the model saved in cache

We have model class saved in Redis as mentioned below:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#RedisHash("book")
public class Book implements Serializable {
private static final long serialVersionUID = 2208852329346517265L;
#Id
private Integer bookID;
#Indexed
private String title;
#Indexed
private String authors;
private String averageRating;
private String isbn;
private String languageCode;
private String ratingsCount;
private BigDecimal price;
}
We have title and authors as our indexed property.
Now we wanted to search all the records from Redis by passing title and a list of authors using the spring crud repository as mentioned below.
public interface BookSpringRepository extends CrudRepository<Book, String> {
List<Book> findAllByTitleAndAuthors(String title, List<String> authors);
}
Service layer:-
#Override
public Optional<List<Book>> searchBooksByTitleAndAuthorNames(String title, List<String>
autherNames) {
return Optional.ofNullable(bookSpringRepository.findAllByTitleAndAuthors(title,
autherNames));
}
Here we are getting below exception
Unable to fetch data from Spring data Redis cache using List of Integer or
String.
Getting error while fetching - "Resolved
[org.springframework.core.convert.ConversionFailedException: Failed to convert from type
[java.lang.String] to type [byte] for value 'Ronak';
nested exception is java.lang.NumberFormatException: For input string: "Ronak"]."
We would not want to convert the list of string/integer to byte as it is a time-consuming process and as we tried took so much amount of time. Also when the results are retrieved we will again have to convert back to normal integer or string values.
The other option is to loop through the list and pass a single value at a time to the Redis crud repository and this time Redis crud repository is happy but that will be a loop call to Redis and network latency.
We cannot add ID attributes on authors' property as these can be duplicate records.
Does the spring crud repository support the LIKE query in search that way we can create a unique id having these authors' names and make put ID annotation on that new derived property to search the records using spring crud repository using LIKE or contains kind of query.
Any suggestions here are highly appreciated!!
Try to add serialization to your redis key and value. This might help :
https://medium.com/#betul5634/redis-serialization-with-spring-redis-data-lettuce-codec-1a1d2bc73d26

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

Replacing entire contents of spring-data Page, while maintaining paging info

Using spring-data-jpa and working on getting data out of table where there are about a dozen columns which are used in queries to find particular rows, and then a payload column of clob type which contains the actual data that is marshalled into java objects to be returned.
Entity object very roughly would be something like
#Entity
#Table(name = "Person")
public class Person {
#Column(name="PERSON_ID", length=45) #Id private String personId;
#Column(name="NAME", length=45) private String name;
#Column(name="ADDRESS", length=45) private String address;
#Column(name="PAYLOAD") #Lob private String payload;
//Bunch of other stuff
}
(Whether this approach is sensible or not is a topic for a different discussion)
The clob column causes performance to suffer on large queries ...
In an attempt to improve things a bit, I've created a separate entity object ... sans payload ...
#Entity
#Table(name = "Person")
public class NotQuiteAWholePerson {
#Column(name="PERSON_ID", length=45) #Id private String personId;
#Column(name="NAME", length=45) private String name;
#Column(name="ADDRESS", length=45) private String address;
//Bunch of other stuff
}
This gets me a page of NotQuiteAPerson ... I then query for the page of full person objects via the personIds.
The hope is that in not using the payload in the original query, which could filtering data over a good bit of the backing table, I only concern myself with the payload when I'm retrieving the current page of objects to be viewed ... a much smaller chunk.
So I'm at the point where I want to map the contents of the original returned Page of NotQuiteAWholePerson to my List of Person, while keeping all the Paging info intact, the map method however only takes a Converter which will iterate over the NotQuiteAWholePerson objects ... which doesn't quite fit what I'm trying to do.
Is there a sensible way to achieve this ?
Additional clarification for #itsallas as to why existing map() will not suffice..
PageImpl::map has
#Override
public <S> Page<S> map(Converter<? super T, ? extends S> converter) {
return new PageImpl<S>(getConvertedContent(converter), pageable, total);
}
Chunk::getConvertedContent has
protected <S> List<S> getConvertedContent(Converter<? super T, ? extends S> converter) {
Assert.notNull(converter, "Converter must not be null!");
List<S> result = new ArrayList<S>(content.size());
for (T element : this) {
result.add(converter.convert(element));
}
return result;
}
So the original List of contents is iterated through ... and a supplied convert method applied, to build a new list of contents to be inserted into the existing Pageable.
However I cannot convert a NotQuiteAWholePerson to a Person individually, as I cannot simply construct the payload... well I could, if I called out to the DB for each Person by Id in the convert... but calling out individually is not ideal from a performance perspective ...
After getting my Page of NotQuiteAWholePerson I am querying for the entire List of Person ... by Id ... in one call ... and now I am looking for a way to substitute the entire content list ... not interively, as the existing map() does, but in a simple replacement.
This particular use case would also assist where the payload, which is json, is more appropriately persisted in a NoSql datastore like Mongo ... as opposed to the sql datastore clob ...
Hope that clarifies it a bit better.
You can avoid the problem entirely with Spring Data JPA features.
The most sensible way would be to use Spring Data JPA projections, which have good extensive documentation.
For example, you would first need to ensure lazy fetching for your attribute, which you can achieve with an annotation on the attribute itself.
i.e. :
#Basic(fetch = FetchType.LAZY) #Column(name="PAYLOAD") #Lob private String payload;
or through Fetch/Load Graphs, which are neatly supported at repository-level.
You need to define this one way or another, because, as taken verbatim from the docs :
The query execution engine creates proxy instances of that interface at runtime for each element returned and forwards calls to the exposed methods to the target object.
You can then define a projection like so :
interface NotQuiteAWholePerson {
String getPersonId();
String getName();
String getAddress();
//Bunch of other stuff
}
And add a query method to your repository :
interface PersonRepository extends Repository<Person, String> {
Page<NotQuiteAWholePerson> findAll(Pageable pageable);
// or its dynamic equivalent
<T> Page<T> findAll(Pageable pageable, Class<T>);
}
Given the same pageable, a page of projections would refer back to the same entities in the same session.
If you cannot use projections for whatever reason (namely if you're using JPA < 2.1 or a version of Spring Data JPA before projections), you could define an explicit JPQL query with the columns and relationships you want, or keep the 2-entity setup. You could then map Persons and NotQuiteAWholePersons to a PersonDTO class, either manually or (preferably) using your object mapping framework of choice.
NB. : There are a variety of ways to use and setup lazy/eager relations. This covers more in detail.

Select fews columns (DTO) with specification JPA

I am using spring-data-jpa version 1.5.1.RELEASE .
My domain is :
public class MyDomain{
....
....
private String prop1;
private String prop2;
......
......
}
My JPA Specification is:
public final class MyDomainSpecs {
public static Specification<MyDomain> search(final String prop1,final String prop2) {
return new Specification<MyDomain>() {
public Predicate toPredicate(Root<MyDomain> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// Some tests if prop1 exist .....
Predicate predicate1 = cb.equal(root.get("prop1"), prop1);
Predicate predicate2 = cb.equal(root.get("prop2"), prop2);
return cb.and(predicate1, predicate2);
}
};
}
}
My Repository :
public interface MyDomainRepository extends JpaRepository<MyDomain, Long>, JpaSpecificationExecutor<MyDomain> {
List<MyDomain> findAll(Specification<MyDomain> spec);
}
All is Working .
But my need (For performance DB tunning) is to not return and select all fields of MyDomain from DB .
I need to select only for example tree properties (prop1, prop2, prop3) , idealy in a DTO Object .
I don't want to convert My List<MyDomain> to List<MyDto> because i am tunning DB request .
So , I don't find any way to do that with spring-data-Jpa and Specification .
Any Idea ?
Thanks
This is not possible as for now. There is a ticket for this but no idea if it will be ever implmented: https://jira.spring.io/browse/DATAJPA-51
Create a special version of MyDomain (e.g. MyDomainSummary or LightMyDomain) that only includes the fields you want to map.
Basic example
Borrowed from the excellent JPA WikiBook.
Assume a JPA entity (i.e. domain class) like so:
#Entity
#Table(name="EMPLOYEE")
public class BasicEmployee {
#Column(name="ID")
private long id;
#Column(name="F_NAME")
private String firstName;
#Column(name="L_NAME")
private String lastName;
// Any un-mapped field will be automatically mapped as basic and column name defaulted.
private BigDecimal salary;
}
The SQL query generated will be similar to
SELECT ID, F_NAME, L_NAME, SALARY FROM EMPLOYEE
if no conditions (where clause) are defined. So, to generalize the basic case one can say that the number of queried columns is equal to the number of mapped fields in your entity. Therefore, the fewer fields your entity, the fewer columns included in the SQL query.
You can have an Employee entity with e.g. 20 fields and a BasicEmployee as above with only 4 fields. Then you create different repositories or different repository methods for both.
Performance considerations
However, I seriously doubt you'll see noticeable performance improvements unless the fields you want to omit represent relationships to other entities. Before you start tweaking here log the SQL that is currently issued against the data base, then remove the columns you want to omit from that SQL, run it again and analyze what you gained.

Resources