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
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.
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!
I have a class Song that contains a lazy association to CoverArt class
#OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name = "recNo")
private List<CoverArt> coverArts;
That is fine, I don't usually need the CoverArt object, but sometimes I do
For SQL efficiency
Because I want to use the Song class on the EventThread, and user may try to access the CoverArt outside of HibernateSession, and so it will fail
So How would I modify a method such as
public static List<Song> getSongsWithCoverArtFromDatabase(Session session, List<Integer> ids)
{
Criteria c = session
.createCriteria(Song.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.add(Restrictions.in("recNo", ids));
List<Song> songs = c.list();
return songs;
}
so that it retrieved CoverArt for each song
You can use setFetchMode of Criteria to set the fetch mode during runtime as below,
Criteria c = session
.createCriteria(Song.class)
.setFetchMode("coverArts", FetchMode.EAGER)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.add(Restrictions.in("recNo", ids));
For more information, https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/FetchMode.html and https://kodejava.org/how-do-i-set-the-fetch-mode-for-criteria-association/
I want to ask about what is the most efficient way to search about specific data from a database without doing a for loop in all of the records?
I have a project on java spring and I have this Entity:
#Entity
#Table(name = "USERS") public class USERS {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "UID")
private Integer id;
#Column(name = "FName")
private String firstName;
#Column(name = "SName")
private String secondName;
#Column(name = "TName")
private String thirdName;
#Column(name = "LName")
private String fourthName;
#Column(name = "Email")
private String email;
#Column(name = "PW")
private String password;
#Column(name = "MNumber")
private String mobileNumber;
#Column(name = "ISDeleted")
private boolean isUserDeleted;
//---------------------- Getters and Setters ----------------------
and I made this service:
public List<USERS> findAllActive() {
List<USERS> usersList = new ArrayList<USERS>();
for (USERS users: usersRepository.findAll()){
if (!users.isUserDeleted()){
usersList.add(users);
}
}
return usersList;
}
For example; I have one property for User, if he is active or not.
So, my question; what is the most efficient way to do get specific data like retrieving all of the active users from the DB without doing a for loop like in the code above? Because if the list of users is a 1 Million or more, it could have performance issues.
Assuming that you are using JpaRepository then you can create custom query.
#Query("SELECT u FROM USERS u WHERE u.userDeleted = false")
List<USERS> findNotDeletedUsers();
and then call usersRepository.findNotDeletedUsers();
First of all, use an index on the field you want to search on (this won't help you much if the column has only two distinct values, but will make a huge difference if the value has high sparsity).
#Entity
#Table(name = "USERS",
indexes = {
// not a huge performance gain, since the column values are true/false
#Index(name = "index_by_active", columnList="ISDeleted", unique = false),
// possible huge performance gain, since only the relevant records are scanned
#Index(name = "index_by_first_name", columnList="FName", unique = false)})
public class USERS {...}
Then, define a query method that uses the indexed field (if you are using spring data it would look as follows).
public interface UsersRepository extends CrudRepository<USERS, Long> {
List<USERS> findUsersByISDeleted(boolean deleted);
List<USERS> findUsersByFName(String name);
List<USERS> findUsersByFNameAndISDeleted(String name, boolean deleted);
}
Queries on indexed fields will leverage the underlying index and provide an efficient access plan (so you won't end up scanning the whole table in order to extract a subset of entities matching a given criteria).
The solution from #Madis is okay. But if you always want to get users which are not deleted in all queries, you can specify it on Entity:
#Entity
#Table(name = "USERS")
#Where("ISDeleted = false")
public class USERS {
So now the condition "ISDeleted = false" is automatically append to all queries from the UserRepository. You can use usersRepository.findAll() instead of.
You don't need to specify any sql query or where clause. CrudRepository will do it for you automatically. Just use below code and pass true/false on need basis
List<Users> findIsUserDeleted(boolean isDeleted)
I'm having an strange problem when I try to retrieve some entities from the database. The table where the entities lives just have 4 rows. When I try select all rows I get a list where the first and the last elements are loaded correct, however, the second and the third has all properties as null. Here is a print of my debug console:
The entity is simple, as you can see below:
#Entity
#Table(name = "Empresa")
public class Empresa implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID_EMPRESA")
private Integer idEmpresa;
#Basic(optional = false)
#Column(name = "NOME_EMPRESA")
#OrderColumn
private String nomeEmpresa;
#Column(name = "CNPJ")
private String cnpj;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "iDEmpresa", fetch = FetchType.LAZY)
private List<Cadastro> cadastroList;
}
If you want know how I am retrieving the entities, here is the code:
#Override
public List<T> recuperarTodos() {
Query query = entityManager.createQuery(criarQueryRecuperarTodos());
limitarQuantidadeDeRegistros(query);
return query.getResultList();
}
private String criarQueryRecuperarTodos() {
StringBuilder builder = new StringBuilder("SELECT e FROM ");
builder.append(classe.getSimpleName());
builder.append(" e");
builder.append(criarParametrosOrdenacao());
return builder.toString();
}
This is perfectly legal and expected situation. Hibernate uses dynamically generated proxies (hence javaassist objects, in the past hibernate used cglib as well) as placeholders for not fully fetched entities to allow lazy fetching. Because of this, generally speaking, you should not attempt to access attribute values directly. Using getters instead allows hibernate to issue an appropriate DB query and fill the entity. This can be a problem in some situations - for example, if the values are first requested outside the Hibernate session.