There are two entities POST and TAG they are connected by a many-to-many relationship.
Post
#ManyToMany
#JoinTable(name = "posts_tags",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
private List<Tags> tags = new ArrayList<>();
Tag
#ManyToMany(mappedBy = "tags")
#JsonIgnore
private List<Posts> posts = new ArrayList<>();
Depending on which Tag I chose, should show me all posts dependent on him.
All posts should be displayed using pagination and sorting which user selects.
The problem is that sorting and pagination are ignored.
My first try
public Page<Posts> getPostsByTag(String tag, Integer page, Integer size, String sort, String dir){
Tags tags = tagsRepository.findByTagName(tag);
Pageable pageable = PageRequest.of(page,size, dir.equals("asc") ? Sort.Direction.ASC : Sort.Direction.DESC,sort);
return new PageImpl<>(tags.getPosts(),pageable,tags.getPosts().size());
}
My second try
public Page<Posts> getPostsByTag(String tag, Integer page, Integer size, String sort, String dir){
Tags tags = tagsRepository.findByTagName(tag);
PagedListHolder pagedListHolder = new PagedListHolder(tags.getPosts());
pagedListHolder.setPage(page);
pagedListHolder.setPageSize(size);
pagedListHolder.setSort(new MutableSortDefinition(sort, true ,dir.equals("asc")));
return new PageImpl<>(pagedListHolder.getPageList());
}
On the second try, pagination works, but sorting doesn't.
How to get tags.getPosts() list sorted?
Try with OrderBy anotation
#ManyToMany(mappedBy = "tags")
#JsonIgnore
#OrderBy("direction")
private List<Posts> posts = new ArrayList<>();
Your first try seems to correct but you have two missing points as I guess.
The first one is, you should definitely make your query for getting posts from the post repository by providing the paging details.
The second one is in order to do that you should define Pageable in your repository as a parameter.
#Repository
public interface PostDao extends JpaRepository<Posts, UUID> {
public Page<Posts> findByTagsName(String name, Pageable pageable);
}
After that you can use your repository in your service like below:
#Override
public Page<Posts> getPostsByTag(String tag, Integer page, Integer size, String sort, String dir) {
PageRequest request = PageRequest.of(page, size, Direction.fromString(dir), sort);
return postDao.findByTagsName(tag, request);
}
This will provide you the expected behavior with the abilities of both paging and sorting dynamically. Hope this helps. Cheers!
Related
I'm using Spring boot JPA to get list of objects (Using Java 8 now). Each object has relationships and I use the related objects also to transform to a dto list.
Let's say I have below model classes.
public class Product {
#EmbeddedId
private ProductId id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "userid", referencedColumnName = "USER_ID")
#MapsId("userId")
private User owner;
}
public class User {
#Id
#Column(name = "USER_ID")
private Long userId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "gp_code", referencedColumnName = "GP_CODE")
#JoinColumn(name = "userid", referencedColumnName = "USER_ID")
private UserGroup userGroup;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula = #JoinFormula(value = "country_id", referencedColumnName = "COUNTRY_ID")),
#JoinColumnOrFormula(column = #JoinColumn(name = "region_code", referencedColumnName = "REGION_CODE")) })
private Country country;
}
I do query for List<Product> and using stream I'm converting it into a dto object. During which I call the related entity to get the data. I have the below code and works fine unless the list is too much. If I have 1000+ items in the list it takes around 30 seconds.
I believe because of lazy loading this is happening. What is the best way to optimize this?
One option is to do pagination, but I cannot do it. I need all results together.
Is there any way to parallelly execute this? I tried to call parellelStream() instead of stream(), but it's same result.
public List<ProductDTO> getProducts(String countryId) {
//List<Product> products = Query Result
List<ProductDTO> productsList = products.stream().filter(isOwnerFromCountryAndInAnyGroup(countryId))
.map(product -> getProductDTO(product)).collect(Collectors.toList());
}
private Predicate<? super Product> isOwnerFromCountryAndInAnyGroup(String countryId) {
return product -> {
User user = product.getOwner();
return null != user && null != user.getCountry()
&& user.getCountry().getCountryId().equals(countryId) && (null != user.getUserGroup());
};
}
private ProductDTO getProductDTO(Product product) {
ProductDTO productDTO = new ProductDTO();
productDTO.setProductNbr(product.getId().getProductNbr());
productDTO.setPrice(product.getPrice());
productDTO.setOwnerName(product.getOwner().getName());
return productDTO;
}
Edit
I missed to add the line productDTO.setOwnerName(product.getOwner().getName()); for the purpose of asking question here. With query or using filter I'm getting the correct number of results. And with lazy loading, query returns faster and then while calling getOwner() for each row, the process takes time (30 seconds).
And with FethType.EAGER, the query takes similar time(30 seconds) and then processes faster. Either way it is similar time.
To fasten the process, is there any way to execute the stream code block in parallel and collect all results together in list?
public List<ProductDTO> getProducts(String countryId) {
//List<Product> products = Query Result
List<ProductDTO> productsList = products.stream().filter(isOwnerFromCountryAndInAnyGroup(countryId))
.map(product -> getProductDTO(product)).collect(Collectors.toList());
}
From your use case here I am pretty confident that it is not the creation of DTO that takes time. It is that you retrieve a huge set from database (even the complete table of Products) and then you filter for a relation with a specific country just from java.
So Step1 optimization:
If you want to filter for products that are associated with a user from a specific country then this can go on JPA level and translated in optimal way in database. Then the allocation of resources (memory, cpu) would be much more optimal, instead of your java application trying to load a huge data set and filter it there.
#Query("SELECT p FROM Product p where p.owner IS NOT NULL AND p.owner.userGroup IS NOT NULL AND p.owner.country IS NOT NULL AND p.owner.country.id = :countryId")
List<Product> findProductRelatedWithUserFromCountry(#Param String countryId);
and remove the filtering from your method getProducts.
Step2 optimization:
In addition to the above, not only you can pass the java filtering in the database query by moving it to JPA layer but you can also optimize the query a bit more by defining in JPA that you want to load the associated Owner as well so that it doesn't hit later the database to retrieve it when you create the DTO. You can achieve this with join fetch, so your query should now become:
#Query("SELECT p FROM Product p JOIN FETCH p.owner own where p.owner IS NOT NULL AND own.userGroup IS NOT NULL AND own.country IS NOT NULL AND own.country.id = :countryId")
List<Product> findProductRelatedWithUserFromCountry(#Param String countryId);
Step3 optimization:
If we want to take it an extra step further it seems that most times using DTO projections would speed up the execution. This can happen as the query would define only specific information it needs to retrieve and convert into DTO instead of the complete entities.
So your query now would be:
#Query("SELECT new org.your.package.where.dto.is.ProductDTO(p.id.productNbr, p.price, own.name) FROM Product p JOIN FETCH p.owner own where p.owner IS NOT NULL AND own.userGroup IS NOT NULL AND own.country IS NOT NULL AND own.country.id = :countryId")
List<ProductDTO> findProductRelatedWithUserFromCountry(#Param String countryId);
Also remember to have the DTO constructor used in the JPA query available in your ProductDTO.class.
How can we get the count of OneToMany field of JPA entity as querying count for each parent entity while fetching as a list is costly and there is no way in JPA Repository.
I want to get the number of likes and comments for each PostEntity. The field is Lazy fetch type and if I call likes.size() or comments.size() then it will load all of the comments and likes from database and there can be thousands of comments and likes.
I know I can create a seperate repo for likes and comments to get the counts but while calling method from PostRepository how to get the counts for each and every entity? What is the best and efficient way?
Parent Entity
#Entity
#Table(name = "posts")
#Getter
#Setter
public class PostEntity extends MappedSuperClassEntity<UserEntity> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Nullable
private String title;
#Nullable
private String postText;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private UserEntity user;
#Nullable
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "community_id")
private CommunityEntity community;
#OneToMany(fetch = FetchType.LAZY)
private List<CommentEntity> comments;
#OneToMany(fetch = FetchType.LAZY)
private List<LikeEntity> likes;
#Transient
private int numberOfLikes;
#Transient
private int numberOfComments;
}
I would like to get the likes and comments count for each PostEntity while querying for the list of posts.
My Repo
public interface PostsRepository extends PagingAndSortingRepository<PostEntity, Integer> {
#Query(value = "SELECT P FROM PostEntity P WHERE P.user.id = :userId ORDER BY P.createdDate DESC")
Page<PostEntity> getUserPosts(int userId, Pageable pageable);
#Query(value = "select P from PostEntity P where p.community.id = :communityId order by P.createdDate desc")
Page<PostEntity> getCommunityPosts(int communityId, Pageable pageable);
}
I searched for a lot and someone suggested to use #Formula annotation for custom queries on the entity field but #Formula is hibernate specific and don't know if it works with #Transient field. Is there any JPA specific way to do that as it's a common problem.
You need "LazyCollection" annotation with EXTRA option.
#OneToMany(fetch = FetchType.LAZY)
#LazyCollection(LazyCollectionOption.EXTRA)
private List<CommentEntity> comments;
This annotation would allow to access "size()" without loading.
You can check this article.
https://www.baeldung.com/hibernate-lazycollection
Sometimes, we're only concerned with the properties of the collection, and we don't need the objects inside it right away. For example, going back to the Branch and the Employees example, we could just need the number of employees in the branch while not caring about the actual employees' entities. In this case, we consider using the EXTRA option. Let's update our example to handle this case. Similar to the case before, the Branch entity has an id, name, and an #OneToMany relation with the Employee entity. However, we set the option for #LazyCollection to be EXTRA:
I try to add comment but i have no writing comment access because of reputation so i send an answer.
public class Item { #ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) #JoinColumn(name = "headingName", nullable = true) private Heading heading; ... }
public class Heading { #OneToMany(mappedBy = "heading", cascade = CascadeType.ALL) #OrderBy("description ASC") private List<Item> items; ...}
#Repository
public interface HeadingRepository extends JpaRepository<Heading, String> {
List<Heading> findByItemsDescriptionContainsIgnoreCase(String description);
}
#PostMapping("/search")
public String search(#RequestParam("text") String search, Model model) {
List<Heading> headings = headingService.findByItems_Description(search);
return "Index";
}
I got a h2 database connected to the website and I am trying to get a list of heading which includes a list of items but I am getting a list of all items on that heading including the item I search but I need multiple headings and multiple items on that item list. I need only the item or characters that I searched. Please help me.
i have a table
Permission:
id
name
desc
what i am doing right now
is to make a query that returns a permission object then put the values in the map programmatically
1- But i was wondering if it's possible to make an HQL (or native sql if not possible) to select the permission_id, permission_name and return them in a map.
2- is it possible to return map in one to many relationship instead of following list or set
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "perm_cat_map", joinColumns = { #JoinColumn(name = "perm_cat_id") }, inverseJoinColumns = { #JoinColumn(name = "permission_id") })
private List<Permission> permissions = new ArrayList<Permission>(0);
is it possible to have something like:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "perm_cat_map", joinColumns = { #JoinColumn(name = "perm_cat_id") }, inverseJoinColumns = { #JoinColumn(name = "permission_id") })
private Map<String,String> permissions = new ArrayList<String,String>(0);
where the two strings are permission_id, permission_name.
Use the select new map syntax in HQL to fetch the results of each row in a Map. Take a look at the following question, that addresses the issue: How to fetch hibernate query result as associative array of list or hashmap.
For instance, the following HQL: select new map(perm.id as pid, perm.name as pname) from Permission perm will return a List of Maps, each one with keys "pid" and "pname".
It is not possible to map an association to a Map<String, String>. It is possible to map the key of the Map to a column with the #MapKeyColumn annotation in the association. See this question, that also addresses the issue, for an example: JPA 2.0 Hibernate #OneToMany + #MapKeyJoinColumn. Here is another example.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "perm_cat_map",
joinColumns = { #JoinColumn(name = "perm_cat_id") },
inverseJoinColumns = { #JoinColumn(name = "permission_id") })
#MapKeyColumn(name="permission_id")
private Map<String, Permission> permissions = new HashMap<String,Permission>(0);
try like this,
Session session = sessionFactory.getCurrentSession();
String HQL_QUERY = "select new map(user.id as id, user.firstName as fullName) from User user";
List<Map<String,String>> usersList = session.createQuery(HQL_QUERY).list();
1- But i was wondering if it's possible to make an HQL (or native sql
if not possible) to select the permission_id, permission_name and
return them in a map.
its posible with Resulttransformer
String queryString="select id, name from Permission ";
List<List<Object>> permission= session.createQuery(queryString)
.setResultTransformer(Transformers.TO_LIST).list();
//now you just expect two columns
HashMap<Integer,String> map= new HashMap<Integer,String>();
for(List<Object> x: permission){
map.put((Integer)x.get(0),(String)x.get(1))
}
String sqlQuery="select userId,name,dob from user"
Pass the query to following method.
public List<Map<String,Object>> getDataListBySQL(final String sql, final Long adId){
List<Map<String,Object>> list=(List<Map<String,Object>>)getHibernateTemplate().executeFind(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException,SQLException {
Query query=session.createSQLQuery(sql);
query.setParameter("adId", adId);
return query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list();
}
});
return list;
}
Iterate this list in this way-
for(int i=0;i<list.size();i++){
Map<String,Object> map=list.get(i);
System.out.println(map.get("userId"));
System.out.println(map.get("name"));
}
In JPA 2.0 (which recent versions of Hibernate support), you can map collections of primitives using an #ElementCollection annotation.
For some samples of such mappings see the hibernate collections docs.
If you're not actually mapping it in this way but want to create a map using either HQL or a Criteria query, you can create a ResultTransformer to create a map from the returned result set.
Judging from Xavi's answer, I guess there is also support in HQL for creating a map without using a transformer.
I have such entity:
#Entity
public class Album {
private Integer id;
private Integer ownerId;
private String name;
private String description;
private Date created;
#OneToMany #JoinColumn(name = "albumId")
private Set<AlbumUser> users = new HashSet<AlbumUser>();
#OneToMany #JoinColumn(name = "albumId")
private Set<Picture> pictures = new HashSet<Picture>();
}
and another one:
#Entity
public class Picture {
private Integer id;
private Integer creatorId;
private Integer albumId;
private Date created;
private String title;
private String description;
#ManyToOne #JoinColumn(name = "eventId")
private Event event;
}
Using Criteria API I want to get unique AlbumDs with filtered set of Picturs. I try something like this:
public Album read(Integer albumId, Set<Integer> picFilter) {
Criteria crit = getCurrentSession().createCriteria(Album.class, "album");
crit.add(Restrictions.idEq(albumId));
if (picFilter != null && !picFilter.isEmpty()) {
crit = crit.createAlias("album.pictures", "picture");
crit.add(Restrictions.in("picture.event.id", picFilter));
crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
Album resultDs = (Album) crit.uniqueResult();
return resultDs;
}
And here I get Album with all pictures associated. They're not filtered at all.
When I try to execute query printed by a logger, I get only four rows wich is the number of pictures with given eventId, but in the Album I get all pictures.
I also tried other ResultTransformers, but eventualy got many result (4) not distinct one.
What do I miss or do wrong?
You can not filter the content of Collections associated with an entity by including Restrictions on the Collection in the query. The query will only fetch the Albums. The content of the Collection can be fetched later, when the Collection is accessed. All you do is filter the Albums to retrieve only those Albums that contain the Pictures with the event ids.
If the Collection would only contain the Pictures that match your Criteria and you would get a partial Collection it would cause problems on updates, because Hibernate then think the filtered items have been removed and would update the database to reflect that change, actually removing the items from the Collection.
If you want to receive only some items from a Collection you can use the Session.createFilter() method. The only problem is, that it only supports HQL queries currently.
I recall this being an issue for something I did recently. Have you tried this:
if (picFilter != null && !picFilter.isEmpty()) {
Criteria subCriteria = crit.createCriteria("album.pictures"); // Or just 'pictures?'
Disjunction or = Restrictions.disjunction();
for (Integer id : picFilter)
or.add(Restrictions.idEq(id));
subCriteria.add(or);
crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
try this:
Criteria crit = getCurrentSession().createCriteria(Album.class, "album");
crit.add(Restrictions.idEq(albumId));
if (picFilter != null && !picFilter.isEmpty()) {
crit.createAlias("album.pictures", "picture");
crit.createAlias("picture.event", "event");
crit.add(Restrictions.in("event.id", picFilter));
crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
If you use alias with left_join, it will return just sub object which satisfy related condition. Otherwise it returns main object which satisfy conditions but with all of sub object set.
crit = crit.createAlias("album.pictures", "picture", CriteriaSpecification.LEFT_JOIN);
This method is deprrecated in some hibernate version, if so you can use below solution for it too:
criteria with filtered complex set