) I have some problem with sorting and spring pageable when I use QueryDSL. I need quite advanced sorting, not just by the fields of the object stored in the database in the same table.
This is my model in approx:
#Getter
#Setter
#Entity
public class Book {
#Id
#GeneratedValue
private long id;
#OneToMany
private Set<Category> cats = new HashSet<>()
}
#Getter
#Setter
#Entity
public class Category{
private long id;
private Name name;
}
public enum Name{
WINTER,
SUN,
SUMMER
}
Now, this what I want to do is sort (desc and asc) Book (I have many Books) by Category id only if Category has name SUN. And i want to pass Qsort as Sort interface to PageRequest sping class.
I have no idea how I can achieve it. I try many way but none of these are even close to resolve this problem (for example some subquery with invoke any() on collection and use Qsort class). I affraid I made a mistake using Query DSL rather than Criteria Api. Can someone direct me? I will be very grateful.
Best Regards
Related
Fairly new to the hibernate-core integrate with JPA. Our entities look like
#MappedSuperclass
abstract class BasicEntity {
#Id
#GenericGenerator(name="uuid",strategy="uuid")
#GeneratedValue(generator="uuid")
private String id;
}
#Entity
#Table(name = "book")
class Book extends BasicEntity {
#ManyToOne
#JoinColumn(name="book_genre")
Genre genre
#Column(name="texture")
String texture;
}
#Data //lombok
#Entity
#Table(name="user_book_mapping")
class UserBookMapping extends BasicEntity {
#ManyToOne
#JoinColumn(name = "book_id")
private Book book;
#Column(name="user_id")
String user_id;
}
What I am trying to perform is for every book read by a user, upsert(a book can be read twice by the same user) the UserBookMapping table. Request object includes an array of user and books [{userId, bookId}] attributes for me to process.
Currently I am stuck with a solution where I am ending up fetching the Book entity from the primary table first, then fetching if such a row already exists with a combination of userId+bookId and then creating a new instance of UserBookMapping to write it to the database.
This doesn't look optimized in any sense to me. Theoretically(and in the NoSql world), what should be sufficient for me to do all of this would be to ensure valid bookIds are requested (EXISTS) and then perform an UPSERT for the UserBookMapping. But I am puzzled with both the ways to write and to formulate the data as java objects while computing.
Q1. Do I really need to construct the Book entity?
Q2. What is the clean way to do what I am trying to achieve?
As was suggested by #crizzis, you can do something like this:
Book book = entityManager.getReference(Book.class, bookId);
Long booksPerUserCount = entityManager.createQuery(
"select count(bu) from UserBookMapping bu where bu.book = :book and bu.user_id = :userId",
Long.class )
.setParameter("book", book)
.setParameter("userId", userId)
.getSingleResult();
if (booksPerUserCount == 0) {
UserBookMapping userBook = new UserBookMapping();
userBook.setBook(book);
userBook.setUserId(userId);
entityManager.persist(userBook);
}
But it will work only if you know for sure that a book with id bookId exists.
You can read about entityManager.getReference() in hibernate documentation.
Please also note that you should use #Data lombok annotation with caution in hibernate/jpa entity, see for example this article.
I have this entity model class (Book) where an author can have written multiple books.
#Entity
#Table(name="book")
public class Book {
#Id
#GeneratedValue
private Long id;
#Column(name="book_name")
private String bookName;
#Column(name="author_id")
private Long authorId;
//Setters and getters
}
In my Spring Boot project, I don't want to have an author table since there is a third part service that defines authors and their ids, how could I make a paginated repository call for all authorIds and their books?
I would want to have an endpoint that takes in (page, size) and returns a paginated list of a AuthorDTO like so:
public abstract class AuthorDTO implements Serializable {
public abstract Long authorId();
public abstract List<Book> books();
}
[
{
"authorId": 123,
"books": [...]
},
...
]
My first thought is to create a repository call not sure how we can get a page of a custom object. This is not valid below, but I would like to do something like the following.
Page<AuthorDTO> findAllBooksGroupedByAuthorId(Pageable pageable);
Your code seems to suggest you are trying to show the foreign key relationship in the class as an id.
JPA doesn't really do that.
JPA = "Java Persistence Language" i.e. you represent the relations between Java classes that mirror the database.
So in the database you may have a foreign key like 'author_id' in the book table, but in JPA/Java side it will be an "Author" class and not just a long/int.
I hope the below helps. Iv'e just slapped it on the main() of my code so it may not be perfect but I have left some comments as well.
Once you have a Page<Book> you may then want to map it to the DTO in java.
As the query is "get books by author id" we can assume that they all have the same author ID...so there is no real need to try get this projection in the database.
EDIT: Is it not at all possible to have a reference to the author from the 3rd party?
I.e. I don't know how you are populating "Book"...but could you not as you get "Book" from the 3rd party see if you have an Author entity with the books 'author_id' and not persist a new "Author" with that ID if it doesn't already exist?
In this case you can then do an AuthorRepo and simply query like:
Page<Author> findAllBy(Pageable page)
==========================================================================
Seeming as you are fetching a Page of Books by an author Id...you should really have a JPA relationship to show that:
#Entity
private class Book{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "book_name")
private String name;
//Technically this could be Many:Many as a book could have 2 authors? If so....#ManyToMany
//For simplicity (and what you seem to want) Many Books have ONE author.
#ManyToOne(fetch = FetchType.LAZY)
private Author author;
}
#Entity
private class Author{
//ID here - omitted for clarity
#Column(name = "authors_name")
String name;
//The Author has many books.
// Mapped by shows the bi-direction relationship. You can then do 'Author.getAuthorsBooks()'
//Lazy means it wont fetch all the books from database/(hibernate wont) when you do AuthorRepo.get()
//and will only do the `JOIN ON Books where` if you do Author.getAuthorsBooks()
#OneToMany(fetch = FetchType.LAZY,mappedBy = "author")
private Set<Book> authorsBooks = new HashSet<>();
}
private interface AuthorRepo extends JpaRepository<Author,Long>{
//Note the JPA syntax.
Page<Book> findAll(Pageable pageable);
}
EDIT:
I have only written this in an empty file...so it may need tweaking or has typos etc
If you can NOT have a separate entity for some reason for Author, having to keep your entity as it currently is...I'd do 2 queries.
I feel you can either do this in various ways.
If you MUST stick with spring's Pageable:
Get the page request in the controller and make it in to a new PageRequest.of(pagenum,size)
and feed it in to do the Page query below
List<Long> getPageOfUniqueAuthorIds(Pageable pageable);
This will give a page of author Ids.
Then you want to use that List of Longs (aithorIds) to do the second query.
List<AuthorDTOProjection> getBooksAndAuthorIdsWithAuthorsIdsIn(List<Long> authorIds);
#Entity
#Table(name="book")
public class Book {
#Id
#GeneratedValue
private Long id;
#Column(name="book_name")
private String bookName;
#Column(name="author_id")
private Long authorId;
//Setters and getters
}
private interface BookRepo extends JpaRepository<Book,Long> {
//The countQuery is required by Spring Paging.
//Hibernate will need to use the count query when doing paging on a native query.
#Query(nativeQuery = true,
value = "SELECT DISTINCT(author_id) FROM book b ",
countQuery = "SELECT count(*) \n" +
"FROM (SELECT DISTINCT(author_id) FROM book b) authorIds ")
List<Long> getPageOfUniqueAuthorIds(Pageable pageable);
//This is not paged. You want all books with the author IDs from the page query above.
List<Book> findAllByAuthorIdIn(List<Long> authorIds);
}
You will then have to map the Entity to the DTO in your service layer.
#Autowired
BookRepo bookRepo;
//This would be from the controller method...not declared here...
Pageable pageableFromController = PageRequest.of(0,10);
List<Long> pageOfUniqueAuthorIds = bookRepo.getPageOfUniqueAuthorIds(pageableFromController);
//Get All the books with Author Ids.
List<Book> books = bookRepo.findAllByAuthorIdIn(pageOfUniqueAuthorIds);
//Your abstract AuthorDTO.
abstract class AuthorDTO implements Serializable {
public abstract Long authorId();
public abstract List<Book> books();
}
//Your Author DTO needs to be implemented so I made a "View".
#AllArgsConstructor
class AuthorView extends AuthorDTO{
private long authorId;
private List<Book> books;
#Override
public Long authorId() {
return authorId;
}
#Override
public List<Book> books() {
return books;
}
}
//Get a List of the authorIds in the List<Books>. Could also use the original Page<Long> authorIds...
//As an author without a book is not possible in your database.
final List<Long> authorIdsInBooks = books.stream().map(it -> it.authorId).distinct().collect(Collectors.toList());
//Map the Ids of authors to an Impl of your abstract DTO. Personally I don't see why the AuthorDTO is abstract.
//I'd have expected just an abstract DTO class called "DTO" or something and then AuthorDTO impl that.
//But as the way you have it this will work. I guess you may want more impl of the AuthorDTO so maybe leave the AuthorDTO as abstract.
//This can be returned to client.
final List<AuthorView> authorViews = authorIdsInBooks.stream()
.map(authorId -> new AuthorView(
authorId,
books.stream().filter(it -> it.authorId.equals(authorId)).collect(Collectors.toList()))
)
.collect(Collectors.toList());
EDIT: For those who may pass through here in the future, I'd like to share something I found. Although it's not going to work for this particular situation, hibernate does have an #Where annotation. With this annotation you could have N amount of Sets. Each set would have #Where(clause='column=value'). I will not be using the #Where solution but it may work out for you.
I'm new(ish) to JPA Hibernate and am looking for some help. I don't necessarily know what to search for either so here is a quick example of my problem.
I need to take a Set and split it into multiple Set dependent on a value of a field.
Lets look at something simple:
public class Customer{
private String name;
private String age;
private Set<Order> orders;
}
public class Order{
private int amount;
private String status;
}
Goal: I would like to have Hibernate split my Customer's orders by their status into seperate Sets. Currently I use the #POSTLOAD annotation to loop over the Set and seperate them out accordinly. I would prefer Hibernate do it for me resulting in something like:
public class Customer{
//Irrelevant stuff from above...
private Set<Order> pendingOrders;
private Set<Order> completedOrders;
private Set<Order> canceledOrders;
}
These sets would be based off the order's status.
pendingOrders is where Order.getStatus() == "PENDING"
completedOrders is where Order.getStatus() == "COMPLETED"
canceledOrders is where Order.getStatus() == "CANCELED"
The tables are directly represented by the classes and are both considered "Entities". (Customer and Order)
If I haven't provided adequate information, could you please point me to a proper search for what I am "trying" to accomplish. In terms of JPA terminology I have no idea what to search for and could really use some help!
Thank you!
The best approach in your case is create a discriminator column and some extra classes to use it. Example
#Entity
#Table(name = "ORDER")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "STATUS", discriminatorType = DiscriminatorType.STRING)
public abstract class Order{
}
#Entity
#DiscriminatorValue(value = "PENDING")
public class PendingOrder extends Customer {
}
public class Customer{
//Irrelevant stuff from above...
private Set<PendingOrder> pendingOrders;
//same fo0r all others types
}
And if you notice the orders have some specific information only valid for pending orders or completed orders, then probably the best approach is use
#Inheritance(strategy = InheritanceType.JOINED)
Or
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
And add the specific columns in those new tables
So i have the following class:
#Entity("Item")
#ToString(callSuper=true, includeFieldNames=true)
#EqualsAndHashCode
public class Item {
public Item() {
this.timestamp = UUID.randomUUID();
}
#Id
#Getter
#Setter
private ObjectId id;
#Getter
#Setter
private UUID timestamp;
#Getter
#Setter
#Reference (lazy=false)
private GeneralInfo generalInfo;
}
while trying to save/update the item into mongodb, i have to first save the generalInfo class first and only then i can save the item class. i get it that the tables are dependand now, but is there a way to let morphia/mongo know what i want to use "deep update / save" or something like that?
No. Morphia does not support cascading saves like that. The use of references is largely orthogonal to the recommended way to model your domain (we encourage/recommend embedding documents) so supporting such a feature doesn't make much sense.
let be the following entities:
#Entity
public class Person {
#Id
long id;
#ManyToOne
Family fam;
#ManyToOne
Job job;
}
#Entity
class Family{
#Id
long id;
#OneToMany(mappedBy="fam")
#OrderBy("job")
List<Person> p;
}
#Entity
class Job implements Comparable<Job>{
#Id
long id;
String descr;
public int compareTo(Job o) {
return descr.compareTo(o.descr);
}
}
the problem i'm facing comes from the #orderby annotation which, far from what i was expecting, seems to be not supporting the comparable entities.
I'm using the eclipselink 2.3 and wondering if there's any other facility that deals with this problem.
Best Regards
George
#OrderBy is used to sort the entities using an "order by" clause in the generated SQL queries. So, of course, it doesn't the compareTo method. If you want to sort using Java, then just return a sorted list in the getter getPersons().