Pagination of an complex object into DTO. JPA #Query - spring

I have the following query to take some data regarding two entities in the same time and I receive an error.
#Query(value = "select new base.models.HRTableEntity( yr.user.gid, yr.user.id, yr.user.lastName || ' ' || yr.user.firstName, yr.user.position, yr.user.created,yr.genericField1,yr.genericField2) from YearlyReview yr where yr.year = :yr and yr.user.realDepartment = :dep and yr.user.city = :ct",
countQuery = "select count(yr.id) from YearlyReview yr where yr.year = :yr and yr.user.realDepartment = :dep and yr.user.city = :ct",
nativeQuery = false)
Page<HRTableEntity> getAllTableEntity(Pageable pageRequest, #Param("yr") int year, #Param("dep") String department, #Param("ct") String location);
I call this cunction with default Sort (gid: ASC) and receive the following error
org.hibernate.QueryException: could not resolve property: gid of: base.entities.YearlyReview
Repo interface:
public interface PageableYearlyReview extends CrudRepository<YearlyReview, UUID>
Yearly review have a member (user) of type ApplicationUser and I want to put information into DTO from yr.user.gid into HRTableEntry.gid.
What is the right way to do that ?
EDIT:
function call:
crunRepoYearTable.getAllTableEntity(PageRequest.of(pageNo - 1, pageSize, sort), year, realDepartment, user.getCity())
sort building
sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortedField).ascending() : Sort.by(sortedField).descending();
Entity structure:
public class YearlyReview {
#Id
private UUID id;
private int year;
#OneToOne
private ApplicationUser user;
....
}
public class ApplicationUser {
#Id
private String id;
private String gid;
.....
}

Problem solved:
for sort by gid need to send from the fronted like this user.gid. User is required to refer at AppUser and gid to access information. And change interface like this
public interface PageableYearlyReview extends CrudRepository<ApplicationUser, String>

Related

Filter data in spring boot

Dont know if this is possible but I want to call my data and filter it , so for example notification type will be for example 1 or 2 and then give me everything with a 1 and a 2
My Model
public class Notifications {
#Id
private String id;
private String notificationMsg;
private String notificationType;
private Date createdDate = new Date();
//Get all Notification by type
#RequestMapping(value = "/all/{notificationType}", method = RequestMethod.GET)
public List<Notifications> getAllByNotificationType(#PathVariable("notificationType") String notificationType) {
List<Notifications> notifications= this.notificationRepository.findByNotificationType(notificationType);
return user;
}
Or should I add to the model and create a interface like this
List<Notifications> findByNumber1ORNumber2ORNumber3(String Number1,String Number2,String Number3);
You can use in clause :
https://javadeveloperzone.com/spring/spring-jpa-query-in-clause-example/
List<Employee> findByEmployeeNameIn(List<String> names);

spring-data-jdbc: query entity containing 1-n relation with JOOQ

I am trying to load entities containing a reference to another entity (1-n) with the help of JOOQ (based on spring-data-jdbc).
I'm started extending the spring-data-jdbc-jooq-example.
The adjusted model with the 1-n relation:
#Data
public class Category {
private #Id Long id;
private String name, description;
private AgeGroup ageGroup;
private Set<SubCategory> subCategories;
public Category() {}
public Category(Long id, String name, String description, AgeGroup ageGroup) {
this(id, name, description, ageGroup, new HashSet<>());
}
public Category(Long id, String name, String description, AgeGroup ageGroup, Set<SubCategory> subCategories) {
this.id = id;
this.name = name;
this.description = description;
this.ageGroup = ageGroup;
this.subCategories = subCategories;
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public class SubCategory {
private #Id Long id;
private String title;
}
I wrote two queries, one via the #Query-Annotation in the CrudRepository and one with the help of JOOQ in the JooqRepository.
interface CategoryRepository extends CrudRepository<Category, Long>, JooqRepository {
#Query("SELECT * FROM category")
List<Category> findAllWithQuery();
}
public interface JooqRepository {
List<Category> findAllWithJooq();
}
public class JooqRepositoryImpl implements JooqRepository {
private final DSLContext dslContext;
public JooqRepositoryImpl(DSLContext dslContext) {
this.dslContext = dslContext;
}
#Override
public List<Category> findAllWithJooq() {
return dslContext.select()
.from(CATEGORY)
.fetchInto(Category.class);
}
}
(for me both methods should return the same result-set b/c they execute the same query?!)
But my unit-test fails:
#Test
public void exerciseRepositoryForSimpleEntity() {
// create some categories
SubCategory sub0 = new SubCategory(null, "sub0");
SubCategory sub1 = new SubCategory(null, "sub1");
Category cars = new Category(null, "Cars", "Anything that has approximately 4 wheels", AgeGroup._3to8, Sets.newLinkedHashSet(sub0, sub1));
// save category
repository.saveAll(asList(cars));
// execute
List<Category> actual = repository.findAllWithJooq();
List<Category> compare = repository.findAllWithQuery();
Output.list(actual, "JOOQ");
Output.list(compare, "Query");
// verify
assertThat(actual).as("same size of categories").hasSize(compare.size());
assertThat(actual.get(0).getSubCategories()).as("same size of sub-categories").hasSize(compare.get(0).getSubCategories().size());
}
with
java.lang.AssertionError: [same size of sub-categories]
Expecting actual not to be null
As you can see in the following output the sub-categories queried by JOOQ will not be loaded:
2019-11-26 16:28:00.749 INFO 18882 --- [ main] example.springdata.jdbc.jooq.Output : ==== JOOQ ====
Category(id=1,
name=Cars,
description=Anything that has approximately 4 wheels,
ageGroup=_3to8,
subCategories=null)
2019-11-26 16:28:00.749 INFO 18882 --- [ main] example.springdata.jdbc.jooq.Output : ==== Query ====
Category(id=1,
name=Cars,
description=Anything that has approximately 4 wheels,
ageGroup=_3to8,
subCategories=[SubCategory(id=1,
title=sub0),
SubCategory(id=2,
title=sub1)])
This is the used database-shema:
CREATE TABLE IF NOT EXISTS category (
id INTEGER IDENTITY PRIMARY KEY,
name VARCHAR(100),
description VARCHAR(2000),
age_group VARCHAR(20)
);
CREATE TABLE IF NOT EXISTS sub_category (
id INTEGER IDENTITY PRIMARY KEY,
title VARCHAR(100),
category INTEGER
)
In the JOOQ variant, JOOQ does the conversion from ResultSet to object instances. Since JOOQ doesn't know about the interpretation of aggregates as it is done by Spring Data JDBC it only hydrates the Category itself, not the contained Set of SubCategory.
Spring Data JDBC on the other hand interprets the structure of the Category and based on that executes another statement to load the subcategories.

Using Spring Data JPA - how to save ONLY UNIQUE items in child entity while saving parent entity and using CascadeType=PERSIST

I am preparing simple Spring app. I have 2 entities :
Book.class (parent) and Author.class (child): with #OneToMany from Author view and #ManyToOne(cascade=CascadeType.PERSIST) from Book view relations. While saving new Book - also Author is being saved and added to DB( mySql)- which is what I want. But I cannot understand why Spring adds Author - if such item already exists. How to change the code to make sure that only unique Authors will be added to DB and there will be no duplicates in author table in DB?
I've added hashCode and equals methods to Author class but it did not help.
I've tried to change also Cascade.Type but also did not help.
The Author.class(part of code):
#Entity
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#OneToMany(mappedBy = "author")
#JsonManagedReference(value = "book-author")
private Set<Book> books = new HashSet<Book>();
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return Objects.equals(getFirstName(), author.getFirstName()) &&
Objects.equals(getLastName(), author.getLastName());
}
#Override
public int hashCode() {
return Objects.hash(getFirstName(), getLastName());
}
And the Book.class(part of code):
#Entity
public class Book {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String title;
#ManyToOne( cascade=CascadeType.PERSIST)
#JoinColumn(name = "author_id", unique = true)
#JsonBackReference(value="book-author")
private Author author;
Edit 1
BookServiceImpl.class
#Override
public BookDto addBookDto(BookDto bookDto) {
Book book = bookConverter.apply(bookDto);
bookRepository.save(book);
return bookDtoConverter.apply(book);
}
AuthorServiceImpl.class
#Override
public Author findAuthor(String firstName, String lastName) {
Optional<Author> authorByNameOptional = authorRepository.findByFirstNameAndLastName(firstName, lastName);
if (authorByNameOptional.isPresent()) {
return authorByNameOptional.get();
} else {
Author newAuthor = new Author();
newAuthor.setFirstName(firstName);
newAuthor.setLastName(lastName);
return newAuthor;
}
And BookWebController.class
#PostMapping("/addWebBook")
public String addBook(#ModelAttribute(name = "addedBook") BookDto addedBook, Model model) {
Author author1 = addedBook.getAuthor();
Author author = authorService.findAuthor(author1.getFirstName(), author1.getLastName());
addedBook.setAuthor(author);
bookService.addBookDto(addedBook);
return "redirect:/message?msg";
}
Would be greatful for any hint as I am quite new to this area :-)
Let me suggest that your Author object in Book has empty primary key field? Hibernate's logic in this case: empty id means new row to insert. It may work correctly if you set a primary key to author. For example, user can find author (with it's PK) or add new (without PK) and call save(book) method which will cascadely persists only new author. In most cases that usually works like that.
Another moment to pay attention, that if you wanna keep author's uniqueness in database, than you must to put constraint on the author's entity.
For example if each author must have unique first name and last name it may look something like this:
#Entity
#Table(uniqueConstraints= #UniqueConstraint(columnNames={"first_name", "last_name"}))
public class Author {
...
After that, DataIntegrityViolationException will be thrown on duplicating value insertion and your database will stay clean of duplicates.

Spring Data JPA map the native query result to Non-Entity POJO

I have a Spring Data repository method with a native query
#Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
GroupDetails getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
and I'd like to map the result to Non-Entity POJO GroupDetails.
Is it possible and if so, could you please provide an example ?
I think the easiest way to do that is to use so called projection. It can map query results to interfaces. Using SqlResultSetMapping is inconvienient and makes your code ugly :).
An example right from spring data JPA source code:
public interface UserRepository extends JpaRepository<User, Integer> {
#Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
public static interface NameOnly {
String getFirstname();
String getLastname();
}
}
You can also use this method to get a list of projections.
Check out this spring data JPA docs entry for more info about projections.
Note 1:
Remember to have your User entity defined as normal - the fields from projected interface must match fields in this entity. Otherwise field mapping might be broken (getFirstname() might return value of last name et cetera).
Note 2:
If you use SELECT table.column ... notation always define aliases matching names from entity. For example this code won't work properly (projection will return nulls for each getter):
#Query(value = "SELECT user.firstname, user.lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
But this works fine:
#Query(value = "SELECT user.firstname AS firstname, user.lastname AS lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
In case of more complex queries I'd rather use JdbcTemplate with custom repository instead.
Assuming GroupDetails as in orid's answer have you tried JPA 2.1 #ConstructorResult?
#SqlResultSetMapping(
name="groupDetailsMapping",
classes={
#ConstructorResult(
targetClass=GroupDetails.class,
columns={
#ColumnResult(name="GROUP_ID"),
#ColumnResult(name="USER_ID")
}
)
}
)
#NamedNativeQuery(name="getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")
and use following in repository interface:
GroupDetails getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
According to Spring Data JPA documentation, spring will first try to find named query matching your method name - so by using #NamedNativeQuery, #SqlResultSetMapping and #ConstructorResult you should be able to achieve that behaviour
I think Michal's approach is better. But, there is one more way to get the result out of the native query.
#Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
String[][] getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
Now, you can convert this 2D string array into your desired entity.
You can write your native or non-native query the way you want, and you can wrap JPQL query results with instances of custom result classes.
Create a DTO with the same names of columns returned in query and create an all argument constructor with same sequence and names as returned by the query.
Then use following way to query the database.
#Query("SELECT NEW example.CountryAndCapital(c.name, c.capital.name) FROM Country AS c")
Create DTO:
package example;
public class CountryAndCapital {
public String countryName;
public String capitalName;
public CountryAndCapital(String countryName, String capitalName) {
this.countryName = countryName;
this.capitalName = capitalName;
}
}
This is my solution for converting to Map and then to custom Object
private ObjectMapper objectMapper;
public static List<Map<String, Object>> convertTuplesToMap(List<?> tuples) {
List<Map<String, Object>> result = new ArrayList<>();
tuples.forEach(object->{
if(object instanceof Tuple single) {
Map<String, Object> tempMap = new HashMap<>();
for (TupleElement<?> key : single.getElements()) {
tempMap.put(key.getAlias(), single.get(key));
}
result.add(tempMap);
}else{
throw new RuntimeException("Query should return instance of Tuple");
}
});
return result;
}
public <T> List<T> parseResult(List<?> list, Class<T> clz){
List<T> result = new ArrayList<>();
convertTuplesToMap(list).forEach(map->{
result.add(objectMapper.convertValue(map, clz));
});
return result;
}
public static class CustomDTO{
private String param1;
private Integer param2;
private OffsetDateTime param3;
}
public List<CustomDTO> doSomeQuery(){
Query query = entityManager.createNativeQuery("SELECT param1, param2 param3 ... ", Tuple.class);
return parseResult(query.getResultList(), CustomDTO.class);
}
Use the default method in the interface and get the EntityManager to get the opportunity to set the ResultTransformer, then you can return the pure POJO, like this:
final String sql = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = ? WHERE g.group_id = ?";
default GroupDetails getGroupDetails(Integer userId, Integer groupId) {
return BaseRepository.getInstance().uniqueResult(sql, GroupDetails.class, userId, groupId);
}
And the BaseRepository.java is like this:
#PersistenceContext
public EntityManager em;
public <T> T uniqueResult(String sql, Class<T> dto, Object... params) {
Session session = em.unwrap(Session.class);
NativeQuery q = session.createSQLQuery(sql);
if(params!=null){
for(int i=0,len=params.length;i<len;i++){
Object param=params[i];
q.setParameter(i+1, param);
}
}
q.setResultTransformer(Transformers.aliasToBean(dto));
return (T) q.uniqueResult();
}
This solution does not impact any other methods in repository interface file.
USE JPA PROJECTIONS
In your case it may be desirable to retrieve data as objects of customized types. These types reflect partial views of the root class, containing only properties we care about. This is where projections come in handy.
first declare Entity as #immutable
#Entity
#Immutable
public class Address {
#Id
private Long id;
set your Repository
public interface AddressView {
String getZipCode();
}
Then use it in a repository interface:
public interface AddressRepository extends Repository<Address, Long> {
#Query("EXEC SP_GETCODE ?1")
List<AddressView> getAddressByState(String state);
}
If you are looking for running a custom SQL query in spring boot with #repository and #service structures. Please have a look.
https://stackoverflow.com/a/71501509/4735043
You can do something like
#NamedQuery(name="IssueDescriptor.findByIssueDescriptorId" ,
query=" select new com.test.live.dto.IssuesDto (idc.id, dep.department, iss.issueName,
cat.issueCategory, idc.issueDescriptor, idc.description)
from Department dep
inner join dep.issues iss
inner join iss.category cat
inner join cat.issueDescriptor idc
where idc.id in(?1)")
And there must be Constructor like
public IssuesDto(long id, String department, String issueName, String issueCategory, String issueDescriptor,
String description) {
super();
this.id = id;
this.department = department;
this.issueName = issueName;
this.issueCategory = issueCategory;
this.issueDescriptor = issueDescriptor;
this.description = description;
}

Is it possible to avoid typecast on custom #Query?

Imagine that we have an entity:
#Entity
public class Person implements Serializable {
#Id
private String name;
private Long age;
private Boolean isMad;
...
}
And a repository with a trivial (and unnecessary) example for a custom query:
#Repository
public interface PersonRepository extends PagingAndSortingRepository<Info, String> {
#Query("select p.isMad, count(*) from Person p group by p.isMad")
List<Object> aggregateByMadness();
}
Now to parse this List we need to do something like this:
for (Object element : list) {
Object[] result = (Object[]) element;
Boolean isMad = (Boolean) result[0];
Long count = (Long) result[1];
}
which is a pain, can we cast the result of the query directly to List of a POJO?
Yes, you could use the JPQL construction expression:
package com.foo;
public class Madness {
public Madness(boolean isMad, Number count) { /* ...*/ }
}
And in your repository:
#Query("select new com.foo.Madness(p.isMad, count(*)) from Person p group by p.isMad")
List<Madness> aggregateByMadness();

Resources